Skip to content

Cool-down: no package installs from versions younger than three days #399

Description

@JArmandoAnaya

A compromised package is most dangerous in the hours between its publication and
its yanking. The cheapest defence available to us is patience: refuse to install
anything the ecosystem has not had a few days to look at. Dependabot already does
this — .github/dependabot.yml carries cooldown.default-days: 3 on all three
ecosystems — but Dependabot is only one of the ways a version gets chosen here. A
developer typing pnpm add, a uv add, a uv pip install in CI, and a Docker
image build all pick versions with no waiting period at all.

This issue is to apply one cooldown rule — three days — everywhere a version is
chosen
, and to make the rule structural rather than something a person has to
remember to type.

What the survey found

Three things were verified against the pinned toolchains (uv 0.9.13, pnpm 10.30.2)
rather than assumed, and each one shapes the design:

  1. A cooldown is a resolution-time control, not an install-time one. Both
    install-from-lock paths ignore it: pnpm install --frozen-lockfile installs a
    locked version that violates the cooldown without complaint, and uv sync --frozen audits and moves on. That is correct behaviour, not a hole — the
    lockfile is the reviewed artifact. The cooldown belongs where versions are
    picked.

  2. UV_EXCLUDE_NEWER on an install path silently discards the lockfile. Set
    it on a plain uv sync and uv answers Ignoring existing lockfile due to addition of timestamp cutoff, re-resolves, and rewrites uv.lock. So the
    naive "export it globally in CI" approach would have quietly stopped CI from
    testing the pinned dependency set. The cooldown must be scoped to the commands
    that resolve, and the install paths must be pinned so they cannot resolve.

  3. uv has no rolling cooldown setting. --exclude-newer accepts RFC 3339
    timestamps and dates only — 3 days ago, 3d and P3D are all rejected — and
    there is no --minimum-release-age equivalent. pnpm does have one
    (minimumReleaseAge, in minutes, honoured by 10.30.2 and verified to refuse a
    too-new version with ERR_PNPM_NO_MATURE_MATCHING_VERSION). The asymmetry is
    why Python needs a small wrapper and Node needs one config line.

Scope

  • pnpm — declare minimumReleaseAge in pnpm-workspace.yaml so every
    pnpm add/pnpm update, local or in Docker, is covered without a flag.
  • uv — one script owning the number and the rolling cutoff, wrapping the
    commands that resolve (uv add, uv lock, uv pip install, uv build).
  • Harden the install paths so a re-resolve cannot happen behind the cooldown's
    back: CI's uv sync becomes uv sync --locked, which both refuses to resolve
    and gates lockfile drift.
  • Docker — the images inherit the pnpm setting through the copied workspace
    file; add the docker ecosystem to Dependabot, which has no cooldown today
    because it has no entry today, and pin the one floating :latest tag in
    docker/compose.yaml (a floating tag is the one thing no cooldown can protect).
  • Node is pnpm only. scripts/check.sh invokes npx playwright test twice;
    those become pnpm exec. This is already the stated rule in the nodejs-setup
    skill ("pnpm only (never npm, never yarn)") and check.sh is the exception.
  • A gate, in tests/scripts/, so none of the above can rot back.
  • Documentation of the rule and — more importantly — of when to override it,
    since a cooldown that cannot be escaped becomes a cooldown people disable.

Explicitly out of scope

  • apt (ffmpeg in CI and in docker/api.Dockerfile). Debian stable is a
    frozen, signed snapshot already; a three-day rule over it would be theatre.
  • GitHub Actions are already covered by the existing Dependabot entry.
  • Security updates stay immediate, in every ecosystem. A cooldown that delays a
    fix for a known-exploited hole is worse than no cooldown.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    ciCI, tooling, repo automationenhancementNew feature or requesttoolingDeveloper tooling: scripts, checks, local workflow

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions