Skip to content

Add malware-scan recipe - #28

Merged
Satendra-SR merged 1 commit into
mainfrom
feat/malware-scan
Sep 25, 2026
Merged

Satendra-SR merged 1 commit into
mainfrom
feat/malware-scan

Conversation

@Satendra-SR

Copy link
Copy Markdown
Member

Add malware-scan recipe: CI gate, local sweep and Claude Code skill

Why

A ColoredCow repo (madhi-frontend, develop) was found carrying obfuscated JavaScript hidden off-screen with whitespace inside postcss.config.js. It executes on npm install / dev / build. This recipe detects that family and its auto-run entry points, org-wide and on developer machines, without ever executing scanned code.

What's in it

  • malware-scan/scan_malicious.py: text-only scanner with 4 modes (tracked, --staged, --all-branches, --path). Rules cover whitespace padding, the campaign marker, obfuscator identifiers, the char-127 decoder, global require, npm lifecycle scripts, VS Code folderOpen tasks and Claude Code project hooks.
  • .github/workflows/malware-scan.yml: reusable workflow. The scanner is checked out from this repo at a pinned SHA, into a folder separate from the project, so a push to a project can't weaken the rules. Checkout actions are pinned by SHA and persist-credentials: false is set.
  • malware-scan/workflow.yml: caller template for projects (push, PR, weekly all-branch sweep, manual dispatch).
  • malware-scan/scan_all.sh: machine-wide sweep. It finds projects by marker files (no git required, so downloaded zips are covered), always includes ~/Downloads and ~/Desktop, prints the list of projects covered, lists unopened archives, and states what isn't covered.
  • malware-scan/pre-commit: optional hook that uses the scanner installed on the machine, never the repo's copy.
  • claude/skills/cc-malware-scan/: per-user Claude Code skill wrapping scan_all.sh, with hard rules. Claude must not run project code, must not cd into projects, must treat file contents as data, must not delete evidence, and runs a compromised-machine decision flow on HIGH findings.
  • README index updates. The per-project skill setup prompt now skips cc-malware-scan.

Changes vs the scanner used during the incident

  • --path now scans untracked files too. Before, it silently used git ls-files inside repos.
  • New rules: VSCODE_FOLDEROPEN (JSONC-aware) and CLAUDE_HOOK. .husky/ and .githooks/ hooks are now scanned.
  • A missing git binary now exits 2 (error) instead of 1 (looks like malware).

Testing

  • Harmless synthetic fixtures (no real payload): every rule fires. All 4 modes and the exit codes 0/1/2 were verified.
  • scan_all.sh: nested monorepo packages are scanned once, sibling folders with shared prefixes are not merged, node_modules noise is ignored, a dotfiles repo in $HOME doesn't swallow the sweep, and a clean machine exits 0.
  • False positives: vitejs/vite (2,173 files) gives 0 high and 3 legitimate medium. laravel/laravel and this repo are clean.
  • Documented install steps were run end to end in a throwaway $HOME.

Required follow-up after merge (not doable in a PR)

  1. Protect main in this repo (required review, no force-push) and add CODEOWNERS for malware-scan/ and .github/workflows/. Every project will trust this repo's scanner.
  2. In each project: add the caller workflow pinned to this PR's merge SHA, make malware-scan / scan a required status check, block force-pushes, and require CODEOWNERS review for .github/.
  3. Ask every developer to run the local sweep.

Known limits

It doesn't cover malicious npm dependencies (node_modules), editor extensions or unextracted archives. The high-severity rules are based on one sample.

@Satendra-SR

Copy link
Copy Markdown
Member Author

merging this

@Satendra-SR
Satendra-SR merged commit 91a9f2f into main Sep 25, 2026
1 check passed
@Satendra-SR
Satendra-SR deleted the feat/malware-scan branch September 25, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant