Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion malware-scan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ reports 0 high and 3 legitimate medium findings.

Scanned: `.js .cjs .mjs .jsx .ts .tsx .json .vue .svelte .html .py .sh .yml .yaml`, `*.config.*`,
dotfile configs, and extensionless hooks in `.husky/` and `.githooks/`.
Skipped: `node_modules`, `vendor`, `dist`, `build`, `.next`, lockfiles, `*.min.js`.
Skipped everywhere: lockfiles, `*.map`, `*.min.js`.
Git modes (default, `--staged`, `--all-branches`) scan every tracked path, including committed
`vendor/`, `dist/`, `build/` and `node_modules/`. Payloads have been found in a committed
`vendor/` folder. The on-disk walk (`--path`) also skips untracked install and cache folders:
`node_modules`, `.next`, `.nuxt`, `coverage`, `.venv`/`venv`, `__pycache__`, `.turbo`, `.cache`.

## 1. CI gate (every project)

Expand Down
18 changes: 11 additions & 7 deletions malware-scan/scan_malicious.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@

TEXT_EXT = {".js", ".cjs", ".mjs", ".jsx", ".ts", ".tsx", ".json", ".vue",
".svelte", ".html", ".py", ".sh", ".yml", ".yaml"}
# Directories never descended into / never reported.
SKIP_DIRS = {"node_modules", ".git", "dist", "build", ".next", ".nuxt", "vendor",
"coverage", ".venv", "venv", "__pycache__", ".turbo", ".cache"}
# Directories skipped by the on-disk walk (--path) only: installed or generated
# content that is normally untracked. Git modes scan every tracked path, because a
# committed vendor/, dist/ or build/ folder is as attacker-writable as src/.
# vendor/, dist/ and build/ are deliberately NOT listed: repos commit them (e.g.
# WordPress plugins ship vendor/) and payloads have been found there.
DISK_SKIP_DIRS = {"node_modules", ".git", ".next", ".nuxt", "coverage", ".venv",
"venv", "__pycache__", ".turbo", ".cache"}
SKIP_SUFFIX = (".min.js", ".map", "package-lock.json", "yarn.lock",
"pnpm-lock.yaml")
MAX_BYTES = 3_000_000
Expand Down Expand Up @@ -72,10 +76,10 @@ def norm(path):
return path.replace("\\", "/")


def should_scan(path):
def should_scan(path, skip_dirs=frozenset()):
p = norm(path)
parts = p.split("/")
if any(part in SKIP_DIRS for part in parts[:-1]) or p.endswith(SKIP_SUFFIX):
if any(part in skip_dirs for part in parts[:-1]) or p.endswith(SKIP_SUFFIX):
return False
return (os.path.splitext(p)[1].lower() in TEXT_EXT
or bool(CONFIG_RE.search(p)) or bool(HOOK_DIR_RE.search(p)))
Expand Down Expand Up @@ -210,11 +214,11 @@ def iter_tracked():

def iter_disk(root):
for d, dirs, files in os.walk(root):
dirs[:] = [x for x in dirs if x not in SKIP_DIRS]
dirs[:] = [x for x in dirs if x not in DISK_SKIP_DIRS]
for f in files:
full = os.path.join(d, f)
rel = norm(os.path.relpath(full, root))
if should_scan(rel):
if should_scan(rel, DISK_SKIP_DIRS):
try:
text = read_file(full)
except OSError:
Expand Down
Loading