The question I get asked most often by teams adopting Rust in regulated industries is some version of "do we run cargo-audit, cargo-deny, or both?" It sounds like a simple tooling question but it isn't. The two tools overlap, diverge, and sometimes give contradictory answers about the same dependency tree. I have been running both against a thirty-eight crate workspace for a payments product since late 2023, and the conclusion I landed on is different from what I expected when I started.
The Surface-Level Comparison
On the tin, cargo-audit and cargo-deny look like siblings. Both consume Cargo.lock. Both know about the RustSec Advisory Database. Both exit non-zero on findings, which makes them drop-in CI gates. cargo-audit 0.21, released in January 2024, ships as a single-purpose binary that does one thing: walks your lockfile, matches package names and versions against advisories in rustsec/advisory-db, and prints what it finds. cargo-deny 0.16, released in March 2024, does that too but also handles license policy, banned crates, duplicate version detection, and source registry allowlists.
If you stop reading here you would reasonably conclude that cargo-deny is the superset and cargo-audit is redundant. That was my assumption going in. It is not quite right.
Where cargo-audit Still Earns Its Keep
The RustSec Advisory Database is updated more or less continuously. cargo-audit 0.21 fetches the database at runtime by default and checks every lockfile entry against the latest snapshot. It also has a fix subcommand that will attempt minimum-version bumps for flagged advisories, which cargo-deny does not offer.
A concrete example from February 2024: RUSTSEC-2024-0019, the mio 0.8.10 advisory about unsoundness on Windows when a socket was dropped during an overlapped I/O operation, landed on a Thursday afternoon. My cargo-audit job in GitHub Actions caught it on the next PR build Friday morning. cargo-deny, configured with our pinned advisory-db snapshot for reproducibility, did not catch it until we bumped the snapshot two weeks later. The lesson is not that cargo-deny is slow. The lesson is that the two tools have different default postures around freshness, and for an early-warning system you want the latest advisories even at the cost of some nondeterminism.
cargo-audit also has a much smaller footprint. The binary is under 15 MB and the CI step typically runs in under 20 seconds on a modest runner. If you just want to know "are any of my dependencies known-vulnerable today," it is the right tool.
Where cargo-deny Does Things cargo-audit Cannot
License compliance is where cargo-deny pulls ahead and nothing in the cargo-audit world replicates it. My deny.toml declares an explicit allowlist of SPDX identifiers: MIT, Apache-2.0, Apache-2.0 WITH LLVM-exception, BSD-2-Clause, BSD-3-Clause, ISC, Unicode-DFS-2016, and CC0-1.0. Anything outside that set fails the build. When we pulled in ring 0.17.8, cargo-deny flagged its BoringSSL-derived ISC+OpenSSL-style license text; I had to either update the allowlist with an explicit exception or replace the dependency. cargo-audit would have said nothing.
The sources section is the other feature I would not give up. We pin our allowed registries to crates.io and one internal Artifactory-hosted registry. Any crate pulled from a random git URL fails the build. This was load-bearing in October 2023 when a contractor opened a PR that added a dependency from a personal GitHub repo; cargo-deny blocked the merge. That pattern, a git-sourced dependency that bypasses the registry's review process, is the exact shape of supply chain attack I worry about most.
Duplicate version detection is underrated too. The multiple-versions check found that we were compiling three different versions of syn (1.0.109, 2.0.48, and 2.0.52) in the same tree, which was mostly cosmetic but also meant an advisory against syn 2.0.48 would have been harder to audit. Pinning to a single version per major reduced our dependency-audit surface by roughly 11 percent.
Where They Actively Disagree
In March 2024 I hit a case where the two tools produced opposite verdicts on atty 0.2.14 and RUSTSEC-2021-0145. cargo-audit flagged it as a vulnerability. cargo-deny, with the project's ignore list populated from a previous VEX assessment, did not. Neither was wrong. The advisory is real but unreachable in our binary because we never call atty::is in a context where the described race can occur. cargo-deny's ignore list with justification strings is the right primitive for expressing "we know, we looked, it does not apply." cargo-audit has --ignore but it is a flag, not a file, and gets stale fast.
If you run only one tool, you either get noisy advisories you cannot structurally acknowledge, or you get silence about new issues because you pinned the database. Running both with cargo-deny holding the justified-exceptions list and cargo-audit running against live advisory data is how I reconciled this.
Performance in CI
On the workspace I benchmark against (174 crates in the dependency graph after feature resolution), cargo-audit 0.21 completes in 18-22 seconds cold, under 10 seconds with advisory-db cache warm. cargo-deny 0.16 takes 45-65 seconds because it runs every check (advisories, bans, licenses, sources) and fetches license text for any crate missing an SPDX expression. Splitting cargo-deny into parallel jobs per check category cut wall time to about 25 seconds at the cost of more runner minutes.
For pre-commit hooks I run cargo-audit only. For PR gates I run both. For release gates I run cargo-deny with a frozen advisory-db commit so the release is reproducible, then cargo-audit against live advisories as a non-blocking warning.
The Recommendation
Run both. They are not redundant. cargo-audit answers "are there new advisories against my pinned dependencies today." cargo-deny answers "does my dependency tree conform to the policy I wrote down." The first question is about freshness. The second is about policy. A mature supply chain program needs both.
If budget or CI minutes force a choice, cargo-deny gives you the larger security surface because license and source-registry violations are how real supply chain incidents start. The advisories cargo-audit catches are important, but they are a known-knowns problem. Policy violations are a known-unknowns problem, and unknown unknowns are where incidents hide.
How Safeguard Helps
Safeguard ingests your Cargo.lock and runs the equivalent of cargo-audit's advisory check and cargo-deny's license and source policy checks in a single pass, with VEX-style justifications preserved across scans so you are not re-litigating the same atty or chrono advisory every Monday. You get the freshness of live advisory data and the reproducibility of pinned policy snapshots without maintaining two tools and two configs. When we flag a RUSTSEC advisory, the finding includes reachability context so your team can decide whether it is exploitable in your binary rather than just present in your graph. That is the difference between a CI gate that gets muted and one that teams actually act on.