Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dependency Management and Supply Chain Security 🟒

What you’ll learn:

  • Scanning for known vulnerabilities with cargo-audit
  • Enforcing license, advisory, and source policies with cargo-deny
  • Supply chain trust verification with Mozilla’s cargo-vet
  • Tracking outdated dependencies and detecting breaking API changes
  • Visualizing and deduplicating your dependency tree

Cross-references: Release Profiles β€” cargo-udeps trims unused dependencies found here Β· CI/CD Pipeline β€” audit and deny jobs in the pipeline Β· Build Scripts β€” build-dependencies are part of your supply chain too

A Rust binary doesn’t just contain your code β€” it contains every transitive dependency in your Cargo.lock. A vulnerability, license violation, or malicious crate anywhere in that tree becomes your problem. This chapter covers the tools that make dependency management auditable and automated.

cargo-audit β€” Known Vulnerability Scanning

cargo-audit checks your Cargo.lock against the RustSec Advisory Database, which tracks known vulnerabilities in published crates.

# Install
cargo install cargo-audit

# Scan for known vulnerabilities
cargo audit

# Output:
# Crate:     chrono
# Version:   0.4.19
# Title:     Potential segfault in localtime_r invocations
# Date:      2020-11-10
# ID:        RUSTSEC-2020-0159
# URL:       https://rustsec.org/advisories/RUSTSEC-2020-0159
# Solution:  Upgrade to >= 0.4.20

# Check and fail CI if vulnerabilities exist
cargo audit --deny warnings

# Generate JSON output for automated processing
cargo audit --json

# Fix vulnerabilities by updating Cargo.lock
cargo audit fix

CI integration:

# .github/workflows/audit.yml
name: Security Audit
on:
  schedule:
    - cron: '0 0 * * *'  # Daily check β€” advisories appear continuously
  push:
    paths: ['Cargo.lock']

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: rustsec/audit-check@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

cargo-deny β€” Comprehensive Policy Enforcement

cargo-deny goes far beyond vulnerability scanning. It enforces policies across four dimensions:

  1. Advisories β€” known vulnerabilities (like cargo-audit)
  2. Licenses β€” allowed/denied license list
  3. Bans β€” forbidden crates or duplicate versions
  4. Sources β€” allowed registries and git sources
# Install
cargo install cargo-deny

# Initialize configuration
cargo deny init
# Creates deny.toml with documented defaults

# Run all checks
cargo deny check

# Run specific checks
cargo deny check advisories
cargo deny check licenses
cargo deny check bans
cargo deny check sources

Example deny.toml:

# deny.toml

[advisories]
vulnerability = "deny"        # Fail on known vulnerabilities
unmaintained = "warn"         # Warn on unmaintained crates
yanked = "deny"               # Fail on yanked crates
notice = "warn"               # Warn on informational advisories

[licenses]
unlicensed = "deny"           # All crates must have a license
allow = [
    "MIT",
    "Apache-2.0",
    "BSD-2-Clause",
    "BSD-3-Clause",
    "ISC",
    "Unicode-DFS-2016",
]
copyleft = "deny"             # No GPL/LGPL/AGPL in this project
default = "deny"              # Deny anything not explicitly allowed

[bans]
multiple-versions = "warn"    # Warn if same crate appears at 2 versions
wildcards = "deny"            # No path = "*" in dependencies
highlight = "all"             # Show all duplicates, not just first

# Ban specific problematic crates
deny = [
    # openssl-sys pulls in C OpenSSL β€” prefer rustls
    { name = "openssl-sys", wrappers = ["native-tls"] },
]

# Allow specific duplicate versions (when unavoidable)
[[bans.skip]]
name = "syn"
version = "1.0"               # syn 1.x and 2.x often coexist

[sources]
unknown-registry = "deny"     # Only allow crates.io
unknown-git = "deny"          # No random git dependencies
allow-registry = ["https://github.com/rust-lang/crates.io-index"]

License enforcement is particularly valuable for commercial projects:

# Check which licenses are in your dependency tree
cargo deny list

# Output:
# MIT          β€” 127 crates
# Apache-2.0   β€” 89 crates
# BSD-3-Clause β€” 12 crates
# MPL-2.0      β€” 3 crates   ← might need legal review
# Unicode-DFS  β€” 1 crate

cargo-vet β€” Supply Chain Trust Verification

cargo-vet (from Mozilla) addresses a different question: not β€œdoes this crate have known bugs?” but β€œhas a trusted human actually reviewed this code?”

# Install
cargo install cargo-vet

# Initialize (creates supply-chain/ directory)
cargo vet init

# Check which crates need review
cargo vet

# After reviewing a crate, certify it:
cargo vet certify serde 1.0.203
# Records that you've audited serde 1.0.203 for your criteria

# Import audits from trusted organizations
cargo vet import mozilla
cargo vet import google
cargo vet import bytecode-alliance

How it works:

supply-chain/
β”œβ”€β”€ audits.toml       ← Your team's audit certifications
β”œβ”€β”€ config.toml       ← Trust configuration and criteria
└── imports.lock      ← Pinned imports from other organizations

cargo-vet is most valuable for organizations with strict supply-chain requirements (government, finance, infrastructure). For most teams, cargo-deny provides sufficient protection.

cargo-outdated and cargo-semver-checks

