Skip to content

Instantly share code, notes, and snippets.

@altryne
Created May 14, 2026 23:28
Show Gist options
  • Select an option

  • Save altryne/233ea8c8446c1ed0aead7561aeeca213 to your computer and use it in GitHub Desktop.

Select an option

Save altryne/233ea8c8446c1ed0aead7561aeeca213 to your computer and use it in GitHub Desktop.
Setting minimum age on package manager configs to prevent supply chain attacks

Minimum-Age Gates for Package Managers

Use this as a baseline across all Macs to reduce exposure to fast-moving supply-chain attacks, where a malicious package version is published, installed by early updaters, then removed hours later.

The default policy I would use is:

  • Personal/dev machines: 3 days.
  • CI and production lock refresh jobs: 3 to 7 days.
  • Emergency security fixes: bypass deliberately, one package at a time, with a reviewed lockfile diff.

This does not replace lockfiles, checksums, audit tooling, or script restrictions. It just adds a time buffer before newly published versions become installable.

Quick Matrix

Ecosystem Native minimum-age gate? Where to set it Unit
npm Yes .npmrc days
pnpm Yes pnpm-workspace.yaml or global pnpm config minutes
Yarn Berry Yes .yarnrc.yml duration, for current Yarn
Deno Yes, unstable deno.json or CLI flag minutes, ISO-8601 duration, or RFC3339 timestamp
Bun Yes bunfig.toml or CLI flag seconds
Python / PyPI via pip No native pip gate Use uv, a mirror/proxy, or generated constraints uv supports timestamps/durations
Homebrew No native age gate Use pins, delayed brew update, or your own tap n/a
Rust / Cargo No native age gate Use Cargo.lock, vendoring, or alternate registry/mirror n/a

Recommended Baseline

Use a 3-day gate unless a project has a specific reason not to.

Equivalent values:

  • npm: 3
  • pnpm: 4320
  • Yarn: "3d"
  • Deno: "P3D" or 4320
  • Bun: 259200
  • uv: "3 days"

npm

Requires npm 11.10.0 or newer.

In a repo .npmrc:

min-release-age=3

This tells npm to resolve only versions that were available more than the configured number of days ago. npm's docs also note that this cannot be combined with before.

For "all my Macs", set it in the user-level npm config:

npm config set min-release-age 3 --location=user

Still commit project lockfiles and use:

npm ci

in CI instead of casual npm install.

pnpm

Requires pnpm 10.16.0 or newer. pnpm 11 defaults minimumReleaseAge to 1440 minutes, but the built-in default is non-strict for compatibility. If you want enforcement, set it explicitly.

In pnpm-workspace.yaml:

minimumReleaseAge: 4320
minimumReleaseAgeStrict: true
minimumReleaseAgeIgnoreMissingTime: false

Optional allowlist:

minimumReleaseAgeExclude:
  - "@your-org/*"
  - "typescript"

For all Macs, use pnpm's global config file (~/.config/pnpm/config.yaml):

minimumReleaseAge: 4320
minimumReleaseAgeStrict: true
minimumReleaseAgeIgnoreMissingTime: false

Yarn Berry

Use modern Yarn, not Yarn Classic. Current Yarn docs expose npmMinimalAgeGate in .yarnrc.yml.

In .yarnrc.yml:

npmMinimalAgeGate: "3d"

Optional allowlist:

npmPreapprovedPackages:
  - "@your-org/*"
  - "typescript"

Review note: the docs confirm npmMinimalAgeGate; Yarn's security page says Yarn 4.12 introduced it, while release notes around 4.11 mention the duration-setting work. To avoid version-edge weirdness, standardize on Yarn 4.12 or newer across machines.

Also consider:

enableHardenedMode: true

for CI or outside-contributor PRs, since it validates lockfile resolution against the registry.

Deno

Deno supports a minimum dependency age, but the schema currently labels it unstable.

In deno.json:

{
  "minimumDependencyAge": "P3D",
  "lock": { "frozen": true }
}

You can also use the object form to exclude specific npm or JSR packages:

