cargo-cooldown
cargo-cooldown is a lightweight wrapper for Cargo that shields local workspaces from freshly published crates on crates.io. It enforces a configurable cooldown window before new releases can enter your dependency graph, buying time for review and reducing a common supply chain risk. cargo-cooldown is a proof of concept aimed at developer machines. It is meant as a local utility for workflows where you refresh dependencies and immediately rebuild or run the project, so it shields developers in their own environment. CI pipelines and release automation should continue to run plain Cargo against committed Cargo.lock files.
Why it exists
Attackers can push brand new crates or updates that satisfy permissive semver ranges. Installing them right away means your project might consume malicious code before the ecosystem can react. By delaying adoption, cargo-cooldown keeps you on the most recent release that is older than the cooldown window you configure. Socket covers this threat model in their report on crates.io phishing campaigns.
Quick start
- Install the wrapper next to your toolchain:
- Run day-to-day commands like
build,check,test, orrunthroughcargo-cooldown, setting a cooldown in minutes:
Avoid pairing it withCOOLDOWN_MINUTES=1440cargo update: that command is meant to refreshCargo.lock, so running it through the wrapper would undo the cooled down graph. - When a dependency is too young,
cargo-cooldownlooks for the newest eligible version, pins it withcargo update --precise, re-runscargo metadata, and then retries your command. If no compatible version is old enough, it exits with guidance on how to proceed.
How it works
cargo-cooldownensures aCargo.lockfile exists, generating one withcargo generate-lockfileif needed.- It calls
cargo metadatato read the full dependency graph and records everyVersionReqthat parents impose on their children. - For each crate sourced from a watched registry, it fetches publication metadata from the crates.io HTTP API through a small on-disk cache and computes the package age. Allowlist rules can lower the effective cooldown per crate or globally, but they never raise it above the baseline from
COOLDOWN_MINUTES. - Every crate younger than the effective cooldown enters a queue. The queue gives priority to nodes that might drag others with strict
=constraints so related packages can be updated together. - Candidate versions are filtered so they are not yanked, satisfy every observed semver requirement, are older than the current lockfile entry, and were published before the cutoff timestamp.
- Each candidate is attempted via
cargo update -p crate@<current_version> --precise <candidate_version>. If Cargo rejects the change, the blocking crates are added back to the queue unless they are exempt through the allowlist. - After a successful downgrade, the tool repeats the cycle until the graph contains only releases older than the cooldown window. When no acceptable candidate exists, the run aborts with a clear error so you can wait, loosen the requirement, or patch it manually.
Note: today the publication timestamp comes from the crates.io API. Once that data is shipped with the index metadata, those network calls can be replaced with local lookups.
Configuration
All behavior is driven by environment variables so you can tune it per invocation or in scripts:
COOLDOWN_MINUTES(default0): minimum age, in minutes, for a release to be considered safe. The cooldown logic only runs when the value is greater than zero.COOLDOWN_MODE(defaultenforce): switch towarnto log violations without failing, oroffto skip cooldown logic temporarily.COOLDOWN_ALLOWLIST_PATH: path to a TOML allowlist that relaxes cooldowns for specific crates or pins exact versions. If unset, the tool looks forcooldown-allowlist.tomlin the workspace root.COOLDOWN_TTL_SECONDS(default86400): lifetime of cached registry responses.COOLDOWN_CACHE_DIR: directory used to store cache files. By default the OS cache directory is used with acargo-cooldown/suffix.COOLDOWN_OFFLINE_OK(defaultfalse): when true, missing network calls are tolerated and only cached data is used.COOLDOWN_HTTP_RETRIES(default2, max8): retry budget for API requests.COOLDOWN_VERBOSE(defaultfalse): enable extra tracing output to see resolution decisions.COOLDOWN_REGISTRY_API(defaulthttps://crates.io/api/v1/): override the API base if you mirror crates.io.COOLDOWN_REGISTRY_INDEX(defaultregistry+https://github.com/rust-lang/crates.io-index, registry+sparse+https://index.crates.io/): comma separated list of registry sources to guard. Values without theregistry+prefix are normalized automatically. Dependencies from other registries are left untouched.
Examples
The examples/ directory contains material to explore the tool:
demo/: a small workspace with crates.io dependencies you can build withcargo-cooldown buildto watch downgrades in action.cooldown-allowlist.toml: sample allowlist showing global and per crate overrides as well as exact exceptions.run.sh: convenience script with ready made invocations that toggle the most relevant environment variables.
You can try the full flow by running:
cd examples/demoCOOLDOWN_MINUTES=1440 cargo-cooldown build- Inspect the output, tweak the allowlist or environment variables, and run again to see how the graph changes.
Good practices
- Start with
COOLDOWN_MODE=warnso you can review which crates would be downgraded before modifyingCargo.lockin a sensitive repository. - Use an allowlist entry when you must adopt a critical update sooner, and document the reason so it can be revisited later.
- If you depend on alternate registries, make sure
COOLDOWN_REGISTRY_INDEXexplicitly lists each source you want to protect.
cargo-cooldown remains a prototype exploring how cooldown periods could fit into Cargo workflows. Feedback and real-world reports are welcome.