Skip to content
rusthacks.dev

Yanking a Crate Version and What It Does Not Do Explained

cargo yank stops new projects from selecting a version but leaves it downloadable for existing lockfiles. Learn when to yank, why it cannot remove leaked secrets, and the correct fix for a bad release.

6 min read
Summarize in AI
Flat illustration for the post “Yanking a Crate Version and What It Does Not Do Explained”, composed around security.

The first time a published release turns out to be broken, the instinct is to take it down. crates.io does not let you. What it gives you instead is cargo yank, a switch that is easy to misread because it sounds like deletion. This post shows precisely what the switch changes, using a crate with a bad release, what it leaves alone, and how to handle the three situations that come up in practice: a build-breaking bug, a semver mistake, and a leaked credential. Tested against Cargo 1.99 behavior; the registry rules are those of crates.io.

What yank changes

A yank sets a flag on one version in the registry index. Cargo's resolver treats a yanked version as unavailable when it computes a new dependency graph: running cargo add mycrate or cargo update will not select it, and a requirement such as mycrate = "1" falls back to the newest unyanked 1.x release. The effect shows when you point a fresh project at a range that used to resolve to the bad version:

Shell
cargo yank --version 1.4.0 mycrate
cargo yank --version 1.4.0 --undo mycrate

Run from the crate's directory, the crate name may be omitted. The second command reverses the first, so a yank placed by mistake costs nothing. Only an owner of the crate can run either one, using a token with the yank scope.

The exception is the lockfile. A project that already recorded mycrate 1.4.0 in Cargo.lock keeps resolving to it, and Cargo still downloads it from the registry, because yanking would otherwise break every existing build the moment a maintainer clicked a button. That promise is the design: yank steers future choices without breaking past ones. It means a yank is slow medicine. Applications that have the lockfile committed stay on the bad version until somebody runs cargo update -p mycrate, and many never will unless something tells them to.

Why you cannot delete

crates.io keeps every published version so that a build from five years ago still works, which is the whole point of an immutable registry. If a maintainer could delete left-pad-style, thousands of lockfiles would break in an afternoon, and the Rust community has treated that as unacceptable since the registry's design. The practical consequence is that nothing you publish can be un-published, so the checks in publishing a crate step by step deserve their time. In rare legal or abuse cases, the crates.io team can remove content, but that is a request to them, not a command you run.

Case one: the release fails to build or panics

You published 1.4.0 with a typo in a cfg that makes the crate fail to compile on Windows. The sequence is mechanical:

Shell
cargo yank --version 1.4.0
# fix the bug on the main branch
cargo publish --dry-run
cargo publish   # 1.4.1

Yank first so nobody new picks up the bad version while you fix it, then publish the fix as a patch. Do not republish the same number; the registry refuses and you would not want it to succeed. Write the cause and the affected versions into the changelog, because a reader who sees 1.4.0 listed as yanked wants to know why. A common mistake is to yank 1.4.0 and publish nothing, leaving the latest unyanked version at 1.3.9 with a requirement of "^1.4" in downstream crates that now cannot resolve at all.

Case two: a semver mistake

You removed a public function in 1.5.0, a breaking change that should have been 2.0.0. Downstream crates written for 1.4 fail to compile after cargo update. Yanking 1.5.0 repairs the resolution for everyone not yet locked to it, and then you republish the change correctly as 2.0.0 with the removal, and re-release 1.5.1 with the function restored if it is still wanted. The yank is the correct tool here, and the reason it is correct is the rule in what counts as a breaking change: the version number is a promise, and a release that broke it should disappear from resolution. A tool such as cargo semver-checks catches this before the publish, which is much cheaper than a yank.

Case three: a leaked secret

You published a crate that contained an .env file, or a test fixture with a real API token. Yanking does not help. The archive remains downloadable by anyone who knows the URL, and docs.rs may have rendered it, and mirrors and crawlers copy new releases within minutes. Treat the secret as public from the moment of publish:

Text
1. Revoke or rotate the credential at the provider. Do this first.
2. Yank the version so new projects do not pull it.
3. Publish a clean patch release with the file excluded.
4. Check provider logs for use of the credential after the publish time.

Prevention lives in exclude and in reading cargo package --list before every release, as described in crate metadata and documentation. A secret scanner in CI on the packaged output is cheap insurance.

Telling users it happened

Because a yank does not reach people who already depend on the version, you have to tell them. For ordinary bugs, a note in the changelog and the patched release are enough: users who run cargo update get the fix. For a vulnerability, file an advisory with the RustSec database, which feeds cargo audit and cargo deny in your users' CI. A single advisory file marking 1.4.0 as affected and 1.4.1 as patched produces a warning in every downstream pipeline, which is the only channel that actually reaches applications with committed lockfiles. See cargo audit and deny for how users consume it. For other unsound or unmaintained cases, the advisory database also has categories that do not imply an exploit, and an honest "unmaintained" notice is often the kindest thing a maintainer can publish.

Checking what users will see

After a yank, verify the effect from the user's side instead of trusting the command. In a scratch project, add the crate with the affected requirement and look at what resolves:

Shell
cargo new check-yank --vcs none && cd check-yank
cargo add mycrate@1
cargo tree -p mycrate

If the tree shows the fixed 1.4.1 and not the yanked 1.4.0, new projects are protected. Then try the case that matters for existing users, copying a lockfile that pins 1.4.0 and running cargo build --locked: it still succeeds, which confirms nothing broke for them and also confirms they are still running the bad code. That gap is the reason to publish the fix and the advisory in the same hour as the yank.

When yank is the wrong move

Do not yank a version only because it is old, or because you dislike its API. Yank breaks resolution for new users of any requirement that only that version satisfied, so it should be reserved for versions that are harmful to use. Deprecating the API with #[deprecated] and documenting the migration respects users better. Do not yank to force an upgrade either: lockfile owners are not affected, and everyone else is annoyed. And if the problem is that the whole crate is abandoned, yanking every version is destructive; mark it unmaintained in the README and the advisory database, or transfer ownership.

Also inSecurity
Taggedyank
Share

Keep reading

Flat illustration for the post “Request Limits and Slow Client Attacks in Rust Services”, composed around web.
Security

Request Limits and Slow Client Attacks in Rust Services

Slow clients and oversized requests exhaust connections and memory. Set header and body timeouts, body size limits, concurrency limits and keep-alive bounds at the proxy and in the Axum service with Tower layers.

5 min read

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.