If your Rust project has more than a handful of dependencies, at least one of them is probably tracking a known vulnerability right now. Crates get yanked, maintainers disclose flaws, and transitive dependencies quietly drag in code nobody on your team ever reviewed. This cargo-audit tutorial walks through exactly how to find those problems before they ship: installing the tool, scanning against the RustSec advisory database, reading the output, fixing or suppressing findings responsibly, and wiring the whole thing into CI so a vulnerable crate can never merge silently again.
By the end, you'll have a repeatable cargo vulnerability check that runs in seconds locally and automatically on every pull request. This isn't a theoretical exercise -- cargo-audit is the same tool that flagged real-world issues like the time crate's segfault-on-Unix advisory and multiple openssl-related bindings issues years before most teams noticed. Rust's memory safety guarantees don't extend to logic bugs, cryptographic weaknesses, or unsafe blocks buried three dependencies deep, which is exactly the gap this workflow closes.
Step 1: Install cargo-audit and Build the Advisory Database
cargo-audit is a Cargo subcommand maintained by the RustSec project. Install it directly from crates.io:
cargo install cargo-audit --locked
The --locked flag matters here -- it pins cargo-audit's own dependencies to the versions in its lockfile, avoiding a chicken-and-egg problem where installing your vulnerability scanner pulls in an unvetted dependency tree.
On first run, cargo-audit clones the RustSec advisory database, a community-maintained, git-based repository of known vulnerabilities, unmaintained crates, and yanked releases affecting the Rust ecosystem. You can pre-fetch it explicitly:
cargo audit fetch
This creates a local cache (typically under ~/.cargo/advisory-db) that subsequent scans reuse. In CI, you'll want to fetch fresh data on every run rather than caching this directory long-term, since new advisories are published continuously.
Step 2: Run Your First cargo-audit Tutorial Scan
With the database in place, the core command of this cargo-audit tutorial is simple:
cargo audit
This reads your Cargo.lock file -- not Cargo.toml -- and cross-references every resolved dependency version against the advisory database. That distinction matters: cargo-audit checks what's actually compiled into your binary, including transitive dependencies you never listed directly. A clean run looks like this:
Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
Loaded 720 security advisories
Scanning Cargo.lock for vulnerabilities (142 crate dependencies)
If something's flagged, you'll see a structured block per advisory with an ID (e.g., RUSTSEC-2023-0044), a title, the affected version range, and the patched version. This is your primary rust dependency scanning signal -- run it before every release, and ideally on every commit.
Step 3: Read and Triage RustSec Advisory Output
Not every advisory demands the same urgency. cargo-audit output includes a severity indicator and advisory type, and RustSec categorizes issues into a few buckets worth understanding:
- Vulnerabilities -- actual security flaws with CVE or RUSTSEC identifiers, ranked by CVSS score when available.
- Unmaintained crates -- the maintainer has archived or abandoned the project. Not a vulnerability yet, but a supply-chain risk that compounds over time.
- Unsound -- the crate exposes a safe API that can trigger undefined behavior under certain inputs, a Rust-specific category that's easy to miss with generic scanners.
Triage by asking three questions: Is this crate in your actual runtime path or only a dev/test dependency? Is the vulnerable code path reachable from your usage of the crate? Is a patched version available? cargo-audit answers the first two only partially -- it flags dependency graph position but not call-graph reachability -- so treat every hit as "investigate," not "ignore automatically."
Step 4: Fix or Suppress Vulnerable Dependencies
For most findings, the fix is a version bump. cargo-audit ships a fix subcommand (via the cargo-audit-fix feature) that attempts it for you:
cargo audit fix
This edits Cargo.toml to require patched versions and regenerates the lockfile. Always follow it with cargo build and your test suite -- semver-compatible patches occasionally still shift behavior.
When a fix isn't available yet, or the vulnerable path is genuinely unreachable in your code, you can suppress a specific advisory with justification rather than ignoring the tool's exit code entirely. Create .cargo/audit.toml:
[advisories]
ignore = [
"RUSTSEC-2023-0044", # unreachable: only triggered via TLS renegotiation we disable
]
informational_warnings = ["unmaintained"]
[output]
deny = ["warnings"]
Every ignored advisory should have an inline comment explaining why, an owner, and ideally a tracked ticket to revisit it. Suppression without a paper trail is how supply chain debt accumulates invisibly.
Step 5: Automate Rust Dependency Scanning in CI
A local scan only protects the person who remembers to run it. Add cargo-audit as a required check. A minimal GitHub Actions job:
name: cargo vulnerability check
on: [push, pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
The rustsec/audit-check action wraps cargo-audit, caches the advisory database sensibly, and annotates findings directly on the PR diff. If you'd rather invoke the CLI yourself for more control over failure conditions:
- run: cargo install cargo-audit --locked
- run: cargo audit --deny warnings
--deny warnings promotes informational findings like unmaintained crates to build failures, which is a reasonable default for teams that want zero-tolerance policies, and a strict setting you can loosen once your baseline is clean.
Step 6: Schedule Recurring Scans for New Advisories
Your Cargo.lock doesn't change every day, but the RustSec advisory database does. A dependency that was clean on Monday can have a fresh CVE published on Wednesday against the exact version you're running. Add a scheduled workflow independent of your normal push/PR triggers:
on:
schedule:
- cron: '0 6 * * *'
This is the step teams skip most often, and it's the one that actually catches zero-day disclosures in dependencies you haven't touched in months.
Troubleshooting and Verification
"error: Couldn't find advisory database" -- Run cargo audit fetch explicitly, or check that outbound git access to github.com isn't blocked by a corporate proxy or CI network policy.
Stale results despite a patched crate -- cargo-audit reads Cargo.lock, not Cargo.toml. If you bumped a version constraint but haven't run cargo update -p <crate>, the lockfile still points at the old, vulnerable version.
False positive on a dev-only dependency -- By default cargo-audit scans all dependencies including dev-dependencies. If a flagged crate never ships in your release binary, you can scope scans with cargo audit --ignore-source combinations or track it separately rather than suppressing it identically to a production-path finding.
CI passes locally but fails in the pipeline -- Usually a cache issue: the CI runner is using a stale advisory-db snapshot. Ensure the fetch step runs fresh, or explicitly clear the cache directory before each job.
To verify your setup end-to-end, deliberately pin a known-vulnerable version temporarily (check the RustSec advisory database's web index at rustsec.org for an ID with a wide affected range), confirm cargo-audit catches it, then revert. This closes the loop and proves the pipeline actually fails when it should, not just when you expect it to.
How Safeguard Helps
cargo-audit and the RustSec advisory database are excellent building blocks, but they only cover one language ecosystem and one point in the pipeline -- a scan you have to remember to run, interpret, and act on manually. Safeguard extends that same rust dependency scanning discipline across your entire software supply chain: continuous monitoring of dependency graphs across Rust, JavaScript, Python, and Go in one place, automatic correlation of new RustSec and CVE disclosures against what's actually deployed (not just what's in a lockfile), and policy enforcement that blocks builds the moment a suppressed advisory's justification expires or a new critical vulnerability lands in a crate you already ship.
Instead of a nightly cron job and a .cargo/audit.toml file someone has to remember to update, Safeguard gives security and platform teams a single view of every cargo vulnerability check result alongside SBOM data, provenance attestations, and remediation SLAs -- so the fix in Step 4 above becomes a tracked, auditable workflow rather than a comment in a config file. If cargo-audit is how you catch the problem, Safeguard is how you make sure it never goes unnoticed again.