Package Cooldowns Compared: GitHub Package Firewall vs. What npm, pnpm, Yarn, Bun, pip, uv and Poetry Already Do

GitHub now offers an install-time firewall for npm, free in preview and metered later. Most package managers already ship a free cooldown. Here is what each tool does, the copy-paste config, and where cooldowns stop helping.

Roller conveyor lines carrying parcels through a large automated warehouse
Photo by Alberto Rodríguez on Unsplash

On October 20, GitHub put a new product into public preview: GitHub package firewall. It sits between your machines and the npm registry and refuses to hand over package versions that break a policy you set. Coding agents and CI runners are the obvious target, since they install whatever the lockfile or the model asks for, with nobody watching.

The headline feature is a cooldown: do not install anything published in the last N days. That idea is not new. Most package managers have shipped it since late 2025, for free. So I went through the vendor docs and changelogs for GitHub, npm, pnpm, Yarn, Bun, pip, uv and Poetry to see what each one actually does, and what you can turn on today.

Why cooldowns work at all

Most malicious releases of popular packages are caught within hours. PyPI's incident report on LiteLLM and telnyx gives real numbers. The malicious LiteLLM versions were live for 2 hours 32 minutes from upload to quarantine, and were still downloaded over 119,000 times in that window. telnyx was live for 3 hours 42 minutes. On npm, the AsyncAPI maintainers say the trojanized versions of their packages in July were available for about 4 hours 20 minutes.

A one-day delay would have skipped all three. That is the whole pitch: let the scanners and the registry do their job before your build pulls the new version.

What GitHub package firewall is

  • What it checks. Per the setup docs, enterprise admins set five controls: block known malware from the GitHub Advisory Database (on by default), block versions with vulnerability advisories above a severity you choose (default Critical only), block attestation downgrades where earlier versions had SLSA attestations and the new one does not (on), block versions that newly add preinstall or postinstall scripts (on), and a minimum package age of 1 to 365 days (default 1 day; 0 turns it off).
  • Where it works. npm only, for now. You point npm at an enterprise-specific registry URL, or put it behind JFrog Artifactory (marked experimental). Every request needs a token from a member of the enterprise.
  • Who can use it. GitHub Enterprise Cloud customers, including data residency.
  • What it costs. Free during the preview. GitHub says that at general availability it will be billed on metered usage, similar to Actions. No price has been published yet.
  • When it blocks something you need. The npm output includes a link to request an exception. Enterprise admins or enterprise security managers approve it, optionally for every version of that package.
# user-level ~/.npmrc, replace ENTERPRISE with your enterprise slug
registry=https://package-firewall.githubapp.com/npm/enterprises/ENTERPRISE
//package-firewall.githubapp.com/npm/enterprises/ENTERPRISE/:_authToken=${GITHUB_TOKEN}

The interesting part is not the cooldown. It is the attestation and install-script checks, which compare a new version against the package's own history. That is a signal a client-side setting cannot easily see.

The comparison

Here is where every tool stands as of today, from each vendor's own documentation. Scroll sideways on a phone.

ToolCooldown settingUnitDefaultPer-package exemptionSinceOther install-time checks
GitHub Package FirewallMinimum package age (enterprise policy)Days, 1 to 365 (0 turns it off)1 dayBypass requests, approved by enterprise admins or enterprise security managersPublic preview, Oct 20, 2026Blocks known malware (on), vulnerability advisories (Critical only by default), attestation downgrades (on), newly added install scripts (on). npm only. GitHub Enterprise Cloud only.
npm CLImin-release-ageDaysOffmin-release-age-exclude (11.17.0)11.10.0v12 (Jul 8, 2026): dependency install scripts blocked unless allowed in allowScripts; allow-git and allow-remote default to none.
pnpmminimumReleaseAgeMinutes1440 (1 day) since v11minimumReleaseAgeExclude10.16.0Dependency build scripts off since v10 (allowBuilds since 10.26.0), trustPolicy: no-downgrade, blockExoticSubdeps (on by default). Re-checks lockfile entries since 11.3.0.
Yarn (Berry)npmMinimalAgeGateDuration string like "3d" or "1w"1 day since 4.15.0npmPreapprovedPackages (names or globs)4.10.0Third-party postinstall scripts off by default (enableScripts); enableHardenedMode checks the lockfile against the registry, on automatically for public-repo PRs on GitHub.
BunminimumReleaseAgeSeconds (units like 3d also accepted)OffminimumReleaseAgeExcludes1.3Lifecycle scripts run only for a built-in list of popular packages unless you set trustedDependencies, which replaces that list ([] turns it off); pluggable install.security.scanner.
pip--uploaded-prior-toDate (26.0) or P<n>D days (26.1)OffNone documented26.0 (absolute), 26.1 (relative)Hash checking (--require-hashes); 26.2 reads upload-time from pylock.toml.
uvexclude-newerTimestamp, "7 days", or ISO 8601 like P7DOffexclude-newer-packageNot stated in settings docsOptional OSV malware check during sync ([tool.uv.audit] malware-check, off by default).
Poetrysolver.min-release-ageDays0 (off)solver.min-release-age-exclude, -exclude-source2.4.0Only enforced where the index exposes upload times.
Socket Firewall FreeNonen/an/aNo allow-list in Freen/aFree wrapper (sfw npm install). Blocks confirmed malware for npm, yarn, pnpm, pip, uv and cargo. AI-flagged packages only warn; unknown or unscanned versions pass.

