Field Note 54current
Quarantine freshly published dependencies
The dangerous dependency isn't the old one with a CVE — it's the version published an hour ago by whoever phished the maintainer. Malicious releases are mostly caught within days. Pin everything, and refuse to install anything younger than 72 hours. Freshness is exposure.
TL;DR
Refuse to install any dependency version published less than ~72 hours ago. Modern supply-chain attacks don’t arrive as suspicious new packages you’d never adopt; they arrive as a patch release of something you already depend on, published with a real maintainer’s stolen credentials. Those releases are overwhelmingly detected and yanked within hours to days — by registry scanning, by researchers, by someone noticing their CI got weird (see something, say somethingField Note · currentZFN-50 — See something, say something — you owe a report, not a repairTwo standing duties beyond your assigned work: see something, say something, and leave things better than you found it. But the duty is to report, not to repair — route it to the owner and move on. Security issues always go to security, including ones you fixed yourself.Why it's cited here: What to do when the quarantine catches something real: report it upstream and to the registry — you owe the ecosystem the report, not just yourself the dodge.Open ZFN-50 →). Which means your exposure is almost entirely determined by how quickly you consume new releases. An automatic lockfile-bump bot that merges within the hour is optimised to sit inside the attack window.
So: pin exact versions with a lockfile, and put a release-age floor in front of every
install — minimumReleaseAgepnpm settings: minimumReleaseAgeA pnpm setting that refuses to resolve any package version published more recently than a configured number of minutes, with a per-package exclusion list. Turns 'wait before you upgrade' from a team habit into something the package manager enforces.pnpm.io ↗ in pnpm, or the equivalent policy in whatever
resolves your dependencies. Keep a short, dated exclusion list for the rare version you genuinely
cannot wait for, and treat every entry on it as a deliberate, named acceptance of risk.
Context
The mental model most teams carry is that dependency risk is about staleness: old versions accumulate CVEs, so the safest posture is to update as fast as possible. Tooling reinforces it — bots open the bump PR minutes after a release, and green CI merges it by lunch.
That model is a decade out of date. The attacks that actually land now are freshness attacks:
- A maintainer is phished, or a laptop token leaks, and a malicious patch version of a package with millions of weekly downloads goes out under a legitimate name. The event-stream incidentDetails about the event-stream incident (npm blog, 2018)A widely-depended-on npm package was handed to a new maintainer who published a version containing code targeting a specific bitcoin wallet. The malicious release sat on the registry for weeks — but was structurally the same attack seen many times since: the compromise arrives as a fresh version of something you already trust.blog.npmjs.org ↗ was an early, slow-motion version; the recent ones are industrialised — credential-stealing payloads pushed simultaneously into dozens of popular packages, harvested within hours.
- A contributor spends years earning maintainership and then ships the payload in a routine release, as with xzBackdoor in upstream xz/liblzma leading to ssh server compromise (oss-security, 2024)Andres Freund's disclosure of the xz backdoor: a multi-year social-engineering campaign to obtain maintainership, culminating in a backdoored release that major distributions shipped within weeks. Caught by one engineer investigating a 500ms ssh slowdown — before it reached most stable channels.openwall.com ↗ — caught, notably, before it reached most stable distribution channels, precisely because slower channels had a quarantine window built into their nature.
The asymmetry to notice: these releases have a short life. Registries scan, researchers watch diffs of popular packages, and compromised versions get reported and yanked typically within hours or a few days. The attack only works on whoever installed during the window. The window is the weapon, and your update latency decides whether you’re standing in it.
Meanwhile, the cost of consuming releases three days late is almost exactly zero. You were not blocked on a feature that shipped 40 hours ago. The one genuine exception — a security fix for something being actively exploited against you — is rare, loud, and deserves a human decision rather than a bot merge.
Recommendation
Pin exactly, and age-gate what you adopt.
-
A lockfile, always, everywhere. Every install — dev laptop, CI, production build — resolves from the committed lockfile, never from a floating range at install time. The lockfile pins what you have; the rest of this note governs what you adopt.
-
Enforce a release-age floor in the package manager, not in team habit. This repository’s own workspace sets pnpm’s
minimumReleaseAgepnpm settings: minimumReleaseAgeA pnpm setting that refuses to resolve any package version published more recently than a configured number of minutes, with a per-package exclusion list. Turns 'wait before you upgrade' from a team habit into something the package manager enforces.pnpm.io ↗ to 72 hours:pnpm addandpnpm installwill flatly refuse a version younger than that. Policy that lives in a document gets skipped under deadline pressure; policy that lives in the resolver doesn’t. If your ecosystem’s tooling has no such knob, put the check in CI — fail the build if any newly-added version is younger than the floor. -
72 hours is a good default; pick it consciously. Long enough that the loud, mass-harvesting attacks are almost always caught and yanked before you resolve them; short enough that you’re never meaningfully behind. Slower-moving or higher-blast-radius systems can hold a week.
-
Keep the exception path narrow, dated, and visible. A per-package exclusion with a comment saying why and when it expires — added for an actively-exploited CVE fix, removed once the window passes. If the exclusion list grows, that’s the control failing; review it like you’d review a firewall’s any-any rule.
-
Weigh the security-patch tension honestly, per case. The one argument against the floor is “you’re delaying security fixes by three days.” True — and almost always the right trade, because the median vulnerability has sat unexploited in your tree for months, while the compromised-release window is hot right now. When a fix genuinely can’t wait, that’s what the exclusion list is for: a human decision with a name on it (ZFN-2Field Note · currentZFN-2 — Engineering priority orderingWhen concerns conflict, prioritize security > correctness > availability > performance — and never trade a higher-ranked concern for a lower one. The rule binds the moment you must choose. Cite it instead of re-arguing it.Why it's cited here: The priority ordering that decides this trade: a security control that costs you three days of feature freshness is not a close call.Open ZFN-2 →).
-
Shrink the surface while you’re at it. The floor reduces when you’re exposed; owning more and depending on less (ZFN-31Field Note · currentZFN-31 — Own your components — when you deeply understand the domainOwning your own components rather than generic off-the-shelf services is often the better path as you grow: own what's core, lean on small vetted libraries for the hard parts. LLMs make it attainable at smaller scale — but only when you truly understand the domain, or it hurts.Why it's cited here: Owning more of your components is the other half of this defence: every dependency you don't have is a maintainer who can't be phished on your behalf.Open ZFN-31 →) reduces whether. Every transitive dependency is a maintainer account you now implicitly trust to not be phished. Look at the lockfile diff of a bump before merging it — for small packages that’s actually feasible, and it’s where several of these attacks have been caught.
-
If the quarantine catches something, report it — to the registry and upstream, not just your own team (ZFN-50Field Note · currentZFN-50 — See something, say something — you owe a report, not a repairTwo standing duties beyond your assigned work: see something, say something, and leave things better than you found it. But the duty is to report, not to repair — route it to the owner and move on. Security issues always go to security, including ones you fixed yourself.Why it's cited here: What to do when the quarantine catches something real: report it upstream and to the registry — you owe the ecosystem the report, not just yourself the dodge.Open ZFN-50 →). The whole defence rests on the ecosystem’s detection latency; you’re a sensor in it.
Consequences
Easier:
- You exit the blast radius of the dominant attack shape — mass credential-harvest via compromised releases — without reviewing a line of vendored code. The ecosystem’s detection works for you, because you’ve chosen to be behind it rather than ahead of it.
- Dependency updates become calm. Batched, aged, reviewable — instead of a stream of minutes-old bumps competing for reflexive approval.
- The policy is enforceable and auditable. “Do we wait on new releases?” has a checkable answer in the repo, not a cultural answer that varies by team and deadline.
Harder:
- You are genuinely three days behind, always. Almost always fine; occasionally — an actively-exploited vulnerability with a fresh fix — genuinely not, and then a human has to notice, decide, and use the exception path rather than being saved by the bot.
- It doesn’t cover everything. A patient attacker who lets a malicious release age past your floor beats it (xzBackdoor in upstream xz/liblzma leading to ssh server compromise (oss-security, 2024)Andres Freund's disclosure of the xz backdoor: a multi-year social-engineering campaign to obtain maintainership, culminating in a backdoored release that major distributions shipped within weeks. Caught by one engineer investigating a 500ms ssh slowdown — before it reached most stable channels.openwall.com ↗ nearly did, at distribution scale); so does a compromise of a version you already run. The floor removes the cheap, fast, mass-market attack — it is one layer, not the defence.
- Ecosystem friction. A colleague’s brand-new release, a hotfix from a vendor, your own just-published internal package — all get the same refusal, and each needs either patience or an exclusion entry. The friction is the feature, but you’ll have to keep re-explaining that.
References
- ZFN-31Field Note · currentZFN-31 — Own your components — when you deeply understand the domainOwning your own components rather than generic off-the-shelf services is often the better path as you grow: own what's core, lean on small vetted libraries for the hard parts. LLMs make it attainable at smaller scale — but only when you truly understand the domain, or it hurts.Why it's cited here: Owning more of your components is the other half of this defence: every dependency you don't have is a maintainer who can't be phished on your behalf.Open ZFN-31 → — fewer dependencies is the complementary control: surface reduction where this note is exposure-window reduction.
- ZFN-2Field Note · currentZFN-2 — Engineering priority orderingWhen concerns conflict, prioritize security > correctness > availability > performance — and never trade a higher-ranked concern for a lower one. The rule binds the moment you must choose. Cite it instead of re-arguing it.Why it's cited here: The priority ordering that decides this trade: a security control that costs you three days of feature freshness is not a close call.Open ZFN-2 → — security outranks the convenience of same-day upgrades; this note is that ordering applied to the resolver.
- ZFN-50Field Note · currentZFN-50 — See something, say something — you owe a report, not a repairTwo standing duties beyond your assigned work: see something, say something, and leave things better than you found it. But the duty is to report, not to repair — route it to the owner and move on. Security issues always go to security, including ones you fixed yourself.Why it's cited here: What to do when the quarantine catches something real: report it upstream and to the registry — you owe the ecosystem the report, not just yourself the dodge.Open ZFN-50 → — the reporting duty when the quarantine (or your own reading of a diff) turns something up.
- event-streamDetails about the event-stream incident (npm blog, 2018)A widely-depended-on npm package was handed to a new maintainer who published a version containing code targeting a specific bitcoin wallet. The malicious release sat on the registry for weeks — but was structurally the same attack seen many times since: the compromise arrives as a fresh version of something you already trust.blog.npmjs.org ↗ and xzBackdoor in upstream xz/liblzma leading to ssh server compromise (oss-security, 2024)Andres Freund's disclosure of the xz backdoor: a multi-year social-engineering campaign to obtain maintainership, culminating in a backdoored release that major distributions shipped within weeks. Caught by one engineer investigating a 500ms ssh slowdown — before it reached most stable channels.openwall.com ↗ — the two canonical shapes: the fast stolen-credential release and the slow maintainership capture.
- pnpm
minimumReleaseAgepnpm settings: minimumReleaseAgeA pnpm setting that refuses to resolve any package version published more recently than a configured number of minutes, with a per-package exclusion list. Turns 'wait before you upgrade' from a team habit into something the package manager enforces.pnpm.io ↗ — the enforcement mechanism this site’s own repository uses.
Changelog
- 2026-08-12: First published as a Field Note.