{"slug":"supply-chain-hardening","title":"supply-chain-hardening","summary":"Install-time cooldowns for npm/bun plus a sandboxed pre-install scan for bypasses. Use for supply-chain attacks or npm security.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-25T15:19:59.062852Z","repo":{"url":"https://github.com/jamditis/claude-skills-journalism","stars":399,"forks":64,"license":"MIT","updatedAt":"2026-09-18T21:05:05Z"},"bodyHtml":"<hr>\n<h2>name: supply-chain-hardening\ndescription: Install-time cooldowns for npm/bun plus a sandboxed pre-install scan for bypasses. Use for supply-chain attacks or npm security.</h2>\n<h1>Supply-chain hardening</h1>\n<p>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 <strong>Mini Shai-Hulud TanStack attack (2026-05-11)</strong> compromised 84 versions across 42 <code>@tanstack/*</code> packages and exfiltrated AWS / GCP / Vault / GitHub / SSH credentials via a postinstall script.</p>\n<p>The defense is <strong>layered</strong> and intentionally simple:</p>\n<ol>\n<li><strong>Install-time cooldown</strong>, 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.</li>\n<li><strong>Sandboxed pre-install scan</strong>, 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 <code>bwrap</code>/<code>firejail</code>/<code>unshare</code> so a malicious package can't escape the inspection.</li>\n<li><strong><code>--ignore-scripts</code> at install</strong>, postinstall is the #1 attack vector. Skip lifecycle scripts on every cooldown-bypass install.</li>\n</ol>\n<p>These three together would have blocked the Mini Shai-Hulud TanStack attack on a stock laptop with no human in the loop.</p>\n<h2>Configure the cooldown</h2>\n<p>Verified config keys (npm v11+ and bun 1.3+):</p>\n<table>\n<thead>\n<tr>\n<th>Manager</th>\n<th>File</th>\n<th>Key</th>\n<th>Units</th>\n<th>Exclusion key</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>npm</td>\n<td><code>~/.npmrc</code> (or project <code>.npmrc</code>)</td>\n<td><code>min-release-age</code></td>\n<td>days</td>\n<td>none yet, proposed in <a href=\"https://github.com/npm/cli/issues/8994\">npm/cli#8994</a></td>\n</tr>\n<tr>\n<td>bun</td>\n<td><code>~/.bunfig.toml</code> (or project <code>bunfig.toml</code>)</td>\n<td><code>[install] minimumReleaseAge</code></td>\n<td>seconds</td>\n<td><code>[install] minimumReleaseAgeExcludes = []</code> (exact names, no globs)</td>\n</tr>\n</tbody>\n</table>\n<p>Minimal config:</p>\n<pre><code># ~/.npmrc\nmin-release-age=7\n</code></pre>\n<pre><code># ~/.bunfig.toml\n[install]\nminimumReleaseAge = 604800  # 7 days\nminimumReleaseAgeExcludes = []\n</code></pre>\n<p><strong>Requires npm 11+.</strong> Older npm silently ignores unknown keys, so the config looks correct but does nothing. Check with <code>npm --version</code> and <code>npm config get min-release-age</code> (should echo <code>7</code>, not <code>null</code>).</p>\n<h2>Per-command bypass</h2>\n<p>When the cooldown blocks an install you actually want:</p>\n<pre><code>npm install &lt;pkg&gt;@&lt;version&gt; --min-release-age=0 --ignore-scripts\nbun add     &lt;pkg&gt;@&lt;version&gt; --minimum-release-age=0 --ignore-scripts\n</code></pre>\n<p>The <code>bun add --minimum-release-age=0</code> CLI flag works in 1.3+ even though the docs don't list it, it follows bun's <code>bunfig key → kebab-case flag</code> convention.</p>\n<p><strong>Always pair the bypass with <code>--ignore-scripts</code>.</strong> 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:</p>\n<pre><code>(cd node_modules/&lt;pkg&gt; &amp;&amp; cat package.json | jq .scripts) # eyeball it\n(cd node_modules/&lt;pkg&gt; &amp;&amp; npm run postinstall)            # run if it checks out\n</code></pre>\n<h2>When to scan before bypassing</h2>\n<p>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 (<code>scripts/hotpatch.example.sh</code>) implementing the heuristics. Adapt it to your machine, Bash assumes <code>bwrap</code> (Linux); macOS users substitute <code>sandbox-exec</code> or skip the sandbox layer with the trade-off documented.</p>\n<p>Static checks the scan should perform (each backed by a real attack):</p>\n<table>\n<thead>\n<tr>\n<th>Check</th>\n<th>Diagnostic of</th>\n<th>Severity</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>optionalDependencies</code> / <code>dependencies</code> containing <code>github:</code> or <code>git+</code> URLs</td>\n<td>Mini Shai-Hulud (delivered payload via <code>github:tanstack/router#&lt;sha&gt;</code> ref)</td>\n<td>RED</td>\n</tr>\n<tr>\n<td>Large JS file at package root not referenced by <code>main</code>/<code>module</code>/<code>exports</code>/<code>bin</code>/<code>files</code></td>\n<td>Planted payload pattern (<code>router_init.js</code> in Mini Shai-Hulud)</td>\n<td>RED</td>\n</tr>\n<tr>\n<td>Unpacked size &gt;3x the prior stable version</td>\n<td>Bulk payload smuggling</td>\n<td>RED</td>\n</tr>\n<tr>\n<td><code>fileCount</code> delta of 1–4 paired with &gt;2x size jump</td>\n<td>Single planted file</td>\n<td>RED</td>\n</tr>\n<tr>\n<td><code>preinstall</code>/<code>install</code>/<code>postinstall</code>/<code>prepare</code> scripts present</td>\n<td>Lifecycle-script attack vector (event-stream, ua-parser-js, etc.)</td>\n<td>YELLOW</td>\n</tr>\n<tr>\n<td>JS files referencing <code>.ssh/</code>, <code>.aws/</code>, <code>.npmrc</code>, <code>GITHUB_TOKEN</code>, <code>AWS_SECRET</code>, kube config</td>\n<td>Credential exfiltration</td>\n<td>YELLOW</td>\n</tr>\n<tr>\n<td>Version flagged <code>deprecated</code> in npm registry with \"security\"/\"compromised\"/\"malicious\" wording</td>\n<td>Maintainer/registry yank</td>\n<td>RED</td>\n</tr>\n<tr>\n<td>OSV.dev returns known vulnerabilities for <code>&lt;pkg&gt;@&lt;version&gt;</code></td>\n<td>Disclosed CVE</td>\n<td>RED (severity-dependent)</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Why prerelease versions are skipped from the size-delta baseline:</strong> dev/beta/rc versions have wildly different sizes than stable releases and produce false positives.</p>\n<h2>What the cooldown does <em>not</em> catch</h2>\n<p>Be honest about the limits with whoever you're configuring this for:</p>\n<ul>\n<li><strong>Old packages with new malicious versions still in the cooldown window are blocked</strong>, but if the bad version <em>also</em> passes the cooldown (rare but possible, a compromise that goes &gt;7 days undetected), the cooldown alone won't help. The scan catches most of those.</li>\n<li><strong>Transitive deps</strong>. A clean <code>&lt;pkg&gt;</code> you install can pull in a compromised transitive. Defenses: scan against the <em>resolved</em> tree (<code>npm audit</code>, <code>osv-scanner</code>), and keep the cooldown active globally so transitive resolution also waits.</li>\n<li><strong><code>npm ci</code></strong> against an existing lockfile. The cooldown applies during <em>resolution</em>, not installation of already-pinned versions. If your lockfile pins a compromised version, <code>npm ci</code> will install it. Mitigation: scan lockfiles in CI with <code>osv-scanner --lockfile=package-lock.json</code>.</li>\n<li><strong>Pre-existing compromised packages in <code>node_modules</code></strong>. Hardening protects future installs, not past ones. Audit existing deps separately (<code>npm audit</code>, <code>osv-scanner</code>, manual review of recently-published deps in your tree).</li>\n</ul>\n<h2>Quick-start workflow for a new machine</h2>\n<ol>\n<li>Verify npm &gt;= 11: <code>npm --version</code>. If older, upgrade (<code>sudo npm i -g npm@latest</code> or tarball-swap if self-upgrade races).</li>\n<li>Write <code>~/.npmrc</code> and <code>~/.bunfig.toml</code> with the config above.</li>\n<li>Verify: <code>npm config get min-release-age</code> returns <code>7</code>. <code>cat ~/.bunfig.toml</code> shows the <code>[install]</code> block.</li>\n<li>Copy <code>scripts/hotpatch.example.sh</code> to <code>~/.claude/hotpatch.sh</code> (or wherever fits). Make executable. Run <code>./hotpatch.sh --self-test</code> against the synthetic Mini Shai-Hulud fixture (also shipped) to confirm the heuristics fire.</li>\n<li>Document the bypass workflow somewhere your team will find it. The whole skill assumes the bypass is <em>rare</em> and <em>reviewed</em>, not the default.</li>\n</ol>\n<h2>Threat model: what this defends and what it doesn't</h2>\n<table>\n<thead>\n<tr>\n<th>Defends against</th>\n<th>Doesn't defend against</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Maintainer account compromise (npm token theft)</td>\n<td>Targeted attack tailored to wait through the cooldown</td>\n</tr>\n<tr>\n<td>CI/CD pipeline hijack (Mini Shai-Hulud, valid OIDC tokens, SLSA-attested malice)</td>\n<td>Compromise of a transitive dep already pinned in a lockfile</td>\n</tr>\n<tr>\n<td>Typosquatting (lookalike package names), when paired with <code>npm pkg fix</code> and lockfile review</td>\n<td>Malicious code in your own dev dependencies that you authored</td>\n</tr>\n<tr>\n<td>Postinstall payload execution (cooldown + <code>--ignore-scripts</code> = belt and suspenders)</td>\n<td>Runtime supply-chain attacks (e.g., dynamic loading of bad code from a CDN)</td>\n</tr>\n<tr>\n<td>Drive-by <code>npm install</code> of a brand-new transitive</td>\n<td>Compromise of the registry itself (very rare; out of scope)</td>\n</tr>\n</tbody>\n</table>\n<h2>Further reading</h2>\n<ul>\n<li>Original Mini Shai-Hulud StepSecurity analysis (TanStack attack, 2026-05-11): <a href=\"https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem\">https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem</a></li>\n<li>npm <code>min-release-age</code> config: <a href=\"https://docs.npmjs.com/cli/v11/using-npm/config\">https://docs.npmjs.com/cli/v11/using-npm/config</a></li>\n<li>bun <code>minimumReleaseAge</code> config: <a href=\"https://bun.com/docs/runtime/bunfig\">https://bun.com/docs/runtime/bunfig</a></li>\n<li>OSV.dev API (free, unauth): <a href=\"https://osv.dev/docs/#tag/api\">https://osv.dev/docs/#tag/api</a></li>\n<li>Earlier Shai-Hulud worm context: <a href=\"https://github.com/advisories?query=shai-hulud\">https://github.com/advisories?query=shai-hulud</a></li>\n</ul>\n","files":[{"path":"agents/openai.yaml","sizeBytes":151,"isText":true},{"path":"SKILL.md","sizeBytes":8068,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"notes-only","suspicious":0,"notes":12,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-25T15:21:05.469704Z","sha256":"3EB345B9D3D72D40BA8C2DCCFAA46E69FC3FF0012F868E8AFEF094F4C51260CA","sizeBytes":4036},"review":null,"source":{"repositoryUrl":"https://github.com/jamditis/claude-skills-journalism","path":"security-toolkit/skills/supply-chain-hardening","license":"MIT","commit":"7aca204924ed7fbcd5d1a37232558f2b052c0252","subtreeSha":"70A3DE530B6D66E141AE8201A241E83DE71F4DD96C70603277F9D8735585A8F8","lastSyncedAt":"2026-09-23T13:51:04.922569Z"},"reviewedAt":"2026-08-25T15:22:48.978469Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/jamditis/claude-skills-journalism/tree/master/security-toolkit/skills/supply-chain-hardening"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jamditis-claude-skills-journalism@llmmart"},{"target":"git","command":"git clone https://github.com/jamditis/claude-skills-journalism.git"}]}