---
id: 54
title: "Quarantine freshly published dependencies"
kind: note
status: current
date: 2026-08-12
authors:
  - "Theo Zourzouvillys"
tags: [security, supply-chain, process, operations]
references:
  - id: eventstream
    title: "Details about the event-stream incident (npm blog, 2018)"
    url: https://blog.npmjs.org/post/180565383195/details-about-the-event-stream-incident
    abstract: "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."
  - id: xz
    title: "Backdoor in upstream xz/liblzma leading to ssh server compromise (oss-security, 2024)"
    url: https://www.openwall.com/lists/oss-security/2024/03/29/4
    abstract: "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."
  - id: pnpmage
    title: "pnpm settings: minimumReleaseAge"
    url: https://pnpm.io/settings#minimumreleaseage
    abstract: "A 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."
summary: "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."
supersedes: null
superseded_by: null
aliases: []
crossrefs:
  ZFN-31: "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."
  ZFN-2: "The priority ordering that decides this trade: a security control that costs you three days of feature freshness is not a close call."
  ZFN-50: "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."
---

## 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 something](/zfn/50-see-something-say-something/)). 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 — [`minimumReleaseAge`](ref:pnpmage) 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 incident](ref:eventstream) 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 [xz](ref:xz) — 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.

> [!aside]
>
> The bump-bot default is worth saying plainly: a tool that auto-merges dependency updates within
> minutes of publication is a machine for installing compromised releases at the moment of
> maximum danger, wired directly into your CI credentials. The same bot with a 72-hour holdback
> is genuinely useful. The delay is the entire difference.

## 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 [`minimumReleaseAge`](ref:pnpmage) to 72 hours: `pnpm add` and
  `pnpm install` will 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-2](/zfn/2-engineering-priority-ordering/)).

- **Shrink the surface while you're at it.** The floor reduces *when* you're exposed; owning more
  and depending on less ([ZFN-31](/zfn/31-own-your-components/)) 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-50](/zfn/50-see-something-say-something/)). 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 ([xz](ref:xz) 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-31](/zfn/31-own-your-components/) — fewer dependencies is the complementary control:
  surface reduction where this note is exposure-window reduction.
- [ZFN-2](/zfn/2-engineering-priority-ordering/) — security outranks the convenience of same-day
  upgrades; this note is that ordering applied to the resolver.
- [ZFN-50](/zfn/50-see-something-say-something/) — the reporting duty when the quarantine (or
  your own reading of a diff) turns something up.
- [event-stream](ref:eventstream) and [xz](ref:xz) — the two canonical shapes: the fast
  stolen-credential release and the slow maintainership capture.
- [pnpm `minimumReleaseAge`](ref:pnpmage) — the enforcement mechanism this site's own repository
  uses.

## Changelog

- **2026-08-12**: First published as a Field Note.
