I have been asked to do a security review of a team's Rust dependency posture twice this year, and both times the conversation drifted to the same place: what does crates.io actually protect you from, and what does it not? The answer is more nuanced than the usual "PyPI had an incident, can crates.io have one too?" framing. The short version is that crates.io's security model is deliberately narrow, and understanding the shape of that narrowness is the difference between a defensible program and a program that assumes a registry is doing work it is not doing.
What crates.io Does Well
The registry itself runs on solid infrastructure. Since August 2022 all crate publishes require a verified email and a GitHub-authenticated account. In November 2023 the team rolled out mandatory two-factor authentication for publishers of crates with more than a threshold of downloads, and by early 2024 this had expanded to cover almost every crate anyone reading this actually depends on. Scoped API tokens, introduced in 2022 and stabilized further in the 2023 quarterly updates, mean a leaked CI token can be limited to a single crate and a single operation class.
Publishes are immutable. Once a version is published, its content hash is fixed and cached by every Cargo client. You cannot replace serde 1.0.195 with new bytes under the same version number. This is a stronger guarantee than npm historically offered and is one of the reasons the left-pad scenario is less likely to replay against the Rust ecosystem.
The yanking mechanism is also well-designed. A yank hides a version from the resolver for new builds but does not delete it. Existing Cargo.lock files continue to resolve. This is the right tradeoff. When RUSTSEC-2024-0003 against h2 0.3.24 was issued in January 2024, the maintainers yanked the affected versions and published 0.3.26 within hours; downstream teams saw the advisory through cargo-audit and could upgrade deliberately rather than being force-migrated mid-incident.
The crates.io Sparse index, stabilized with Cargo 1.70 in June 2023 and made default in 1.74 in November 2023, meaningfully reduced the attack surface for metadata tampering. Before Sparse, Cargo cloned a git repository to learn about the registry; any compromise of that repo would have cascaded. Sparse fetches per-crate JSON over HTTPS directly from static.crates.io, with CDN-level integrity, which narrows the set of things an attacker would need to compromise.
What crates.io Does Not Do
This is where I spend most of the review conversation.
First: name squatting and typosquatting are not prevented. A malicious actor can publish actix-web-middleware-cors and hope someone types the wrong crate name. The registry's policy is reactive. Reports are handled, but you have to notice first. In August 2023 there was an incident where a handful of crates mimicking popular names were pulled after community reporting; the response time was reasonable but the window of exposure was real.
Second: no code review. The registry runs no static analysis on uploaded crates beyond format validation. If I publish a crate whose build.rs exfiltrates environment variables to my server, crates.io will accept it and serve it to whoever declares a dependency. The assumption is that the ecosystem handles this downstream: through reputation, through tools like cargo-geiger and cargo-crev, through your own review. That is a defensible position but it means crates.io is not a filter, it is a distribution channel.
Third: no provenance by default. There is no analog to Sigstore's npm provenance attestations that tie a crate version back to a specific source commit and build runner. This was being actively discussed on the Rust internals forum through 2023 and 2024 but at the time of writing there is no shipped solution. If you want provenance you have to bolt it on yourself, usually by signing releases out of band and publishing the signatures separately.
Fourth: the ownership model is weak against account takeover. If I compromise a maintainer's GitHub account and their 2FA, I can push a new version. The 2024 2FA rollout raised the cost meaningfully, but the threat model where a maintainer's laptop is compromised and their session tokens lifted is still live. The Cargo client does not verify signatures because there are no signatures to verify.
The Risks Teams Underweight
The most underweighted risk I see is transitive dependency drift. A team pins their direct dependencies tightly but leaves the resolver to pick transitive versions. A new version of syn or proc-macro2 lands, gets pulled into the next cargo update, and nobody reviews what changed. The attack surface is not the direct dependencies you chose; it is the hundred and forty transitive crates you have never heard of.
The second underweighted risk is build-time execution. build.rs runs with the full permissions of the developer or CI runner. Proc macros execute during compilation, also with full permissions. A compromised crate does not need to wait until runtime to do damage; it can read ~/.aws/credentials during cargo build. crates.io does not check for this and Cargo does not sandbox it.
The third is the long tail of unmaintained crates. RUSTSEC has a category for advisories that are "unmaintained" rather than "vulnerable," and the list grows every quarter. A crate with no commits since 2021 that is still transitively depended on by half the ecosystem is a latent risk; when a vulnerability is found in it, there may be nobody to issue a fix.
What to Do About It
The pragmatic defense is layered. Use the sparse index and keep Cargo current so you inherit registry improvements. Pin or lockfile-commit aggressively, and review Cargo.lock diffs in PRs the same way you review source code diffs. Run cargo-audit against the live RustSec database and cargo-deny against a policy that includes source allowlists. Treat any dependency that executes code at build time as higher risk and prefer alternatives that do not. Periodically review the dependency graph for unmaintained crates and plan migrations before the advisory lands.
For supply chain provenance specifically, several teams I work with now build their Rust projects inside SLSA-compliant runners and publish attestations to an internal store, keyed by the crate's content hash. It is extra work but it closes the provenance gap crates.io leaves open.
How Safeguard Helps
Safeguard treats crates.io as an untrusted channel by default, which matches what the registry actually guarantees. We track every crate version your projects consume, flag typosquat candidates against the top-downloaded names, monitor for yank events that affect your pinned versions, and run reachability analysis so you know whether a RUSTSEC advisory actually touches your binary or just lives in your graph. We also surface build-script and proc-macro activity as first-class risk signals rather than buried metadata, and can enforce source-registry policy in CI so a surprise git-sourced dependency never reaches main.