Skip to content

feat: release-age cooldown (minimumReleaseAge)#87

Open
donfear wants to merge 1 commit into
mainfrom
feat/minimum-release-age
Open

feat: release-age cooldown (minimumReleaseAge)#87
donfear wants to merge 1 commit into
mainfrom
feat/minimum-release-age

Conversation

@donfear

@donfear donfear commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Implements the top 'Next' item from the audit roadmap: a supply-chain cooldown that refuses to offer versions published more recently than a configurable window. Freshly published versions are the ones most likely to be a compromised release nobody has caught yet — this is the same guard as pnpm's minimumReleaseAge (same name, same unit: minutes), which this repo already applies to itself in pnpm-workspace.yaml.

Usage

inup --minimum-release-age 10080   # ignore versions younger than 7 days

or in .inuprc (CLI wins over config; exclusions support the same globs as ignore):

{ "minimumReleaseAge": 10080, "minimumReleaseAgeExclude": ["@myco/*"] }

The gate applies everywhere version targets are computed (detector level), so the TUI, --json/--check reports, and --apply all respect it.

Design notes

  • Full packument opt-in. Publish times don't exist in the abbreviated install-v1 format (verified against registry.npmjs.org), so the policy switches the fetch to application/json — only when enabled, so default runs keep the small payloads. ETag cache keys get a #full variant so a 304 can never revive a body of the wrong format.
  • Filtered at resolve time, not in the cache. The cutoff moves with the clock; versions maturing past the window reappear on the next run even on a 304.
  • Latest recomputation. If the true latest is gated, the newest old-enough version becomes the effective latest, and the deprecation/engines signals (which describe the true latest) are dropped rather than misattributed. If every version is too young, the installed version is the effective latest — "nothing to upgrade to (yet)".
  • Fail-open. Registries that don't expose time degrade to a no-op; missing/unparsable timestamps keep the version visible.

Verification

  • 30 new unit tests across versions/registry/detector/config; full suite (1113 tests) passes with the 100% coverage gate.
  • E2E against the real registry on a project with semver: ~7.5.0: no cooldown → latest 7.8.5; --minimum-release-age 1576800 (3 years) → effective latest 7.5.3.

Follow-ups (deliberately out of scope): released Nd ago badge in the TUI, a minimum-release-age input on the GitHub Action.

Versions published more recently than a configurable window are not
offered as upgrade targets — in the TUI, in reports, and in --apply.
Freshly published versions are the most likely to be a compromised
release nobody has caught yet; this is the same supply-chain guard as
pnpm's minimumReleaseAge (same name, same unit: minutes), which this
repo already applies to itself.

- --minimum-release-age <minutes> CLI flag; .inuprc keys
  minimumReleaseAge + minimumReleaseAgeExclude (glob patterns, like
  ignore). CLI wins over config.
- Publish times only exist in the FULL packument, so the policy opts
  the fetch out of the abbreviated install-v1 format; ETag cache keys
  gain a #full variant so a 304 can never revive the wrong format.
- The gate is applied at resolve time (not baked into cached data), so
  versions maturing past the window reappear without a cache change.
- When the true latest is gated, its deprecation/engines signals are
  dropped rather than misattributed; if every version is too young the
  installed version becomes the effective latest (nothing to offer).
- Registries without time data degrade to a no-op.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant