Skip to content

Add release-age cooldown to dependency resolution#1160

Draft
ericmj wants to merge 1 commit into
mainfrom
cooldown
Draft

Add release-age cooldown to dependency resolution#1160
ericmj wants to merge 1 commit into
mainfrom
cooldown

Conversation

@ericmj
Copy link
Copy Markdown
Member

@ericmj ericmj commented May 18, 2026

A configurable delay between when a package version is published and when it becomes eligible for selection during dependency resolution, to mitigate supply-chain attacks where a freshly compromised release is pulled into projects before it can be detected and retired.

Configuration:

  • New cooldown config key (env HEX_COOLDOWN, project :hex block, global hex.config); accepts "d", "w", "mo".
  • New cooldown_exclude_repos config key (env HEX_COOLDOWN_EXCLUDE_REPOS comma-separated, project / global as list); skips cooldown for the named repos so an organization can consume hotfixes to its own repository without delay.

Implementation:

  • Hex.Cooldown parses durations, computes cutoffs, and renders pre-flight errors and solver failure notes.
  • Hex.Registry.Server stores per-release published_at (Unix seconds extracted from the registry Timestamp) under a new ETS slot; @ets_version bumps to 5 so stale caches missing the slot are invalidated on upgrade. Hex.Dev.copy_package and the test fixture helper grow alongside.
  • Hex.Registry.Cooldown wraps the registry behaviour, filtering versions on versions/2 against the active cutoff and respecting the per-repo exclude list and a per-resolution bypass set.
  • Hex.RemoteConverger builds the cutoff and bypass set at the start of resolution. The bypass set has two sources:
    • packages whose lock survived prepare_locked (mix deps.get against an intact lockfile bypasses cooldown entirely);
    • packages in old_lock whose locked version is known-unsafe (retired or carrying a security advisory) so re-resolving to escape a known-unsafe lock is never blocked. A pre-flight check walks both top-level requirements and nested hex deps under non-Hex (path / git) parents, raising a formatted cooldown error when every matching version of a direct dep is in cooldown. Solver failures get a generic cooldown hint appended when cooldown is non-zero.

Releases without published_at (legacy registry data, repos that have not rebuilt their index, or self-hosted clones) are treated as eligible. Empty env values (HEX_COOLDOWN=,
HEX_COOLDOWN_EXCLUDE_REPOS=) fall through to the next source instead of clobbering project / global config.

Re-vendor hex_core to pick up the Timestamp published_at field on Release.

@ericmj ericmj force-pushed the cooldown branch 2 times, most recently from 628f5ae to 66778b8 Compare May 18, 2026 22:42
A configurable delay between when a package version is published and
when it becomes eligible for selection during dependency resolution,
to mitigate supply-chain attacks where a freshly compromised release
is pulled into projects before it can be detected and retired.

Configuration:
* New `cooldown` config key (env `HEX_COOLDOWN`, project `:hex`
  block, global `hex.config`); accepts "<N>d", "<N>w", "<N>mo".
* New `cooldown_exclude_repos` config key (env
  `HEX_COOLDOWN_EXCLUDE_REPOS` comma-separated, project / global as
  list); skips cooldown for the named repos so an organization can
  consume hotfixes to its own repository without delay.

Implementation:
* `Hex.Cooldown` parses durations, computes cutoffs, and renders
  pre-flight errors and solver failure notes.
* `Hex.Registry.Server` stores per-release `published_at` (Unix
  seconds extracted from the registry Timestamp) under a new ETS slot;
  `@ets_version` bumps to 5 so stale caches missing the slot are
  invalidated on upgrade. `Hex.Dev.copy_package` and the test
  fixture helper grow alongside.
* `Hex.Registry.Cooldown` wraps the registry behaviour, filtering
  versions on `versions/2` against the active cutoff and respecting
  the per-repo exclude list and a per-resolution bypass set.
* `Hex.RemoteConverger` builds the cutoff and bypass set at the
  start of resolution. The bypass set has two sources:
  - packages whose lock survived prepare_locked (mix deps.get against
    an intact lockfile bypasses cooldown entirely);
  - packages in old_lock whose locked version is known-unsafe (retired
    or carrying a security advisory) so re-resolving to escape a
    known-unsafe lock is never blocked.
  A pre-flight check walks both top-level requirements and nested
  hex deps under non-Hex (path / git) parents, raising a formatted
  cooldown error when every matching version of a direct dep is in
  cooldown. Solver failures get a generic cooldown hint appended when
  cooldown is non-zero.

Releases without `published_at` (legacy registry data, repos that
have not rebuilt their index, or self-hosted clones) are treated as
eligible. Empty env values (`HEX_COOLDOWN=`,
`HEX_COOLDOWN_EXCLUDE_REPOS=`) fall through to the next source
instead of clobbering project / global config.

Re-vendor hex_core to pick up the Timestamp `published_at` field on
`Release`.
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