$npx -y skills add jamditis/claude-skills-journalism --skill supply-chain-hardeningConfigure install-time cooldowns for npm/bun (minimum release age) and run a sandboxed pre-install scan when the cooldown has to be bypassed. Use when the user asks about supply-chain attacks, npm/bun security, "minimum release age", a "cooldown" for installs, hardening against S
| 1 | # Supply-chain hardening |
| 2 | |
| 3 | Defends a journalism toolchain against the dominant npm/bun supply-chain attack pattern: a maintainer account or CI pipeline is compromised, a malicious version ships, and machines install it before anyone notices. Recent example: the **Mini Shai-Hulud TanStack attack (2026-05-11)** compromised 84 versions across 42 `@tanstack/*` packages and exfiltrated AWS / GCP / Vault / GitHub / SSH credentials via a postinstall script. |
| 4 | |
| 5 | The defense is **layered** and intentionally simple: |
| 6 | |
| 7 | 1. **Install-time cooldown** — only install package versions older than N days (default 7). This is the primary defense. By the time the cooldown expires, the security community has almost always flagged a compromised version and the registry has yanked it. |
| 8 | 2. **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 that looks for the diagnostic signatures of supply-chain malware. The scan runs inside `bwrap`/`firejail`/`unshare` so a malicious package can't escape the inspection. |
| 9 | 3. **`--ignore-scripts` at install** — postinstall is the #1 attack vector. Skip lifecycle scripts on every cooldown-bypass install. |
| 10 | |
| 11 | These three together would have blocked the Mini Shai-Hulud TanStack attack on a stock laptop with no human in the loop. |
| 12 | |
| 13 | ## Configure the cooldown |
| 14 | |
| 15 | Verified config keys (npm v11+ and bun 1.3+): |
| 16 | |
| 17 | | Manager | File | Key | Units | Exclusion key | |
| 18 | |---|---|---|---|---| |
| 19 | | npm | `~/.npmrc` (or project `.npmrc`) | `min-release-age` | days | none yet — proposed in [npm/cli#8994](https://github.com/npm/cli/issues/8994) | |
| 20 | | bun | `~/.bunfig.toml` (or project `bunfig.toml`) | `[install] minimumReleaseAge` | seconds | `[install] minimumReleaseAgeExcludes = []` (exact names, no globs) | |
| 21 | |
| 22 | Minimal config: |
| 23 | |
| 24 | ```ini |
| 25 | # ~/.npmrc |
| 26 | min-release-age=7 |
| 27 | ``` |
| 28 | |
| 29 | ```toml |
| 30 | # ~/.bunfig.toml |
| 31 | [install] |
| 32 | minimumReleaseAge = 604800 # 7 days |
| 33 | minimumReleaseAgeExcludes = [] |
| 34 | ``` |
| 35 | |
| 36 | **Requires npm 11+.** Older npm silently ignores unknown keys, so the config looks correct but does nothing. Check with `npm --version` and `npm config get min-release-age` (should echo `7`, not `null`). |
| 37 | |
| 38 | ## Per-command bypass |
| 39 | |
| 40 | When the cooldown blocks an install you actually want: |
| 41 | |
| 42 | ```bash |
| 43 | npm install <pkg>@<version> --min-release-age=0 --ignore-scripts |
| 44 | bun add <pkg>@<version> --minimum-release-age=0 --ignore-scripts |
| 45 | ``` |
| 46 | |
| 47 | 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. |
| 48 | |
| 49 | **Always pair the bypass with `--ignore-scripts`.** Postinstall is the most common payload-execution path in supply-chain malware (Mini Shai-Hulud, event-stream, ua-parser-js, coa, all used it). Native modules that legitimately need postinstall can have the script run manually after a human-readable review: |
| 50 | |
| 51 | ```bash |
| 52 | (cd node_modules/<pkg> && cat package.json | jq .scripts) # eyeball it |
| 53 | (cd node_modules/<pkg> && npm run postinstall) # run if it checks out |
| 54 | ``` |
| 55 | |
| 56 | ## When to scan before bypassing |
| 57 | |
| 58 | The scan is for the dangerous moment: you've decided to bypass the cooldown and need a sanity check. The skill ships a reference script (`scripts/hotpatch.example.sh`) implementing the heuristics. Adapt it to your machine — Bash assumes `bwrap` (Linux); macOS users substitute `sandbox-exec` or skip the sandbox layer with the trade-off documented. |
| 59 | |
| 60 | Static checks the scan should perform (each backed by a real attack): |
| 61 | |
| 62 | | Check | Diagnostic of | Severity | |
| 63 | |---|---|---| |
| 64 | | `optionalDependencies` / `dependencies` containing `github:` or `git+` URLs | Mini Shai-Hulud (delivered payload via `github:tanstack/router#<sha>` ref) | RED | |
| 65 | | Large JS file at package root not referenced by `main`/`module`/`exports`/`bin`/`files` | Planted payload pattern (`router_init.js` in Mini Shai-Hulud) | RED | |
| 66 | | Unpacked size >3x the prior stable version | Bulk payload smuggling | RED | |
| 67 | | `fileCount` delta of 1–4 paired with >2x size jump | Single planted file | RED | |
| 68 | | `preinstall`/`install`/`postinstall`/`prepare` scripts present | Lifecycle-script attack vector (event-stream, ua-parser-js, etc.) | YELLOW | |
| 69 | | JS files referencing `.ssh/`, `.aws/`, `.npmrc`, `GITHUB_TOKEN`, `AWS_SECRET`, kube config | Credential exfiltration | YELLOW | |
| 70 | | Version flagged `deprecated` in npm registry with "security"/"compromised"/"malicious" wording | Maintainer/registry yank | RED | |
| 71 | | OSV.dev returns known vulnerabilities for `<pkg>@<version>` | D |