Rust Engineering Practices

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 Profilescargo-udeps trims unused dependencies found here · CI/CD Pipeline — audit and deny jobs in the pipeline · Build Scriptsbuild-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.

# Installcargo install cargo-audit# Scan for known vulnerabilitiescargo 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 existcargo audit --deny warnings# Generate JSON output for automated processingcargo audit --json# Fix vulnerabilities by updating Cargo.lockcargo audit fix

CI integration:

# .github/workflows/audit.ymlname: Security Auditon:  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
# Installcargo install cargo-deny# Initialize configurationcargo deny init# Creates deny.toml with documented defaults# Run all checkscargo deny check# Run specific checkscargo deny check advisoriescargo deny check licensescargo deny check banscargo deny check sources

Example deny.toml:

# deny.toml[advisories]vulnerability = "deny"        # Fail on known vulnerabilitiesunmaintained = "warn"         # Warn on unmaintained cratesyanked = "deny"               # Fail on yanked cratesnotice = "warn"               # Warn on informational advisories[licenses]unlicensed = "deny"           # All crates must have a licenseallow = [    "MIT",    "Apache-2.0",    "BSD-2-Clause",    "BSD-3-Clause",    "ISC",    "Unicode-DFS-2016",]copyleft = "deny"             # No GPL/LGPL/AGPL in this projectdefault = "deny"              # Deny anything not explicitly allowed[bans]multiple-versions = "warn"    # Warn if same crate appears at 2 versionswildcards = "deny"            # No path = "*" in dependencieshighlight = "all"             # Show all duplicates, not just first# Ban specific problematic cratesdeny = [    # 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.iounknown-git = "deny"          # No random git dependenciesallow-registry = ["https://github.com/rust-lang/crates.io-index"]

License enforcement is particularly valuable for commercial projects:

# Check which licenses are in your dependency treecargo 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?"

# Installcargo install cargo-vet# Initialize (creates supply-chain/ directory)cargo vet init# Check which crates need reviewcargo 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 organizationscargo vet import mozillacargo vet import googlecargo 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-outdatedcargo 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-compatiblecargo 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 treecargo tree# Find why a specific crate is includedcargo tree --invert --package openssl-sys# Shows all paths from your crate to openssl-sys# Find duplicate versionscargo 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 dependenciescargo tree --depth 1# Show dependency featurescargo tree --format "{p} {f}"# Count total dependenciescargo 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 setupcargo deny check             # Every PR — licenses, advisories, banscargo audit --deny warnings  # Every push — vulnerability scanningcargo 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 yetwildcards = "deny"[sources]unknown-registry = "deny"unknown-git = "deny"

Supply Chain Audit Pipeline

🏋️ 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 cratescargo deny initcargo 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_derivecargo tree --duplicates# If syn 1.x is gone, you've eliminated a duplicate# Measure impact:time cargo build --release  # Before and aftercargo 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