{
  "minimumDependencyAge": {
    "age": "P3D",
    "exclude": ["npm:@your-org/pkg", "jsr:@your-scope/pkg"]
  }
}

CLI equivalent:

deno update --minimum-dependency-age P3D

Deno accepts an age in minutes, an ISO-8601 duration such as P3D, or an RFC3339 absolute timestamp.

Bun

In bunfig.toml:

[install]
minimumReleaseAge = 259200 # seconds, 3 days
minimumReleaseAgeExcludes = ["@your-org/pkg", "typescript"]

For all Macs, put the same config in:

~/.bunfig.toml

or:

$XDG_CONFIG_HOME/.bunfig.toml

CLI one-off:

bun add some-package --minimum-release-age 259200

Bun applies this during new resolution; existing versions already locked in bun.lock are not changed just because the gate is enabled.

Python / PyPI

pip itself does not currently have a native "minimum release age" setting comparable to npm/pnpm/Yarn/Bun.

Best practical option if you can use uv:

[tool.uv]
exclude-newer = "3 days"

For uv's pip-compatible interface:

[tool.uv.pip]
exclude-newer = "2026-05-11T00:00:00Z"

Important difference: uv compares against the upload time of each distribution artifact, not just the release version. For normal tool.uv, durations such as "3 days" are accepted. For tool.uv.pip, use a full timestamp for consistent behavior across time zones.

If staying with pip, use one of these instead:

  • Generate and commit pinned constraints with hashes:
pip-compile --generate-hashes
pip install --require-hashes -r requirements.txt
  • Use a private PyPI mirror/proxy that only syncs releases after a delay.
  • Run scheduled dependency updates instead of ad-hoc installs.

Homebrew

Homebrew does not have a native minimum-age gate for formulae or casks.

Use these softer controls:

brew pin <formula>
brew unpin <formula>

Delay metadata refresh on machines where you want upgrades to happen only during a reviewed maintenance window:

export HOMEBREW_NO_AUTO_UPDATE=1

Then run upgrades intentionally:

brew update
brew outdated
brew upgrade

For long-term control, maintain your own tap or extract specific versions, but that makes you responsible for security updates and formula maintenance.

Practical Mac policy: do not run blind brew upgrade during critical production/show windows. Run a weekly reviewed Homebrew maintenance pass instead.

Rust / Cargo

Cargo does not have a native crates.io minimum-age gate.

Use the standard safety controls:

cargo update
cargo build --locked
cargo test --locked

For applications, commit Cargo.lock. In CI, use:

cargo build --locked
cargo test --locked

For stronger control:

cargo vendor

and configure vendored sources in .cargo/config.toml, or route Cargo through an internal registry/mirror. Cargo supports alternate registries and source replacement, which is the right place to enforce an organizational age policy if you need one.

Rollout Across Macs

Use dotfiles or your machine bootstrap script. I would standardize these user-level files:

~/.npmrc
~/.config/pnpm/config.yaml
~/.yarnrc.yml
~/.bunfig.toml
~/.config/uv/uv.toml
~/.cargo/config.toml
~/.zshrc

Example snippets:

# ~/.npmrc
min-release-age=3
# ~/.config/pnpm/config.yaml
minimumReleaseAge: 4320
minimumReleaseAgeStrict: true
minimumReleaseAgeIgnoreMissingTime: false
# ~/.yarnrc.yml
npmMinimalAgeGate: "3d"
# ~/.bunfig.toml
[install]
minimumReleaseAge = 259200
# ~/.config/uv/uv.toml
exclude-newer = "3 days"
# ~/.cargo/config.toml
# No native minimum-age gate. Put registry/mirror/source-replacement policy here if you run one.
# ~/.zshrc
export HOMEBREW_NO_AUTO_UPDATE=1

Verification

Run this on each Mac:

npm --version
pnpm --version
yarn --version
deno --version
bun --version
uv --version
cargo --version
brew --version

Then inspect effective config:

npm config get min-release-age
pnpm config get minimumReleaseAge
yarn config get npmMinimalAgeGate

For Bun, Deno, uv, Homebrew, and Cargo, verify by reading the config files and by doing dependency refreshes in a test repo before relying on the policy.

Sources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment