SBOM Generation Guide for Rust - Cargo

Learn how to generate Software Bill of Materials for Rust projects. Complete guide with Cargo.lock examples, workspace dependencies, and CycloneDX output.

Source vs Build SBOMs

Rust has excellent support for reproducible builds through Cargo, its package manager. The Cargo.lock file provides precise dependency information, making source SBOMs highly accurate.

The Rust ecosystem’s security focus means:

  • Crates are immutable once published
  • Cargo.lock contains exact versions and checksums
  • Reproducible builds are a first-class concern

For source SBOMs, you generate from Cargo.lock. For build SBOMs, you analyze the compiled binary (which embeds dependency information in debug builds).

Lockfile Deep Dive

Cargo.lock Structure

The Cargo.lock file contains precise dependency information:

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "actix-web"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e43428f1a9b00f007c853a2adf1e46700a2d81e1d498299a9d5198d417c8e030"
dependencies = [
 "actix-codec",
 "actix-http",
 "actix-router",
]

[[package]]
name = "serde"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
 "serde_derive",
]

Each package entry includes:

  • name: Crate name
  • version: Exact semantic version
  • source: Where the crate comes from (usually crates.io)
  • checksum: SHA-256 hash for integrity verification
  • dependencies: Direct dependencies of this crate

Cargo.toml vs Cargo.lock

File Purpose Commit to VCS?
Cargo.toml Dependency declarations (can use ranges) Always
Cargo.lock Exact resolved versions Yes for binaries/apps, Optional for libraries

Important for SBOMs: Always use Cargo.lock for SBOM generation, as it contains the resolved versions.

Workspace Dependencies

Rust workspaces allow sharing dependencies across multiple crates:

my-project/
├── Cargo.toml       # Workspace root
├── Cargo.lock       # Shared lockfile
├── app/
│   └── Cargo.toml
├── lib-core/
│   └── Cargo.toml
└── lib-utils/
    └── Cargo.toml

Workspace root Cargo.toml:

[workspace]
members = ["app", "lib-core", "lib-utils"]

[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }

Member crate Cargo.toml:

[package]
name = "app"
version = "0.1.0"

[dependencies]
serde = { workspace = true }
tokio = { workspace = true }
lib-core = { path = "../lib-core" }

For workspace SBOMs, generate from the root Cargo.lock which covers all members.

Build vs Runtime Dependencies

Cargo distinguishes between dependency types:

[dependencies]
serde = "1.0"              # Runtime dependency

[dev-dependencies]
mockall = "0.12"           # Test-only dependency

[build-dependencies]
cc = "1.0"                 # Build script dependency

SBOM considerations:

  • [dependencies] - Should always be in your SBOM
  • [dev-dependencies] - Usually excluded from production SBOMs
  • [build-dependencies] - Consider including if build-time supply chain matters

Generating an SBOM

SBOM generation is the first step in the SBOM lifecycle. After generation, you typically need to enrich your SBOM with package metadata and augment it with your organization’s details.

The sbomify GitHub Action is a swiss army knife for SBOMs that automatically selects the best generation tool for your ecosystem, enriches the output with package metadata, and optionally augments it with your business information—all in one step.

For Rust, sbomify uses cdxgen under the hood with fallback to Trivy and Syft.

Standalone (no account needed):

- uses: sbomify/github-action@master
  env:
    LOCK_FILE: Cargo.lock
    OUTPUT_FILE: sbom.cdx.json
    COMPONENT_NAME: my-rust-app
    COMPONENT_VERSION: ${{ github.ref_name }}
    ENRICH: true
    UPLOAD: false

Using github.ref_name automatically captures your git tag (e.g., v1.2.3) as the SBOM version. For rolling releases without tags, use github.sha instead. See our SBOM versioning guide for best practices.

With sbomify platform (adds augmentation and upload):

- uses: sbomify/github-action@master
  env:
    TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
    COMPONENT_ID: my-component-id
    LOCK_FILE: Cargo.lock
    OUTPUT_FILE: sbom.cdx.json
    AUGMENT: true
    ENRICH: true

Alternative Tools

If you prefer to run SBOM generation tools manually:

cargo-sbom (Rust-native):

cargo install cargo-sbom
cargo sbom > sbom.cdx.json

cargo-cyclonedx:

cargo install cargo-cyclonedx
cargo cyclonedx --format json > sbom.cdx.json

cdxgen:

npm install -g @cyclonedx/cdxgen
cdxgen -t rust -o sbom.cdx.json

Trivy:

trivy fs --format cyclonedx --output sbom.cdx.json .

When using these tools directly, you’ll need to handle enrichment and augmentation separately.

GitLab CI

generate-sbom:
  image: sbomifyhub/sbomify-action
  variables:
    LOCK_FILE: Cargo.lock
    OUTPUT_FILE: sbom.cdx.json
    UPLOAD: "false"
    ENRICH: "true"
  script:
    - /sbomify.sh
  artifacts:
    paths:
      - sbom.cdx.json

Handling Special Cases

Git Dependencies

Cargo supports dependencies from Git:

[dependencies]
my-crate = { git = "https://github.com/example/my-crate", rev = "abc123" }

These appear in Cargo.lock with Git-specific information:

[[package]]
name = "my-crate"
version = "0.1.0"
source = "git+https://github.com/example/my-crate?rev=abc123#abc123def456"

SBOM tools should capture the Git commit hash as the version identifier.

Path Dependencies

Local path dependencies won’t have version information from crates.io:

[dependencies]
my-local-crate = { path = "../my-local-crate" }

These are useful for workspaces but require special handling in SBOMs.

Features

Cargo features affect what code is compiled:

[dependencies]
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"], default-features = false }

Features can enable additional dependencies, so your SBOM should reflect the features used.

Vendored Dependencies

For offline builds or additional supply chain control:

cargo vendor

This creates a vendor/ directory. Your .cargo/config.toml:

[source.crates-io]
replace-with = "vendored-sources"

[source.vendored-sources]
directory = "vendor"

Best Practices

  1. Always commit Cargo.lock - Essential for reproducible builds and accurate SBOMs
  2. Use exact versions in production - Pin versions in Cargo.toml for applications
  3. Audit regularly - Use cargo audit alongside SBOM generation
  4. Review dependencies - Use cargo deny for license and security policies
  5. Consider vendoring - For high-security environments
  6. Document unsafe code - Note any crates using unsafe in your SBOM metadata

Security Tooling Integration

Rust’s security ecosystem complements SBOM generation:

# Install cargo-audit
cargo install cargo-audit

# Check for vulnerabilities
cargo audit

# Use RustSec advisory database
cargo audit --db ./advisory-db

Combine cargo audit results with your SBOM for comprehensive vulnerability tracking.

Further Resources

For more SBOM tools and resources, see our SBOM Resources page, which includes additional Rust-specific tools like CycloneDX Rust, sbom-rs, and sbom4rust.