Copy-paste config, tool by tool

I use 3 days in most examples. The PyPI team uses 3 days in its own guidance, uv's docs use examples like 7 days, and pnpm ships with 1. Pick a number your team can live with, and exempt your own packages so internal releases are not stuck for days.

npm

min-release-age arrived in npm 11.10.0 and is set in days. The exclude list, min-release-age-exclude, arrived in 11.17.0 and takes names or globs like @your-scope/*. --allow-git has been around since early 2026 (11.9.0 per the CLI changelog). The other three, --allow-file, --allow-remote and --allow-directory, need 11.15.0 or newer: they first appear in 11.14.0, with config support in 11.15.0.

# ~/.npmrc or project .npmrc (npm 11.17.0 or newer for the exclude list)
min-release-age=3
min-release-age-exclude[]=@your-scope/*
# Already the default on npm 12; set explicitly on npm 11.15.0+
allow-git=none
allow-remote=none

The bigger change is npm 12, released July 8. It blocks dependency lifecycle scripts by default (preinstall, install, postinstall and implicit node-gyp builds) unless the package is listed in allowScripts in your package.json. It also stops resolving git and remote tarball dependencies unless you allow them. npm 11.16.0 and newer show warnings for all of this, so you can prepare first.

# npm 12: see which dependencies want to run install scripts
npm approve-scripts --allow-scripts-pending
# approve the ones you trust; entries are pinned to the installed version by default
# (--no-allow-scripts-pin writes name-only entries), then commit package.json
npm approve-scripts esbuild sharp
# explicitly block the rest
npm deny-scripts some-package
npm rebuild

When min-release-age blocks npm audit fix from installing a fresh patch, npm keeps the vulnerable version, warns, and exits non-zero. Add the package to the exclude list to take the fix.

pnpm

pnpm and Yarn both ship a one-day cooldown by default. In pnpm, since v11, minimumReleaseAge defaults to 1440 minutes (1 day). One catch: the built-in default is non-strict, so if no version in your range is old enough, pnpm quietly falls back to a newer one. Set the value yourself and strict mode turns on. pnpm also stopped running dependency build scripts in v10. Today you approve them with allowBuilds, added in 10.26.0; earlier releases used onlyBuiltDependencies, which v11 removed. pnpm's trustPolicy: no-downgrade refuses a version whose trust evidence dropped compared to earlier releases.

# pnpm-workspace.yaml
minimumReleaseAge: 4320        # 3 days, in minutes
minimumReleaseAgeExclude:
  - '@your-scope/*'
trustPolicy: no-downgrade
allowBuilds:
  esbuild: true

Yarn

Yarn (the modern 4.x line) added npmMinimalAgeGate in 4.10.0, and 4.15.0 (May 19, 2026) made 1d the default. Exemptions go in npmPreapprovedPackages, which takes package names or globs. Per the same docs, Yarn does not run third-party postinstall scripts unless you turn on enableScripts.

# .yarnrc.yml
npmMinimalAgeGate: "3d"
npmPreapprovedPackages:
  - "@your-scope/*"

Bun

minimumReleaseAge arrived in Bun 1.3 and is in seconds, though the install docs say it also takes units like 3d or 2 weeks. Exemptions can name a whole package or specific vetted versions. Bun also does not run arbitrary dependency lifecycle scripts by default. Without trustedDependencies, it still runs them for a built-in list of popular packages. Setting the field replaces that list, and trustedDependencies: [] turns it off entirely.

# bunfig.toml (or ~/.bunfig.toml for every project)
[install]
minimumReleaseAge = 259200     # 3 days, in seconds
minimumReleaseAgeExcludes = ["@your-scope/shared"]

pip

pip 26.0 added --uploaded-prior-to with an absolute date. pip 26.1 lets it take a duration in days, like P3D, which is what makes it usable as a standing cooldown in config. I could not find a per-package exemption in pip's docs; you override the whole setting with P0D on the command line.

# ~/.config/pip/pip.conf  (pip 26.1 or newer)
[install]
uploaded-prior-to = P3D
# one-off, or override the config for an urgent fix
python -m pip install --uploaded-prior-to=P3D requests
python -m pip install --uploaded-prior-to=P0D somepackage==1.2.3

uv

exclude-newer takes an RFC 3339 timestamp, a friendly duration like "7 days", or an ISO 8601 duration like "P7D". They mean the same thing, so the "P3D" example in PyPI's guidance and the "7 days" style in uv's docs are both valid. exclude-newer-package gives per-package dates, or false to exempt a package entirely.

# pyproject.toml  (or drop the [tool.uv] header in ~/.config/uv/uv.toml)
[tool.uv]
exclude-newer = "7 days"
exclude-newer-package = { my-internal-lib = false }

Poetry

Poetry 2.4.0 added solver.min-release-age in days, plus exclusions by package or by whole index. Poetry notes it can only filter on indexes that publish upload times.

poetry config solver.min-release-age 3
poetry config solver.min-release-age-exclude "my-internal-lib"

Socket Firewall Free

If you want malware blocking without GitHub Enterprise, Socket Firewall Free is a free wrapper you put in front of the install command. No API key, no config. It blocks only malware confirmed by human review; AI-flagged packages get a warning. Telemetry is always on in the free version, it only works with public registries, and it uses the PolyForm Shield license. It does not block unknown or unscanned versions either, so it does not replace a cooldown. And it cannot block what is already in your local cache, so clear the cache once when you start.

npm i -g sfw
sfw npm ci
sfw uv pip install -r requirements.txt

What the registries do on their side

  • npm scans at publish time. Since July 28, new npm packages are scanned before they become installable, usually taking about five minutes. A version can be published, held for review, or blocked.
  • npm staged publishing. Since May 22 (CLI 11.15.0), maintainers can make CI publish to a queue that a human must approve with a 2FA check. That protects you only if the maintainers of your dependencies turn it on.
  • PyPI quarantine is both manual and automatic. PyPI admins have been able to quarantine a project since 2024, which hides it from installers and can be reversed. In the April incident report, PyPI says the telnyx response was taken "autonomously" because reports from its pool of trusted reporters "add weight to any given report, triggering an automated quarantine feature." So: admins still quarantine by hand, and enough trusted reports can now trigger it automatically.
  • Attestations prove origin, not safety. PyPI's own security model puts it plainly: "An attestation will tell you where a PyPI package came from, but not whether you should trust it."

Where cooldowns stop helping

  • Patient attackers. A cooldown is a delay, not a scan. If nobody notices a malicious version within your window, you install it anyway. Seven days buys more than one, but nothing is guaranteed.
  • Properly signed malware. In the AsyncAPI attack, the attacker pushed code to release branches and let the project's own pipeline publish it, so the packages carried valid OIDC trusted publishing provenance. GitHub's attestation downgrade check and pnpm's trustPolicy look for missing or weaker trust evidence. Neither would catch that case. And the payload ran when the library was loaded, not from an install script, so blocking install scripts would not have stopped it either.
  • Lockfiles. Cooldowns filter new resolution. Bun's docs say it outright: existing packages in bun.lock stay unchanged. pnpm is stricter: since 11.3.0 it re-applies minimumReleaseAge and trustPolicy to lockfile entries unless you set trustLockfile: true. npm's docs do not say it re-checks an existing package-lock.json, so I would not count on it. Review lockfile changes in pull requests, especially from outside contributors.
  • Urgent security fixes. A cooldown also delays the patch. PyPI notes that Dependabot and Renovate skip cooldowns for security updates by default; for everything else, use the exemption lists.
  • Everything that is not npm or PyPI. GitHub's firewall covers npm only today, and none of these settings touch container images, GitHub Actions, or install scripts piped from curl. Pin base images by digest (Docker's best practices explain why), and pin Actions by commit SHA rather than tag.
# Dockerfile: pin the base image by digest, not just by tag
FROM node:24-slim@sha256:<digest-you-verified>

What I would do

  1. Turn on a cooldown everywhere today. It costs nothing. 3 days is a sensible start, with your own packages exempted. Put it in the user-level config on developer machines, and in the project config so CI gets it too.
  2. Get to npm 12, or stay on pnpm 10 or newer, Yarn 4, or Bun. Blocking dependency install scripts by default closes the most common way these attacks run code.
  3. Commit lockfiles and install from them (npm ci, pnpm install --frozen-lockfile, yarn install --immutable, bun install --frozen-lockfile, uv sync --locked).
  4. If you are already on GitHub Enterprise Cloud, try Package Firewall on CI and agent runners while it is free. Its install-script and attestation checks add something a cooldown cannot.
  5. If you are not, Socket Firewall Free covers malware blocking for npm, pnpm, yarn, pip, uv and cargo. Commercial gateways like Sonatype Repository Firewall cover far more ecosystems, including Docker, if you need one policy for everything.

Cooldowns are the cheapest supply-chain control I know. They will not stop a determined attacker, but a one-day delay would have skipped all three incidents above, and it takes one line of config.

Sources: GitHub changelog, GitHub package firewall docs, npm staged publishing, npm v12 breaking changes, npm v12 GA, npm publish-time scanning, npm CLI v11 changelog, npm config, npm approve-scripts, pnpm supply chain security, pnpm settings, Yarn settings, Yarn 4.15.0, Bun install docs, Bun lifecycle scripts, Bun 1.3, pip changelog, pip user guide, uv settings, Poetry configuration, PyPI incident report, PyPI quarantine, PyPI attestation security model, Socket Firewall Free, AsyncAPI incident report, Microsoft on AsyncAPI.