When to use
- Hardening a new dev machine, CI runner, or build server against npm/bun supply-chain attacks
- Reacting to a fresh ecosystem incident (Shai-Hulud-class worm, maintainer-account compromise, CI pipeline hijack)
- Asked about "minimum release age," "install cooldown," or how to safely install a just-published package
- Need to bypass the cooldown for a CVE patch and want a sanity scan before letting the tarball run
The attack this stops
Mini Shai-Hulud TanStack attack, 2026-05-11. 84 versions across 42 @tanstack/* packages compromised via OIDC token theft from an orphan-commit workflow run. The payload exfiltrated AWS, GCP, Vault, GitHub, and SSH credentials via a prepare script on a github:-resolved optional dependency.
A 7-day install-time cooldown plus --ignore-scripts on every cooldown bypass would have blocked the entire attack on a stock laptop with no human in the loop. This skill makes that posture the default.
Three layers of defense
Install-time cooldown
Only install versions older than N days (default 7). By the time the cooldown expires, the security community has almost always flagged a compromised version and the registry has yanked it. This is the primary defense.
Sandboxed pre-install scan
When the cooldown has to be bypassed (CVE patch, fresh dep, urgent install), run the candidate tarball through a static-analysis scan inside bwrap/firejail/unshare with no network and no access to ~/.ssh, ~/.aws, ~/.npmrc, or your pass-store.
--ignore-scripts on every bypass
Postinstall is the #1 attack vector. Skip lifecycle scripts on every cooldown-bypass install. Native modules that legitimately need postinstall can have the script run manually after a human-readable review.
Configure the cooldown
Verified config keys (npm v11+ and bun 1.3+):
| Manager | File | Key | Units |
|---|---|---|---|
| npm | ~/.npmrc |
min-release-age |
days |
| bun | ~/.bunfig.toml |
[install] minimumReleaseAge |
seconds |
# ~/.npmrc
min-release-age=7
# ~/.bunfig.toml
[install]
minimumReleaseAge = 604800 # 7 days
minimumReleaseAgeExcludes = []
Requires npm 11+. Older npm silently ignores unknown keys, so the config looks correct but does nothing. Verify with npm config get min-release-age — should echo 7, not null.
Per-command bypass
When the cooldown blocks an install you actually want:
npm install <pkg>@<version> --min-release-age=0 --ignore-scripts
bun add <pkg>@<version> --minimum-release-age=0 --ignore-scripts
The bun add --minimum-release-age=0 CLI flag works in 1.3+ even though the docs don't list it — it follows bun's bunfig key → kebab-case flag convention.
What the scan checks for
Each check is backed by a real attack signature:
| Check | Diagnostic of | Severity |
|---|---|---|
optionalDependencies or dependencies containing github:/git+ URLs |
Mini Shai-Hulud-style payload delivery | Red |
Large JS file at package root not referenced by main/module/exports/bin/files |
Planted payload (router_init.js) |
Red |
| Unpacked size >3x prior stable version | Bulk payload smuggling | Red |
fileCount delta of 1–4 with >2x size jump |
Single planted file | Red |
preinstall/install/postinstall/prepare scripts present |
Lifecycle-script attack vector | Yellow |
JS referencing .ssh/, .aws/, .npmrc, GITHUB_TOKEN, kube config |
Credential exfiltration | Yellow |
Version flagged deprecated with "security"/"compromised"/"malicious" wording |
Maintainer/registry yank | Red |
| OSV.dev returns known vulnerabilities for the candidate version | Disclosed CVE | Red |
The /security-toolkit:hotpatch command
Bundled with this skill in the security-toolkit plugin: a slash command that wraps the scan workflow.
# Scan a registry tarball before installing
/security-toolkit:hotpatch lodash@4.17.21
# Scan a local .tgz
/security-toolkit:hotpatch ./suspicious.tgz
The command tries the local hotpatch.example.sh reference implementation first and falls back to an in-conversation scan when no script is present. A bundled synthetic Mini Shai-Hulud fixture (test-fixtures/fake-mini-shai.tgz) lets you self-test the heuristics — --self-test against it should report 2 red, 1 yellow, SELF-TEST PASSED.
Threat model
Defends against
- Maintainer account compromise (npm token theft)
- CI/CD pipeline hijack (Mini Shai-Hulud, valid OIDC tokens)
- Typosquatting when paired with lockfile review
- Postinstall payload execution
- Drive-by
npm installof a brand-new transitive
Doesn't defend against
- Targeted attack tailored to wait out the cooldown
- Compromise of a transitive already pinned in a lockfile
- Malicious code in dev dependencies you authored
- Runtime supply-chain attacks (CDN-loaded code)
- Compromise of the registry itself (rare; out of scope)
Resources
- StepSecurity: Mini Shai-Hulud TanStack analysis — original incident write-up
- npm config docs (v11) —
min-release-agereference - bun bunfig docs —
minimumReleaseAgereference - OSV.dev API — free, unauthenticated vulnerability lookup
- GitHub Advisory database — earlier Shai-Hulud worm context
Installation
# Recommended: install the security-toolkit plugin
/plugin marketplace add jamditis/claude-skills-journalism
/plugin install security-toolkit@claude-skills-journalism
# Or copy just this skill from the plugin tree
git clone https://github.com/jamditis/claude-skills-journalism.git
cp -r claude-skills-journalism/security-toolkit/skills/supply-chain-hardening ~/.claude/skills/
Or browse this skill in the GitHub repository.
Related skills
Layered defense, no human in the loop
Cooldown plus sandbox plus --ignore-scripts would have blocked Mini Shai-Hulud on a stock laptop.