The dependency graph is a minefield
You just merged a feature that uses a new crate. It compiles. Tests pass. You ship it. Three weeks later, a security advisory drops for a transitive dependency you didn't even know existed. Or your legal team flags a license violation deep in the dependency tree. The fix is a hotfix, but the damage is done. You need a way to audit your entire dependency graph before it reaches production, not after.
cargo deny is the tool for this job. It audits your dependencies for security vulnerabilities, license compliance, banned crates, and source restrictions. It checks everything, including the crates your crates depend on. You configure it once, and it guards your project against bad dependencies forever.
How it works
Your Rust project is a supply chain. You pick a crate, and that crate pulls in three others, which pull in five more. You can see your direct dependencies, but the transitive ones hide in the dark. cargo deny acts as a customs inspector for your dependency graph. It walks every branch, checks every crate against security databases, verifies licenses, and enforces bans. It catches the bad actors hiding three levels deep before they make it into your binary.
The tool reads your Cargo.lock file. It doesn't just look at Cargo.toml. The lock file contains the exact versions of every crate, including transitive dependencies. cargo deny builds a complete graph of your project. It then fetches the latest advisory database from the RustSec project. It walks the graph, matching every crate against known vulnerabilities. It also parses license files and metadata to verify compliance. If a crate is banned, has a vulnerability, or uses a forbidden license, the check fails. The tool treats violations as errors by default. This means your CI pipeline will block the merge if something is wrong.
Don't wait for the CVE. Audit before you ship.
Getting started
Install the tool globally. You only need to do this once per machine.
# Install the cargo-deny binary.
# This adds the `cargo deny` subcommand to your Cargo installation.
cargo install cargo-deny
Initialize a configuration file in your project root. This creates a deny.toml with sensible defaults.
# Generate a default deny.toml file.
# This file lives in your project root alongside Cargo.toml.
cargo deny init
Run the audit locally. This checks advisories, bans, licenses, and sources. It exits with a non-zero code if any violations are found.
# Run all checks defined in deny.toml.
# This reads Cargo.lock and the advisory database.
# It fails the command if any policy is violated.
cargo deny check
Run it locally. Make it part of your muscle memory.
Configuration in practice
The deny.toml file defines your policy. You can ban specific crates, allow only certain licenses, and restrict which registries your dependencies come from. Here is a realistic configuration that covers most projects.
# Define which platforms you care about.
# cargo deny only checks dependencies relevant to these targets.
# This prevents false positives for crates that only exist on other OSes.
[graph]
targets = [
{ triple = "x86_64-unknown-linux-gnu" },
{ triple = "x86_64-pc-windows-msvc" },
]
# Configure security advisory checks.
# version = 2 is the current schema for the advisory database.
# ignore = [] means no advisories are suppressed.
# Add IDs here only if you have verified a false positive.
[advisories]
version = 2
ignore = []
# Ban specific crates or enforce version constraints.
# multiple-versions = "warn" alerts you to duplicate crate versions.
# This helps reduce binary size and maintenance burden.
# wildcards = "warn" flags wildcard versions in Cargo.toml.
[bans]
multiple-versions = "warn"
wildcards = "warn"
highlight = "all"
# Explicitly deny a crate version due to a known issue.
# This overrides any other rules and blocks the crate entirely.
# Use this for crates with critical vulnerabilities or malicious behavior.
[[bans.deny]]
name = "rand"
version = "=0.8.0"
reason = "Known vulnerability in this specific version"
# Enforce license compliance.
# unlicensed = "deny" blocks crates without a clear license.
# confidence-threshold = 0.8 requires high certainty for license detection.
# allow = [...] lists the licenses your project accepts.
[licenses]
unlicensed = "deny"
confidence-threshold = 0.8
allow = [
"MIT",
"Apache-2.0",
"BSD-3-Clause",
]
# Restrict where dependencies can come from.
# This prevents typosquatting attacks from malicious registries.
# Only allow crates.io and your private registry if you have one.
[sources]
unknown-registry = "deny"
unknown-git = "warn"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
Community convention: set multiple-versions = "warn" initially. The Rust ecosystem has many duplicate crates. Denying multiple versions immediately will likely break your build. Start with warnings, then clean up duplicates over time. Also, keep wildcards = "warn". Wildcards in Cargo.toml can lead to unexpected updates. Pin your versions or use ranges with care.
Treat deny.toml as policy. If it's not in the file, it's not allowed.
Common pitfalls
If you run cargo deny check without a Cargo.lock file, the tool fails. It needs the lock file to know the exact dependency graph. Run cargo generate-lockfile first.
The advisory database updates periodically. If your local cache is stale, you might miss a new vulnerability. Run cargo deny check --update to refresh the database. You can also set up a cron job to update the database regularly.
License detection isn't perfect. Some crates have custom license texts that the tool can't parse. You might see a "unlicensed" error for a crate that actually has a license. You can add exceptions in the config, but verify manually first. Check the crate's repository for a LICENSE file. If the license is valid, add an exception in deny.toml with a comment explaining why.
The tool exits with code 1 on failure. This is intentional. It allows CI pipelines to catch the error automatically. Don't suppress this exit code. If the check fails, the build should fail.
Update the database. Stale checks give false confidence.
Integrating with CI
Add cargo deny to your CI pipeline. This ensures every merge request passes the audit. Here is an example for GitHub Actions.
- name: Check dependencies
uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check
# Optional: fail only on specific categories.
# Use this if you want to warn on licenses but fail on advisories.
# args: advisories bans
The action installs cargo deny and runs the check. It fails the step if any violations are found. You can also generate reports for review.
# Output results to JSON for programmatic analysis.
# This is useful for integrating with other tools.
cargo deny check --format json --output report.json
# Output results to HTML for human review.
# This generates a readable report of all violations.
cargo deny check --format html --output report.html
Automate the gate. Let the tool block the bad merges.
When to use cargo deny
Use cargo deny when you need a comprehensive audit of security, licenses, and bans in one tool. Use cargo deny for CI/CD pipelines where you want to block merges on policy violations. Use cargo deny when you need to enforce source restrictions to prevent typosquatting attacks. Use cargo-geiger when you specifically want to find unsafe code in dependencies, as cargo deny focuses on advisories and licenses rather than code safety. Use manual review when you are evaluating a new crate for the first time and need to understand its purpose and quality beyond automated checks. Reach for cargo deny over cargo audit because cargo audit is older and less feature-rich. cargo deny supports bans, sources, and more granular configuration.
Automate the boring stuff. Let the tool guard the gate.