From agent-infra-security
Triages npm supply chain attacks and compromised packages with interactive checklists, full incident runbooks, or detection shell scripts.
npx claudepluginhub makash/agent-infra-security --plugin agent-infra-securityThis skill uses the workspace's default tool permissions.
Help developers triage, investigate, contain, and recover from a compromised npm package.
Generates interactive triage checklists, incident runbooks, or shell scripts to investigate and remediate PyPI supply chain attacks on compromised Python packages.
Intercepts pip, npm, go installs to audit package identity, vulnerabilities, suspicious signals, and enforce lockfile hash pinning before execution.
Hardens pnpm/Node.js projects against supply chain attacks by configuring minimum-release-age quarantine and frozen lockfile enforcement.
Share bugs, ideas, or general feedback.
Help developers triage, investigate, contain, and recover from a compromised npm package.
This skill produces one of three outputs depending on what the user asks for:
check_npm_compromise.sh script that automates detection, reports findings, and prompts before any remediation action.If the user doesn't specify which format, default to the interactive triage checklist. If the user says something like "just give me everything" or "runbook", produce the full markdown document. If they say "script" or "automate", generate the shell script.
Before producing any output, collect the following from the user. If they've already provided some of this in the conversation, don't re-ask.
Required:
axios)1.14.1, 0.30.4)Helpful but not required (use defaults or skip if the user doesn't know):
1.14.0)references/ioc-patterns.md).plain-crypto-js in the Axios attack). If known, collect this too.Every output format follows these six phases in order. The depth and format change based on the output type, but the sequence is always the same.
The goal is to determine whether the compromised package exists anywhere in the user's environments, including as a transitive dependency they never directly installed.
Commands to guide the user through:
Check if the package is installed and what version:
npm ls <PACKAGE> 2>/dev/null
yarn why <PACKAGE> 2>/dev/null
pnpm why <PACKAGE> 2>/dev/null
Search lockfiles for the compromised package or its malicious dependency:
grep -n "<PACKAGE>" package-lock.json 2>/dev/null
grep -n "<PACKAGE>" yarn.lock 2>/dev/null
grep -n "<PACKAGE>" pnpm-lock.yaml 2>/dev/null
grep -n "<MALICIOUS_DEP>" package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null
Hunt across ALL environments on the machine — developers often have multiple projects, global installs, and package manager caches:
find / -path "*/node_modules/<PACKAGE>/package.json" -type f 2>/dev/null
find / -path "*/node_modules/<MALICIOUS_DEP>" -type d 2>/dev/null
npm ls -g --depth=0 2>/dev/null | grep "<PACKAGE>"
Monorepo support: For workspaces, run checks from the workspace root AND individual packages. Lockfiles may exist at multiple levels.
find . -name "package-lock.json" -o -name "yarn.lock" -o -name "pnpm-lock.yaml" | while read f; do
echo "=== $f ===" && grep -n "<PACKAGE>" "$f" 2>/dev/null
done
Check npm cache (note: npm cache ls was removed in npm 7+):
grep -r '<PACKAGE>' ~/.npm/_cacache/index-v5/ 2>/dev/null
grep -r '<MALICIOUS_DEP>' ~/.npm/_cacache/index-v5/ 2>/dev/null
Check Docker images built during the attack window:
docker images --format "{{.Repository}}:{{.Tag}} {{.CreatedAt}}" | sort -k2
docker run --rm <IMAGE> npm ls <PACKAGE> 2>/dev/null
Search for unpinned or loosely pinned references in package.json:
grep -n "<PACKAGE>" package.json 2>/dev/null
Flag dangerous patterns: ^, ~, *, >=, or no version pin at all. Only exact versions ("1.14.0" without prefix) are safe.
Check CI/CD logs for the package version string during the attack window:
grep -r "<PACKAGE>@" /path/to/ci/logs/ 2>/dev/null
If Phase 1 found the package, confirm whether the installed version matches a compromised version.
cat node_modules/<PACKAGE>/package.json | grep '"version"'
Check if the malicious dependency was pulled in:
ls node_modules/<MALICIOUS_DEP>/ 2>/dev/null
cat node_modules/<MALICIOUS_DEP>/package.json 2>/dev/null | grep '"version"'
Check file timestamps to estimate when the package was installed:
stat node_modules/<PACKAGE>/package.json 2>/dev/null
For npm, check the lockfile resolved URLs and integrity hashes — these are more reliable than the version field if the attacker tampered with package.json post-install:
grep -A5 '"<PACKAGE>"' package-lock.json | grep -E '"version"|"resolved"|"integrity"'
After confirming the version, classify the finding into one of these five categories:
If the user confirmed they had a compromised version, look for evidence that the payload ran. Read references/ioc-patterns.md for the built-in pattern library. Combine those patterns with any attack-specific IOCs the user provides.
Postinstall script detection — scan ALL installed packages:
npm query ':attr(scripts, [postinstall])' 2>/dev/null | jq '.[].name'
If npm query is not available (older npm), use:
find node_modules -maxdepth 2 -name package.json \
-exec grep -l '"preinstall\|postinstall\|preuninstall"' {} \;
Anti-forensics detection:
Some attacks swap package.json with a clean copy after the postinstall runs to hide evidence. Check for:
# The Axios attack swaps package.md → package.json after execution
ls node_modules/<MALICIOUS_DEP>/package.md 2>/dev/null
# Check if the postinstall script file still exists (payload self-deletes)
ls node_modules/<MALICIOUS_DEP>/setup.js 2>/dev/null
If package.md exists alongside package.json, the package.json was likely swapped post-execution to remove the postinstall evidence. The original malicious package.json is gone.
Filesystem IOCs — check platform-specific persistence locations:
macOS:
ls -la /Library/Caches/com.apple.act.mond 2>/dev/null
find ~/Library/Caches -name "*.mond" -mtime -7 2>/dev/null
Linux:
ls -la /tmp/ld.py 2>/dev/null
find /tmp -name "*.py" -mtime -3 2>/dev/null
find ~/.config/systemd/user/ -name "*.service" -mtime -7 2>/dev/null
crontab -l 2>/dev/null
Windows:
dir %PROGRAMDATA%\wt.exe 2>nul
dir %TEMP%\*.vbs %TEMP%\*.ps1 2>nul
Network indicators:
# Check for connections to known C2 domains
ss -tnp 2>/dev/null | grep -i "<C2_DOMAIN>"
netstat -tn 2>/dev/null | grep -i "<C2_DOMAIN>"
# Search logs for C2 communication
grep -rF "<C2_DOMAIN>" /var/log/ 2>/dev/null
Process inspection:
# Look for background processes spawned by the payload
ps aux | grep -E "ld\.py|com\.apple\.act|wt\.exe" | grep -v grep
Credential access evidence:
find ~/.ssh ~/.aws ~/.config/gcloud ~/.kube ~/.npmrc ~/.docker -atime -1 2>/dev/null
stat ~/.ssh/id_rsa 2>/dev/null | grep Access
stat ~/.npmrc 2>/dev/null | grep Access
Kubernetes (if applicable):
kubectl get pods -n kube-system --sort-by=.metadata.creationTimestamp
kubectl get secrets --all-namespaces -o json | jq '.items[] | select(.metadata.creationTimestamp > "<ATTACK_DATE>")'
Before removing anything, preserve evidence if your organization may need forensic analysis.
# Preserve evidence before cleanup
npm ls --all > npm-ls-evidence-$(date +%Y%m%d-%H%M%S).txt 2>&1
cp package-lock.json package-lock-evidence-$(date +%Y%m%d-%H%M%S).json 2>/dev/null
cp -r node_modules/<MALICIOUS_DEP> malicious-dep-evidence-$(date +%Y%m%d-%H%M%S)/ 2>/dev/null
ss -tnp > network-connections-$(date +%Y%m%d-%H%M%S).txt 2>/dev/null
Remove the compromised package and malicious dependency, purge caches:
rm -rf node_modules/<MALICIOUS_DEP>
npm cache clean --force
Pin to a known-safe version:
npm install <PACKAGE>@<SAFE_VERSION> --save-exact
For yarn/pnpm:
yarn add <PACKAGE>@<SAFE_VERSION> --exact
pnpm add <PACKAGE>@<SAFE_VERSION> --save-exact
If persistence artifacts were found in Phase 3, remove them. In the interactive checklist, describe each artifact and ask the user to confirm before deleting. In the shell script, prompt with read -p.
For Docker: rebuild images from a clean base, pinning the safe version. Don't just docker exec into running containers.
Assume everything on the compromised system is burned. For systematic credential rotation and abuse detection, use the credential-exfiltration-response skill. Tell it which credential types were accessible — it will walk through rotation for each class and verify the old credentials are invalidated.
To scope what was accessible, check:
# npm registry tokens
cat ~/.npmrc 2>/dev/null | grep "_authToken"
# Find all .env files and list sensitive variable names
find . -name '.env*' -type f 2>/dev/null | while read f; do
echo "=== $f ===" && grep -iE '(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|API_KEY|AUTH)=' "$f" | sed 's/=.*/=<REDACTED>/'
done
# SSH keys
ls -la ~/.ssh/id_* 2>/dev/null
# Cloud credentials
ls ~/.aws/credentials ~/.config/gcloud/application_default_credentials.json ~/.azure/accessTokens.json ~/.kube/config 2>/dev/null
Hand this list to the credential-exfiltration-response skill for rotation and verification.
Pin exact versions — never use ^ or ~ for critical dependencies:
npm install <PACKAGE>@<SAFE_VERSION> --save-exact
Use npm ci in CI, not npm install:
npm ci strictly follows package-lock.json and fails if it's out of sync. npm install can update the lockfile silently.
Disable postinstall scripts in CI:
npm ci --ignore-scripts
# Then selectively rebuild only trusted native dependencies:
npm rebuild sharp esbuild
Or configure globally in .npmrc:
ignore-scripts=true
Run npm audit in CI:
npm audit --omit=dev
For deeper supply chain detection (typosquats, obfuscated code, suspicious install hooks), use Socket.dev:
npx socket scan create --repo . --branch main
Verify package provenance with npm audit signatures:
npm audit signatures
Packages published with npm publish --provenance from GitHub Actions get Sigstore-signed attestations linking the tarball to a specific commit.
Use Corepack to pin your package manager version:
corepack enable
corepack prepare npm@10.9.0 --activate
Prevents a compromised global npm binary from being used. Stable since Node 20+.
Lockfile integrity validation:
npx lockfile-lint --path package-lock.json --type npm --allowed-hosts npm
Scope secrets in CI/CD:
Instead of workflow-level environment variables, pass secrets only to the specific step that needs them. This limits blast radius if a dependency runs code during npm install.
Use npm provenance for your own packages:
# In GitHub Actions:
permissions:
id-token: write
contents: read
steps:
- run: npm publish --provenance
Walk the user through one phase at a time. After each phase, ask what they found before proceeding to the next. Adapt the remaining phases based on their answers. For example, if Phase 1 shows they're not exposed, stop and tell them they're clear — don't walk through IOC hunting.
Structure each phase as:
Produce a markdown document with all six phases, all commands pre-filled with the specific package name and versions, IOC domains, and persistence paths from the advisory. Include a summary header with the incident metadata (package, versions, attack window, IOC domains). This is meant to be shared with a team, so write it to be self-contained — someone reading it for the first time should understand what happened and what to do.
Save this as a .md file using the create_file tool.
Generate a bash script called check_npm_compromise.sh that:
read -p before any destructive action (uninstall, cache purge, file deletion)--dry-run flag that skips all prompts and just reportsRead scripts/check_npm_compromise.sh for the template. Customize it with the specific package details from the user's context.
Save this using the create_file tool and make it executable.
When producing the full incident response runbook or interactive checklist, include this template at the end so the user can document their findings.
.pth files which fire on every interpreter startup, npm postinstall runs once during npm install — but it runs with the full permissions of the installing user, which is often root in Docker builds or has access to all environment variables in CI.setup.js and swaps package.json with a clean package.md copy. If you find package.md in a suspicious dependency, the attack already ran.axios package adds plain-crypto-js as a dependency, which carries the actual payload. Always check for unexpected new dependencies in your lockfile, not just version changes.npm cache clean --force is necessary because npm caches tarballs. Without purging, a subsequent npm install could reinstall the compromised version from cache even after removing it from node_modules.CI=true or GITHUB_ACTIONS. Developers may not see IOCs on their local machines even if the payload ran in CI.