Policy rules reference
A version is held while any rule fails. All rules are off by default; an empty policy holds nothing. Unless marked otherwise, a rule failure can be waived for a single version by manual promotion approvals.
On publish namespaces, five rules can also reject at publish
instead of holding: their remedy never needs the version to exist, so
rejection keeps the version number free for a fixed re-publish. The
vcs, repository_change, and license toggles default to reject
(the two VCS strengths share one toggle); new_publisher defaults to
hold. Mirrors never reject — there is no publish to refuse, imported
versions always hold at read time — so the toggles do not exist on a
mirror’s policy.
| Rule | Checks | Waivable | Rejects at publish |
|---|---|---|---|
quarantine |
Minimum age since publish (or first seen) | yes | never |
sbom |
An SBOM is attached | yes | never |
attestations |
Enough distinct identities attested | yes | never |
license |
Declared license satisfies the SPDX allowlist | yes | default on |
vcs |
Tarball claims a clean VCS commit | yes | default on |
vcs_downgrade |
Provenance once claimed keeps being claimed | yes, or trust | default on |
repository_change |
The declared repository stays what it was | yes, or trust | default on |
new_publisher |
Publisher is trusted for this crate | yes, or a vouch | default off |
advisories |
No advisory at a blocking level | no, allow the advisory | never |
The three trust rules take a per-version waiver like any other, but their intended exit is the standing trust described in each section below: it lifts every version the same change holds, not one.
advisories is the one rule with no per-version exit at all. Owners
can allow an advisory by id, which stops it holding anything in the
namespace, but there is no way to ship one version despite an advisory
that still applies to the others. Out-voting a CVE on a single version
is not a thing Haven lets you do.
quarantine
Section titled “quarantine”Holds a version until it has aged past the configured delay. Stored in seconds, edited in days in the UI. On publish namespaces the clock starts at publish time; on mirrors it starts when the mirror first sees the version (see Mirrors). Versions older than the policy pass immediately; enabling quarantine never re-holds history.
Use it to let the ecosystem be your canary: most malicious releases are caught within days of appearing.
Holds versions that have no SBOM attached. Haven generates a CycloneDX SBOM for publishes automatically, and one can be uploaded per version; see SBOM & attestations.
attestations
Section titled “attestations”Holds a version until at least the configured number of attestations from distinct identities are attached. Use it to require sign-off from N release engineers or build systems before a version ships.
license
Section titled “license”An SPDX allowlist, for example MIT, Apache-2.0, BSD-3-Clause.
The version’s declared license expression must be satisfiable using
only allowed identifiers (MIT OR GPL-3.0 passes an MIT allowlist;
MIT AND GPL-3.0 does not).
The rule fails closed, with distinct hold reasons for each case: the declaration is missing, the declaration does not parse as SPDX, or the expression cannot be satisfied from the allowlist.
The declaration is frozen into the tarball, so a failing version can never pass this rule later. By default a failing publish is therefore rejected, keeping the version number free: fix the declaration (or widen the allowlist first) and publish the same version again. With the reject toggle off, the version is accepted and permanently held; only an owner waiver can ship it.
Requires the tarball’s packaging metadata to claim a clean VCS state:
a known commit with no dirty files. Publishes made with
--allow-dirty, or without version control provenance, are held.
This state is frozen into the tarball forever: a version held by
vcs can never pass on its own. By default a failing publish is
therefore rejected, keeping the version number free: publish the
same version again from a clean checkout. With the reject toggle off,
the version is accepted and permanently held; only an owner waiver can
ship it.
vcs_downgrade
Section titled “vcs_downgrade”The lighter VCS strength, shown as Hold dropped provenance in the
policy form (one three-way choice with vcs, which subsumes it).
Crates that never claimed provenance pass; a version fails when an
earlier version of the crate claimed VCS provenance and this one
claims none. A release suddenly built outside the crate’s usual git
flow is the tampered-tarball shape and worth a human look. Dirty
claims pass: the rule watches for provenance disappearing, not for
its quality.
Unlike vcs, the hold is not permanent: owners can accept the crate’s
new provenance-less state with Trust new state on the Held tab.
Like publisher vouches, the trust is standing and takes the promotion
threshold of distinct owners to grant, one owner to revoke — and it is
scoped to the current epoch: if the crate resumes claiming provenance
and then drops it again, the drop holds again until owners re-affirm.
The rejection default and mechanics follow the shared VCS toggle: a failing publish is rejected with the version number kept free (republish from the usual checkout, or get the state trusted and the same version publishes again), and rejected attempts are listed on the Held tab with the same Trust action. Inert on mirrors: cached versions carry no provenance facts, so nothing ever counts as prior.
repository_change
Section titled “repository_change”The same change-is-the-signal shape as vcs_downgrade, over a value:
the manifest’s declared repository URL. Crates that never declared
one pass, and so does the first declaration — but once declared, a
version declaring a different repository (or dropping the
declaration) fails. A crate quietly pointing consumers at a new
repository is the handover shape and worth a human look.
The hold is not permanent: owners can accept the new value with Trust new repository on the Held tab. The trust names the exact new URL, takes the promotion threshold of distinct owners to grant, one to revoke — and the rule ratchets onto the crate’s latest declaration, so switching back to a previous repository is itself a change needing its own trust.
By default a failing publish is rejected with the version number kept free: revert the field, or get the new value trusted and the same version publishes again. Rejected attempts are listed on the Held tab with the attempted URL and the Trust action.
On crates.io mirrors the rule works from sampled observations: the dump carries only the crate-level repository (the value as of dump time), so each refresh records it, and a change between two refreshes holds the versions published in that window until owners trust the new URL. The usual dump caveats apply: history before the first observation is trusted, versions newer than the latest dump pass until the next refresh, and several changes inside one refresh window read as one. Mirrors of other registries publish no dump and carry no repository facts, so the rule never holds anything there.
advisories
Section titled “advisories”Holds versions affected by a security advisory whose level is in the
policy’s blocking set. Levels: vulnerability, unsound,
unmaintained, notice. With no levels selected (the default),
advisories are recorded and shown but never hold anything.
For crates.io mirrors, advisories arrive automatically from the
RUSTSEC database; owners can raise advisories manually anywhere
(manual raises file as vulnerability). Withdrawn advisories release
their holds automatically; reclassified ones change level.
Not waivable. The only bypass is per advisory id: adding an id
such as RUSTSEC-2024-0003 to the policy’s ignored list makes that
one advisory never hold, whatever its level. Changing levels or the
ignored list takes effect immediately, no re-sync needed.
An owner can add an id straight from a held version, without going via the policy form: the Held tab’s Trust menu offers an Allow action for each advisory holding that version. It is a policy edit in a per-advisory shape, so one owner suffices and the blast radius is the whole namespace, not the one version: the advisory stops holding everything it touches. This is the practical relief valve on a large mirror, where an advisory on a popular crate can hold thousands of versions that no one will ever review one by one. Removing an id again is a Policy tab edit.
new_publisher
Section titled “new_publisher”A trust model for maintainer changes, aimed at the supply chain attack where a well-known crate quietly gains a new releaser. When enabled, the policy records a cutoff instant:
- Publishers whose first release of a crate predates the cutoff are trusted for that crate. Enabling the rule trusts all history, so it turns on without holding anything retroactively.
- A version released by a publisher new to that crate after the cutoff is held until enough distinct owners vouch for the publisher (the same threshold as promotion approvals).
Vouching happens from the Held tab (Trust publisher) and applies to all of that publisher’s versions of the crate, past and future. Any single owner can revoke trust. Where publisher facts come from depends on the namespace kind: publish namespaces record them first-hand, crates.io mirrors take them from the catalog dump. Mirrors of other registries have no publisher facts, so the rule never holds there.
On publish namespaces the rule can instead reject an untrusted publisher’s release outright (off by default). Trust is in the person, not the version, so nothing is lost: once enough owners vouch, the same version publishes again. Because a rejection leaves no held row, the Held tab lists recent rejected attempts with the same Trust action. Note that a read token minted with “serve held versions” can serve a held version, but a rejected publish was never stored, so there is nothing to serve.
Reviewing a hold
Section titled “Reviewing a hold”A hold is a question put to owners, and most of the Held tab’s work is deciding which questions are still open. The tab therefore opens on Needs review: the versions held by a rule no owner has looked at yet. Accept hold answers one — it records that an owner saw these failures and is leaving the version held, so the row drops out of the review queue.
Accepting releases nothing. The version stays held, invisible to consumers, exactly as before; promotion, a vouch, or a state trust are what actually ship a version. Because nothing is released, a single owner suffices, where a waiver takes the full threshold.
An acceptance names the rule kinds failing when it was made, and is judged against the version’s current failures. If a new rule starts holding the version later, the acceptance no longer covers it and the version returns to the queue: what an owner accepted was those failures, not any future ones.
Quarantine is never reviewable. A version held only by its cooldown clears on its own, so it never enters the queue and cannot be accepted.
Promotion threshold
Section titled “Promotion threshold”promotion approvals is not a hold rule but the policy’s waiver
threshold: how many distinct owner identities must approve to waive a
rule failure for one version, and how many vouches lift a
new_publisher hold. Default 2 (four eyes), minimum 1.