Install
$ agentstack add skill-makash-agent-infra-security-npm-supply-chain-response ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
npm Supply Chain Attack Response
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:
- Interactive triage checklist — step-by-step walkthrough, one phase at a time, asking the user to run commands and report back before proceeding.
- Full incident response runbook — a complete markdown document covering all six phases that the user can save and share with their team.
- Shell script — a
check_npm_compromise.shscript 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.
Gathering context
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:
- Package name — the compromised package (e.g.,
axios) - Compromised versions — which versions contain the malicious payload (e.g.,
1.14.1,0.30.4)
Helpful but not required (use defaults or skip if the user doesn't know):
- Known safe version — the last clean version to pin to (e.g.,
1.14.0) - Attack window — UTC time range when the bad versions were available on npm
- Known IOCs — domains, filenames, persistence paths, process names. If the user doesn't have these, use the built-in IOC pattern library (see
references/ioc-patterns.md). - Payload behavior — what the malware does (credential theft, persistence, lateral movement). If unknown, assume credential theft as the baseline.
- Malicious dependency — some attacks inject the payload via a typosquatted dependency rather than the main package (e.g.,
plain-crypto-jsin the Axios attack). If known, collect this too.
The six phases
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.
Phase 1: Exposure check — "Am I even affected?"
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 2>/dev/null
yarn why 2>/dev/null
pnpm why 2>/dev/null
Search lockfiles for the compromised package or its malicious dependency:
grep -n "" package-lock.json 2>/dev/null
grep -n "" yarn.lock 2>/dev/null
grep -n "" pnpm-lock.yaml 2>/dev/null
grep -n "" 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.json" -type f 2>/dev/null
find / -path "*/node_modules/" -type d 2>/dev/null
npm ls -g --depth=0 2>/dev/null | grep ""
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 "" "$f" 2>/dev/null
done
Check npm cache (note: npm cache ls was removed in npm 7+):
grep -r '' ~/.npm/_cacache/index-v5/ 2>/dev/null
grep -r '' ~/.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 npm ls 2>/dev/null
Search for unpinned or loosely pinned references in package.json:
grep -n "" 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 "@" /path/to/ci/logs/ 2>/dev/null
Phase 2: Version confirmation — "Did I get the bad version?"
If Phase 1 found the package, confirm whether the installed version matches a compromised version.
cat node_modules//package.json | grep '"version"'
Check if the malicious dependency was pulled in:
ls node_modules// 2>/dev/null
cat node_modules//package.json 2>/dev/null | grep '"version"'
Check file timestamps to estimate when the package was installed:
stat node_modules//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-lock.json | grep -E '"version"|"resolved"|"integrity"'
After confirming the version, classify the finding into one of these five categories:
- Not present — package not found anywhere
- Present, safe version — installed but not a compromised version
- Present, likely affected — compromised version was installed
- Present, insufficient evidence — package found but version or install timing unclear
- Confirmed compromise — compromised version installed AND IOC indicators found
Phase 3: IOC hunting — "Did the malware execute?"
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//package.md 2>/dev/null
# Check if the postinstall script file still exists (payload self-deletes)
ls node_modules//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 ""
netstat -tn 2>/dev/null | grep -i ""
# Search logs for C2 communication
grep -rF "" /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 > "")'
Phase 4: Containment — "Stop the bleeding"
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-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/
npm cache clean --force
Pin to a known-safe version:
npm install @ --save-exact
For yarn/pnpm:
yarn add @ --exact
pnpm add @ --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.
Phase 5: Credential rotation — "Assume everything on that box is burned"
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/=.*/=/'
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.
Phase 6: Prevention — "Don't get burned again"
Pin exact versions — never use ^ or ~ for critical dependencies:
npm install @ --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
Output format guidance
Interactive triage checklist
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:
- Brief explanation of what this phase checks and why
- The commands to run (customized with the package name and versions from context)
- What to look for in the output
- A clear yes/no decision: "If you see X, proceed to Phase N. If not, you can stop here."
Full incident response runbook
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.
Shell script
Generate a bash script called check_npm_compromise.sh that:
- Takes the package name as an argument (or hardcodes it if the user specifies)
- Runs detection checks from Phases 1-3
- Color-codes output: green for clean, red for findings, yellow for warnings
- Prompts with
read -pbefore any destructive action (uninstall, cache purge, file deletion) - Generates a summary report at the end listing what was found and what actions were taken
- Includes a
--dry-runflag that skips all prompts and just reports
Read 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.
Incident report template
When producing the full incident response runbook or interactive checklist, include this template at the end so the user can document their findings.
Summary
- Incident:
- Package:
- Ecosystem: npm
- Known bad versions:
- Malicious dependency (if any):
- Attack window:
- Systems reviewed:
- Result:
Findings by system
- System name:
- Lockfile reference found:
- Installed version:
- Direct or transitive:
- Malicious dependency present:
- Indicator found:
- Risk level:
- Evidence:
Secret exposure
- Secrets likely present:
- Secrets rotated:
- Audit logs checked:
Actions taken
- Isolated:
- Rebuilt:
- Blocked versions:
- Monitoring added:
Unknowns
- Missing logs:
- Deleted environments:
- Confidence level:
Important notes
- Never tell the user they're "definitely safe" — supply chain attacks can have delayed or stealthy payloads. Use language like "no indicators found in the checks we ran" and suggest they monitor for advisories.
- The postinstall script vector is how most npm attacks deliver their payload. Unlike Python's
.pthfiles which fire on every interpreter startup, npm postinstall runs once duringnpm 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. - Anti-forensics is increasingly common. The Axios attack self-deletes its
setup.jsand swapspackage.jsonwith a cleanpackage.mdcopy. If you findpackage.mdin a suspicious dependency, the attack already ran. - Transitive dependency injection is the Axios attack pattern — the compromised
axiospackage addsplain-crypto-jsas a dependency, which carries the actual payload. Always check for unexpected new dependencies in your lockfile, not just version changes. - Credential rotation is non-negotiable if the compromised version was installed. The attacker had access to everything on that machine — npm tokens, cloud credentials, SSH keys, environment variables. Don't let the user skip this phase.
npm cache clean --forceis necessary because npm caches tarballs. Without purging, a subsequentnpm installcould reinstall the compromised version from cache even after removing it fromnode_modules.- Environment-gated payloads: some attacks only fire in CI environments by checking for
CI=trueorGITHUB_ACTIONS. Developers may not see IOCs on their local machines even if the payload ran in CI.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: makash
- Source: makash/agent-infra-security
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.