name: detect-project-malware
description: Scans a whole project (JS/TS, Python, shell, Ruby/Go/PHP, Dockerfiles, YAML, package.json, and JS/TS tooling configs) for supply-chain and infostealer patterns — obfuscated JavaScript, eval(atob()) / new Function() loaders, createRequire ESM escapes, browser & SSH & cloud credential theft (Chrome Safe Storage, Login Data, AWS keys, GitHub tokens, crypto wallets, PEM blocks), reverse shells and curl | sh droppers, persistence via .bashrc / authorized_keys / crontab / LaunchAgents, network exfiltration channels (Telegram bots, Discord/Slack webhooks, ngrok/transfer.sh), crypto miners, and suspicious package.json install hooks. Use when the user mentions suspicious config changes, unexplained build hangs, keychain/password prompts during npm run dev / npm run build, recent commits from unknown contributors, incident response, supply-chain attack, malware, infostealer, or when auditing a repo after a git pull.
Detect Project Malware
Detects obfuscated or malicious code injected across a project — in build/tooling
configs, application source, helper scripts, install hooks, and CI files. Built
after a real incident in this workspace where a contributor injected an
infostealer payload into postcss.config.js, got it reverted, and re-injected
it weeks later behind an unrelated feature commit.
When to run
Run this skill when any of the following happen:
npm run devornpm run buildhangs forever and only terminates with Ctrl+C/Ctrl+Z.- macOS shows a keychain prompt mentioning "Chrome Safe Storage",
security, or "wants to use your confidential information" during dev/build. - After
git pull, new commits modify config files with messages that don't match the diff. - The user mentions suspicious contributors, supply-chain attack, infostealer, or malware.
- Before committing, as a quick guardrail on recently-changed config files.
Workflow
Follow this checklist:
Task Progress:
- [ ] Step 1: Run scan.sh across the whole project
- [ ] Step 2: Triage hits by category (see PATTERNS.md)
- [ ] Step 3: For each real hit, identify the injection commit
- [ ] Step 4: Restore the last-clean version of each compromised file
- [ ] Step 5: Incident response (rotate secrets, revoke access, harden repo)
Step 1: Scan the repo
From the repo root:
bash ~/.cursor/skills/detect-project-malware/scripts/scan.sh
Output is grouped per file, listing every category matched and the specific
pattern names that fired. Exit code is 1 on any hit, 0 otherwise, so the
script is suitable for pre-commit or CI.
Step 2: Triage hits
Open the flagged files with the Read tool. Use PATTERNS.md to
understand what each category means and to gauge severity and false-positive
shape for your repo.
Strong indicators (treat as compromised immediately):
| Signal | Why |
|---|---|
CONFIG_ANOMALY: oversized-line-in-config | Real configs are tiny; a giant trailing line is the signature hiding spot. |
JS_OBFUSCATION combined with JS_ESM_ESCAPE or JS_NATIVE_EXEC in any source file | End-to-end loader: decode → recover require → spawn. |
Any CRED_BROWSER, CRED_PRIVATE_KEYS, MINING, SHELL_REVERSE_SHELL, or CRED_DISCORD hit in product code | These have no legitimate reason to appear in an application repo. |
SUPPLY_CHAIN install hook that calls curl/wget/base64/child_process/pipe-to-shell | Always inspect the referenced script manually. |
SHELL_REMOTE_EXEC: curl-pipe-shell in CI / install scripts | Arbitrary remote code on every developer or pipeline run. |
Do not try to "understand" the payload, run it, or paste it into any sandbox that executes JavaScript/Python — static inspection only.
Step 4: Git history check
For every malicious file, run the injection finder — it reads every historical version of the file, classifies each commit as CLEAN / INFECTED / INJECTED / CLEANED, and prints the exact command to restore the last known-clean version:
bash ~/.cursor/skills/detect-project-malware/scripts/find-injection.sh \
<path-to-file> --root <repo_root>
Example output (real incident in lovable-kudos-hub):
STATE HASH DATE AUTHOR SUBJECT
CLEAN 86d29d0695 2025-06-30 18:48:17 gpt-engineer-app Use tech stack vite_react_shadcn_ts
INJECTED 1692ea4ba2 2026-01-24 00:45:46 ShahmeerAli1504 feat: Enhance CompetencyDefinitionEditor ...
CLEANED 7968a7bece 2026-01-28 13:17:03 Reynier security: malware cleaned
INJECTED 519db25441 2026-03-14 00:23:00 Shahmeer0304 feat: Add delete period functionality ...
Two takeaways from that run: the injection hides behind unrelated feature-commit subjects, and a single "cleanup" commit is not enough — check whether any later commit re-introduced the payload.
Also list every commit by the suspected author to look for other tampered files:
git log --all --author='<name-or-email>' --name-only \
--pretty=format:'%n==> %h %an <%ae> %s'
Step 5: Clean and report
When a hit is confirmed:
- Replace the file with its last known-clean version (e.g. a previous commit hash, a fresh copy from docs, or a minimal known-good template). Do not try to "surgically remove" only the injected block — future diffs are cleaner if you rewrite the file.
- Run the scanner again to confirm 0 hits.
- Produce a report using the template below.
- Remind the user of the incident response follow-ups:
- Assume anything the dev machine had access to is potentially exposed: rotate
tokens in
.env,.env.*, Supabase service role keys, GitHub PATs, SSH keys, cloud CLI credentials, and browser-stored passwords/cookies. - If a macOS keychain prompt for Chrome Safe Storage appeared and was approved, treat all Chrome-stored credentials as compromised.
- Remove write access from the suspected contributor on the remote; revert or rewrite their malicious commits; enable branch protection + required review.
- Run
npm ciin a clean clone to rule out a compromisednode_modules(the config file injection is usually the delivery mechanism, but checkpackage.jsonfor newly addedpostinstall/preinstall/preparescripts andpackage-lock.jsonfor unexpected dependencies).
- Assume anything the dev machine had access to is potentially exposed: rotate
tokens in
Report template
# Config malware scan — <repo name> — <YYYY-MM-DD>
## Files flagged
- `path/to/file.js` — matched: `<pattern names>`
## Evidence
- Line <n>: <short quoted snippet, < 120 chars>
- Suspicious import: `<import line>`
## Git attribution
- Injected in `<hash>` by `<author> <email>` on `<date>`
- Commit message (as written): `<message>`
- Prior clean commit: `<hash>`
## Actions taken
- Restored `path/to/file.js` to clean version.
- Scanner re-run: 0 hits.
## Follow-up required
- [ ] Rotate secrets: <list>
- [ ] Revoke contributor access: <user>
- [ ] Rewrite/revert commits: <hashes>
- [ ] User changes OS login password if Keychain was unlocked
- [ ] Run `npm ci` in a fresh clone and re-scan
Anti-patterns while handling a hit
- ❌ Don't run the suspicious config with
node,vite,tsx, or any executor to "see what it does". - ❌ Don't paste the payload into online deobfuscators that execute JS.
- ❌ Don't
git revertwithout checking whether later commits re-introduced the payload (this repo's previous incident: asecurity: malware cleanedcommit was followed by a re-injection hiding behind an unrelated feature commit). - ❌ Don't assume a clean working tree means safety — always scan the committed
content, not just
git status.
Utility scripts
All scripts only read files; they never execute the suspect code.
scripts/scan.sh — ripgrep-based project-wide scanner. Covers JS/TS/JSX/TSX,
Python, shell (bash/zsh/fish), Ruby/Go/PHP/Perl/PowerShell, Dockerfiles,
Makefiles, YAML, package.json, and all JS/TS tooling configs. Excludes
node_modules, dist, build, .git, cache directories, minified files, and
lockfiles.
bash ~/.cursor/skills/detect-project-malware/scripts/scan.sh [repo_root]
scripts/patterns.txt — rule catalog consumed by scan.sh. Format per line:
CATEGORY|name|regex. Blank lines and # comments are skipped. Edit this file
to add, tune, or remove detections without touching the scanner itself; make
sure to update PATTERNS.md at the same time.
scripts/find-injection.sh — walks the full git history of a single file
(including renames), classifies each commit as CLEAN / INFECTED / INJECTED /
CLEANED, and prints the git show command to restore the last clean version.
bash ~/.cursor/skills/detect-project-malware/scripts/find-injection.sh \
<path-to-file> [--root <repo_root>]
Use scan.sh to find which files are compromised, then find-injection.sh
on each hit to attribute the injection and locate the clean baseline.
Additional resources
- PATTERNS.md — full category catalog with severity and false-positive guidance.