Skip to content

Hardcoded Source and IOC Exclusions Allow Malicious Package Payloads to Evade Detection #665

@k14uz

Description

@k14uz

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

  1. Create the directory structure:
$ mkdir -p /tmp/gd-py-evasion
$ cd /tmp/gd-py-evasion
  1. 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())
  1. 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",
)
  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)
  1. Now move the same payload under dist/ (Semgrep-excluded):
$ mkdir -p dist
$ mv installer.py dist
  1. 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",
)
  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]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions