skip to content

Move the gate to the package manager


When a supply chain attack lands on the npm registry and your timeline starts asking what to do, the response that arrives first is always behavioural. Freeze installs for a week. Audit your lockfiles. Rotate your tokens. Pin your floating action tags. All of it is correct, and all of it is the wrong layer to be writing the rule at.

The shape of the May 2026 Mini Shai-Hulud wave makes this concrete. The worm published 84 malicious package versions across the TanStack namespace in a six-minute window on 11 May, using OIDC tokens stolen from a hijacked GitHub Actions release pipeline. Sigstore provenance was valid, SLSA level 3, every signature checked. The window between malicious publish and community detection was measured in minutes. Whatever your install discipline looks like, if you ran pnpm install against @tanstack/router during that six-minute window with version resolution turned on, you got the malicious version with a clean provenance signature attached. Discipline doesn’t help because the discipline has to fire faster than the attack, and the attack window is shorter than your attention span.

The durable fix is one layer down. pnpm shipped a setting called minimumReleaseAge in version 10.16, on by default at twenty-four hours in version 11. It refuses to install any package version younger than the configured threshold, no matter what your lockfile asks for or what semver range you wrote. Set it to seven days and the attacker’s six-minute window stops being a window — anything published inside the last week gets rejected at install time, by the package manager, before your hooks run or your scripts execute. The behavioural rule “don’t install for a week after a wave” becomes the configuration minimumReleaseAge=10080, and the configuration runs every day regardless of what you remember to remember.

This is the move that’s worth generalising. When defending against a fast-moving attack, look for the gate one layer down from where the attack lives. The attack lives in the publish event. The gate is at the package manager, not at human attention. The instinct to add the rule to your head (“I will not install for seven days”) is the instinct to put the load-bearing control on the most failure-prone link in the chain, which is your own discipline at 11pm when you actually do need to add a package. The instinct to add the rule to the tool (“pnpm refuses anything younger than seven days”) puts the control on the link that doesn’t get tired.

The pattern repeats whenever an attack class becomes commoditised. Tag-pinning floating Actions references? Move the gate from “I remember to pin SHAs” to a renovate bot that pins them automatically and a CODEOWNERS rule that flags any workflow edit that introduces a floating tag. Hook injection into ~/.claude/settings.json? Move the gate from “I notice the diff” to a probe that fails an integrity check the moment a non-allowlisted command appears. The behavioural version of the rule is always available as a fallback. The configuration version is the version that doesn’t decay.

There’s a second-order point about what to read for in a panic post on the timeline. The post that triggered this audit closed with three behavioural recommendations — freeze installs, run a self-check script, rotate every credential. Two were correct. One was a non-existent CLI. The signal in a panic post isn’t the recommendations; it’s the attack vector description, which tells you where the gate one layer down actually lives. The recommendations are the loud part. The vector is the quiet part. Read for the vector.