cargo-outdated β€” find dependencies that have newer versions:

cargo install cargo-outdated

cargo outdated --workspace
# Output:
# Name        Project  Compat  Latest   Kind
# serde       1.0.193  1.0.203 1.0.203  Normal
# regex       1.9.6    1.10.4  1.10.4   Normal
# thiserror   1.0.50   1.0.61  2.0.3    Normal  ← major version available

cargo-semver-checks β€” detect breaking API changes before publishing. Essential for library crates:

cargo install cargo-semver-checks

# Check if your changes are semver-compatible
cargo semver-checks

# Output:
# βœ— Function `parse_gpu_csv` is now private (was public)
#   β†’ This is a BREAKING change. Bump MAJOR version.
#
# βœ— Struct `GpuInfo` has a new required field `power_limit_w`
#   β†’ This is a BREAKING change. Bump MAJOR version.
#
# βœ“ Function `parse_gpu_csv_v2` was added (non-breaking)

cargo-tree β€” Dependency Visualization and Deduplication

cargo tree is built into Cargo (no installation needed) and is invaluable for understanding your dependency graph:

# Full dependency tree
cargo tree

# Find why a specific crate is included
cargo tree --invert --package openssl-sys
# Shows all paths from your crate to openssl-sys

# Find duplicate versions
cargo tree --duplicates
# Output:
# syn v1.0.109
# └── serde_derive v1.0.193
#
# syn v2.0.48
# β”œβ”€β”€ thiserror-impl v1.0.56
# └── tokio-macros v2.2.0

# Show only direct dependencies
cargo tree --depth 1

# Show dependency features
cargo tree --format "{p} {f}"

# Count total dependencies
cargo tree | wc -l

Deduplication strategy: When cargo tree --duplicates shows the same crate at two major versions, check if you can update the dependency chain to unify them. Each duplicate adds compile time and binary size.

Application: Multi-Crate Dependency Hygiene

The the workspace uses [workspace.dependencies] for centralized version management β€” an excellent practice. Combined with cargo tree --duplicates for size analysis, this prevents version drift and reduces binary bloat:

# Root Cargo.toml β€” all versions pinned in one place
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
regex = "1.10"
thiserror = "1.0"
anyhow = "1.0"
rayon = "1.8"

Recommended additions for the project:

# Add to CI pipeline:
cargo deny init              # One-time setup
cargo deny check             # Every PR β€” licenses, advisories, bans
cargo audit --deny warnings  # Every push β€” vulnerability scanning
cargo outdated --workspace   # Weekly β€” track available updates

Recommended deny.toml for the project:

[advisories]
vulnerability = "deny"
yanked = "deny"

[licenses]
allow = ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC", "Unicode-DFS-2016"]
copyleft = "deny"     # Hardware diagnostics tool β€” no copyleft

[bans]
multiple-versions = "warn"   # Track duplicates, don't block yet
wildcards = "deny"

[sources]
unknown-registry = "deny"
unknown-git = "deny"

Supply Chain Audit Pipeline

flowchart LR
    PR["Pull Request"] --> AUDIT["cargo audit\nKnown CVEs"]
    AUDIT --> DENY["cargo deny check\nLicenses + Bans + Sources"]
    DENY --> OUTDATED["cargo outdated\nWeekly schedule"]
    OUTDATED --> SEMVER["cargo semver-checks\nLibrary crates only"]
    
    AUDIT -->|"Fail"| BLOCK["❌ Block merge"]
    DENY -->|"Fail"| BLOCK
    SEMVER -->|"Breaking change"| BUMP["Bump major version"]
    
    style BLOCK fill:#ff6b6b,color:#000
    style BUMP fill:#ffd43b,color:#000
    style PR fill:#e3f2fd,color:#000

πŸ‹οΈ Exercises

🟒 Exercise 1: Audit Your Dependencies

Run cargo audit and cargo deny init && cargo deny check on any Rust project. How many advisories are found? How many license categories are in your tree?

Solution
cargo audit
# Note any advisories β€” often chrono, time, or older crates

cargo deny init
cargo deny list
# Shows license breakdown: MIT (N), Apache-2.0 (N), etc.

cargo deny check
# Shows full audit across all four dimensions

🟑 Exercise 2: Find and Eliminate Duplicate Dependencies

Run cargo tree --duplicates on a workspace. Find a crate that appears at two versions. Can you update Cargo.toml to unify them? Measure the compile-time and binary-size impact.

Solution
cargo tree --duplicates
# Typical: syn 1.x and syn 2.x

# Find who pulls in the old version:
cargo tree --invert --package syn@1.0.109
# Output: serde_derive 1.0.xxx -> syn 1.0.109

# Check if a newer serde_derive uses syn 2.x:
cargo update -p serde_derive
cargo tree --duplicates
# If syn 1.x is gone, you've eliminated a duplicate

# Measure impact:
time cargo build --release  # Before and after
cargo bloat --release --crates | head -20

Key Takeaways

  • cargo audit catches known CVEs β€” run it on every push and on a daily schedule
  • cargo deny enforces four policy dimensions: advisories, licenses, bans, and sources
  • Use [workspace.dependencies] to centralize version management across a multi-crate workspace
  • cargo tree --duplicates reveals bloat; each duplicate adds compile time and binary size
  • cargo-vet is for high-security environments; cargo-deny is sufficient for most teams