Rust crate security splits into two distinct problems: detecting dependencies with known vulnerabilities, which cargo audit solves by checking Cargo.lock against the RustSec advisory database, and establishing trust in dependencies nobody has reviewed, which cargo vet solves by tracking human audits. Most teams run the first and skip the second, which means they'll catch last year's CVE but not this week's malicious crate. And neither tool addresses the sharpest edge in the ecosystem: build.rs and proc macros execute arbitrary code on your build machine before your tests ever run.
cargo audit: the ten-minute win
Install it pinned, because your security tooling should not itself float:
cargo install cargo-audit --locked
cargo audit
It parses Cargo.lock, matches against rustsec/advisory-db (roughly 700 advisories and growing), and exits non-zero on hits. Advisories carry more structure than most ecosystems get: affected version ranges, patched versions, and often the specific functions involved. cargo audit fix can bump patched versions for you, though I'd treat that as a suggestion generator rather than an auto-merge candidate.
Two flags worth knowing. cargo audit --deny warnings also fails on unmaintained-crate and yanked-version warnings, not just vulnerabilities — stricter than most teams want on day one, right for most teams by month three. And cargo audit bin inspects compiled binaries that were built with cargo auditable, which embeds the dependency list in a dedicated linker section. That last trick is underrated: it means your SBOM travels inside the artifact, and tools like syft can recover the full dependency tree from a bare binary years later. If you're standardizing SBOM handling across ecosystems, SBOM Studio can ingest those alongside your CycloneDX documents.
If you're weighing this against cargo-deny, we've done a head-to-head comparison — short version: cargo-deny adds license policy, banned-crate lists, and source allowlists, and the two coexist fine.
cargo vet: trust as a shared ledger
cargo audit answers "is this version known bad?" cargo vet answers a harder question: "has a human I trust read this code?"
cargo install cargo-vet --locked
cargo vet init
cargo vet
After init, every existing dependency is exempted (recorded in supply-chain/config.toml), and the tool fails when a new or updated crate arrives unvetted. You discharge the failure one of three ways: audit it yourself (cargo vet certify), record a diff audit against a previously-audited version — usually a much smaller read — or import someone else's audits:
# supply-chain/config.toml
[imports.mozilla]
url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml"
[imports.google]
url = "https://raw.githubusercontent.com/google/supply-chain/main/audits.toml"
Mozilla and Google publish their audit sets publicly, and between them they cover a large slice of the popular-crate long tail. In a workspace I help maintain, importing both cut our unvetted surface from 212 crates to 61 on day one. The remaining 61 were mostly small transitive utilities — exactly the crates nobody would otherwise ever read, which is rather the point.
The cultural shift matters more than the tooling: cargo vet turns "someone should probably look at new dependencies" into a merge-blocking artifact with named auditors.
build.rs and proc macros: code execution at compile time
A crate with a build.rs runs arbitrary Rust on your machine during cargo build. Proc-macro crates run arbitrary code inside the compiler process. No sandbox, full network access, your CI credentials in the environment. The 2022 rustdecimal incident — a typosquat of rust_decimal that pulled a payload from a remote server when built on CI — used exactly this.
Practical mitigations, in ascending order of effort:
cargo tree --format "package.name"piped through a check for how many of your dependencies even have build scripts; you'll be surprised.- Run builds in ephemeral, network-restricted containers. If
build.rscan't reach the internet, most first-stage payloads die quietly. - Scope CI tokens so a compromised build can't push, publish, or read unrelated secrets.
- For the genuinely paranoid: separate the
cargo fetchstep (network on) fromcargo build --offline(network off).
Number 2 is the best effort-to-value ratio. It's a CI config change, not a workflow change.
crates.io realities: no namespaces, permanent versions
crates.io has one global flat namespace, so tokio-utils and tokio_utils and tokioutils are all up for grabs by anyone. Typosquatting is the dominant malicious-crate pattern for that reason. Published versions are permanent — yanking hides a version from new resolution but never deletes it, so a Cargo.lock that already references a yanked version keeps building. That's good for reproducibility and means "the author yanked it" is not remediation for a malicious release.
Commit Cargo.lock for binaries and libraries (the old advice to gitignore it for libraries was dropped by the Cargo team in 2023). In CI, build with --locked so a stale or hand-edited lockfile fails loudly instead of being regenerated:
cargo build --locked
cargo test --locked
Lockfile checksums mean any registry-side tampering with an already-locked version fails at download. That, combined with yank-permanence, makes Rust's baseline integrity story better than most ecosystems — the gaps are in new versions and new crates, which is where vet and scanning come in.
Putting it together in CI
A reasonable pipeline, in order:
| Stage | Command | Catches |
|---|---|---|
| Resolution | cargo build --locked | Lockfile drift, tampered downloads |
| Known-bad | cargo audit --deny warnings | RustSec advisories, yanked deps |
| Trust | cargo vet | Unreviewed new/updated crates |
| Policy | cargo deny check | Licenses, banned crates, sources |
Total added CI time on a mid-size workspace: about 40 seconds, dominated by the first cargo vet fetch. Continuous SCA scanning — Safeguard covers Cargo alongside the other ecosystems — adds malware-pattern detection and alerting between builds, which matters because malicious crates get discovered on the registry's schedule, not your merge schedule.
Frequently asked questions
Do I need cargo vet if I already run cargo audit?
They answer different questions. cargo audit only knows about published advisories, so a malicious crate that hasn't been reported yet sails through; cargo vet fails closed on anything no trusted auditor has reviewed. Small teams often start with audit-only and add vet once import lists from Mozilla and Google make the initial burden tolerable.
Is a yanked crate version safe to keep using?
Yanking is a signal, not a removal — the code still downloads fine for existing lockfiles. Check why it was yanked (RustSec advisory, soundness bug, accidental publish) before deciding; treat security yanks as a forced upgrade.
How dangerous are build scripts really?
A build.rs is unsandboxed native code execution with your build environment's credentials and network access, triggered by cargo build — before any test runs. Real attacks (rustdecimal, 2022) have used it as the payload stage, so network-restricted build containers are worth the setup cost.
Should Cargo.lock be committed for library crates?
Yes. The Cargo team reversed the old guidance in 2023: committing the lockfile gives contributors and CI reproducible builds and lets --locked act as a tamper check, while downstream consumers still resolve their own graph from your Cargo.toml ranges.