cargo-capsec 0.2.2

Static capability audit for Rust — find out what your code can do to the outside world
Documentation

cargo-capsec

Static capability audit for Rust — find out what your code can do to the outside world.

cargo-capsec scans Rust source code and reports every function that exercises ambient authority: filesystem access, network connections, environment variable reads, process spawning, and FFI calls. No annotations or code changes required.

Installation

cargo install cargo-capsec

Commands

cargo capsec init — Bootstrap for existing codebases

cargo capsec init                          # generate .capsec.toml + baseline
cargo capsec init --ci github              # + GitHub Actions workflow
cargo capsec init --ci gitlab              # + GitLab CI config
cargo capsec init --interactive            # guided setup
cargo capsec init --report                 # show migration priority ranking

Runs a full audit, generates a .capsec.toml with allow rules for all existing findings, saves a baseline, and optionally sets up CI. Adopt in 30 seconds — then catch regressions.

cargo capsec audit — Scan for ambient authority

# Basic scan (workspace crates only)
cargo capsec audit

# Cross-crate propagation (workspace + dependencies)
cargo capsec audit --include-deps

# Full dependency tree analysis
cargo capsec audit --include-deps --dep-depth 0

# MIR-based deep analysis (requires nightly + capsec-driver)
cargo capsec audit --deep --include-deps

# Supply-chain view (only dependency findings)
cargo capsec audit --deps-only

# Output formats
cargo capsec audit --format text           # default, color-coded terminal output
cargo capsec audit --format json           # structured JSON for scripts
cargo capsec audit --format sarif          # SARIF for GitHub Code Scanning

# Filtering
cargo capsec audit --min-risk high         # only high + critical
cargo capsec audit --only my-core,my-sdk   # specific crates
cargo capsec audit --skip my-cli,xtask     # exclude crates

# CI integration
cargo capsec audit --fail-on high --quiet  # exit 1 on high-risk, no output

# Baselines
cargo capsec audit --baseline              # save current findings
cargo capsec audit --diff                  # show changes since baseline
cargo capsec audit --diff --fail-on high   # fail only on NEW high-risk findings

Output example

my-app v0.1.0
─────────────
  FS    src/config.rs:8:5     fs::read_to_string     load_config()
  NET   src/api.rs:15:9       reqwest::get            fetch_data()
        ↳ Cross-crate: reqwest::get() → TcpStream::connect [NET]
  FFI   src/db.rs:31:9        rusqlite::execute       query()
        ↳ Cross-crate: rusqlite::execute() → sqlite3_exec [FFI]
  PROC  src/deploy.rs:42:17   Command::new           run_migration()
  VIA   src/main.rs:5:9       load_config()          main()

Summary
───────
  Crates with findings: 1
  Total findings:       5
  Categories:           FS: 1  NET: 1  ENV: 0  PROC: 1  FFI: 1
  2 critical-risk findings

Analysis modes

Mode Flag What it scans Speed
Workspace only (default) Your code Fast
Cross-crate --include-deps Your code + dependency source (syntactic) Medium
Deep --deep --include-deps Everything via MIR (sees through macros, FFI wrappers) Slow (nightly)

cargo capsec diff — Compare crate versions

cargo capsec diff serde_json@1.0.130 serde_json@1.0.133
cargo capsec diff tokio@1.37.0 tokio@1.38.0 --format json
cargo capsec diff my-dep@0.4.0 my-dep@0.5.0 --fail-on-new

Shows what ambient authority was added or removed between two versions of a crate. Useful for reviewing Dependabot PRs or evaluating upgrades.

serde_json 1.0.130 → 1.0.133
─────────────────────────────
+ NET  src/de.rs:142:9  TcpStream::connect  fetch_schema()
- FS   src/io.rs:88:5   fs::read            old_loader()

Summary: 1 added, 1 removed, 1 unchanged

cargo capsec compare — Compare different crates

cargo capsec compare ureq@2.12.1 reqwest@0.12.12

Side-by-side capability profiles for making informed dependency choices.

ureq v2.12.1                   reqwest v0.12.12
──────────                     ────────────────
FS:   0                        FS:   3
NET:  4                        NET:  18
ENV:  1                        ENV:  4
PROC: 0                        PROC: 0
FFI:  0                        FFI:  12
Total: 5                       Total: 37

cargo capsec check-deny — Verify #[capsec::deny] annotations

cargo capsec check-deny

Checks that functions annotated with #[capsec::deny(fs)] or #[capsec::deny(all)] don't contain ambient authority calls. Any violation is promoted to critical risk.

cargo capsec badge — Generate shields.io badge

cargo capsec badge              # markdown badge
cargo capsec badge --json       # shields.io endpoint JSON

Configuration (.capsec.toml)

# Exclude directories from scanning
[analysis]
exclude = ["tests/**", "benches/**", "examples/**"]

# Crate-level deny — all ambient authority is a violation
[deny]
categories = ["all"]

# Custom authority patterns for project-specific I/O
[[authority]]
path = ["my_crate", "secrets", "fetch"]
category = "net"
risk = "critical"
description = "Fetches secrets from vault"

# Suppress known-good findings
[[allow]]
crate = "tracing"
reason = "Logging framework, reviewed"

[[allow]]
crate = "my-app"
function = "load_config"
reason = "Known FS access, reviewed"

# Classify crates as pure (no I/O) or resource (has I/O)
[[classify]]
crate = "my-parser"
classification = "pure"

Deep analysis (--deep)

The --deep flag uses a custom Rust compiler driver (capsec-driver) that walks MIR after macro expansion and type resolution. This catches:

  • FFI calls hidden behind macros (e.g., git2's try_call!()libgit2_sys)
  • Authority exercised through trait dispatch
  • Generic instantiations that resolve to I/O functions

Requires nightly:

cd crates/capsec-deep && cargo install --path .
cargo capsec audit --deep --include-deps

See crates/capsec-deep/README.md for architecture details.

Cross-crate propagation

With --include-deps, capsec builds an export map for each dependency: which functions exercise ambient authority. When your workspace code calls those functions, the finding propagates transitively:

your_code::handler() → reqwest::get() → TcpStream::connect [NET]

This works across:

  • Registry dependencies (crates.io)
  • Workspace member dependencies (topological ordering)
  • FFI boundaries (extern function declarations)
  • Multiple hops (A → B → C → std::fs::read)

Limitations

  • Dynamic dispatch (dyn Trait) — cannot statically resolve which implementation runs
  • C/C++ internals — sees FFI call boundaries but not what foreign code does inside
  • Inline assemblyasm!() blocks are opaque
  • Runtime-loaded codedlopen/libloading is invisible to static analysis

Output formats

Format Flag Use case
Text --format text Terminal, human review
JSON --format json Scripts, dashboards, CI pipelines
SARIF --format sarif GitHub Code Scanning, VS Code SARIF Viewer

License

Apache-2.0