| name | supply-chain-secure-code |
| description | Use when writing Rust code that interacts with dependencies, handles credentials, executes commands, or manages configuration. Provides supply chain attack countermeasures at the code level including safe dependency usage, credential handling, subprocess hardening, and build.rs/proc-macro awareness. Adapted from Shai-Hulud npm attack lessons. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| user-invocable | true |
Supply Chain Secure Code (Rust)
This skill provides Rust coding patterns that defend against supply chain attacks at the application code level. Rust has unique attack surfaces (build.rs, proc macros, unsafe) that require specific attention beyond what Go or TypeScript need.
When to Apply
Apply these guidelines when:
- Importing and using third-party crates
- Handling credentials, tokens, or API keys in code
- Executing external commands (
std::process::Command)
- Loading configuration from files or environment variables
- Writing
build.rs scripts
- Writing or using proc macros
- Reviewing code for supply chain attack vectors
Threat Model: Rust-Specific Attack Vectors
| Attack Vector | Description | Runs When | Defense |
|---|
build.rs | Build scripts execute arbitrary code | cargo build | Audit build.rs, sandbox builds |
| Proc macros | Compile-time code execution | cargo build | Audit proc macro crates |
unsafe blocks | Bypass memory safety | Runtime | Minimize unsafe, audit dependencies |
| Malicious runtime code | Compromised crate logic | Runtime | Minimize dependencies, review |
include_bytes!/include_str! | Read files at compile time | cargo build | Check what files are included |
CRITICAL: Both build.rs and proc macros execute during cargo build, NOT just at runtime. This means building untrusted code is equivalent to running it.
Credential Handling
Never Hardcode Credentials
const API_KEY: &str = "ghp_xxxxxxxxxxxxxxxxxxxx";
const TOKEN: &str = include_str!("../secrets/token.txt");
let api_key = std::env::var("API_KEY")
.expect("API_KEY environment variable is required");
Credential Validation at Startup
use std::env;
struct Config {
api_key: String,
database_url: String,
}
impl Config {
fn from_env() -> Result<Self, String> {
let api_key = env::var("API_KEY")
.map_err(|_| "API_KEY is required")?;
let database_url = env::var("DATABASE_URL")
.map_err(|_| "DATABASE_URL is required")?;
if api_key.len() < 20 {
return Err("API_KEY appears too short".into());
}
Ok(Config { api_key, database_url })
}
}
fn main() {
let config = Config::from_env().expect("Invalid configuration");
}
Credential Isolation in Subprocesses
use std::process::Command;
let output = Command::new("some-tool")
.arg("--flag")
.output()?;
let output = Command::new("some-tool")
.arg("--flag")
.env_clear()
.env("PATH", std::env::var("PATH").unwrap_or_default())
.env("HOME", std::env::var("HOME").unwrap_or_default())
.output()?;
Zeroize Credentials in Memory
use zeroize::Zeroize;
fn process_token(token: &str) {
let mut secret = token.to_string();
secret.zeroize();
}
use zeroize::ZeroizeOnDrop;
#[derive(ZeroizeOnDrop)]
struct Credentials {
api_key: String,
secret: String,
}
Safe Dependency Usage
Minimize Dependencies
[dependencies]
serde = "1"
serde = { version = "1", default-features = false, features = ["derive"] }
Audit Unsafe Usage in Dependencies
cargo install cargo-geiger
cargo geiger
Dependency Feature Minimization
[dependencies]
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] }
Subprocess Security
Command Injection Prevention
use std::process::Command;
let output = Command::new("sh")
.arg("-c")
.arg(format!("echo {}", user_input))
.output()?;
let output = Command::new("echo")
.arg(&user_input)
.output()?;
fn safe_exec(name: &str) -> Result<(), Box<dyn std::error::Error>> {
let re = regex::Regex::new(r"^[a-zA-Z0-9_-]+$")?;
if !re.is_match(name) {
return Err("Invalid input".into());
}
Command::new("tool")
.arg("--name")
.arg(name)
.env_clear()
.env("PATH", "/usr/bin:/bin")
.output()?;
Ok(())
}
Never Download and Execute
let resp = reqwest::get("https://example.com/binary").await?;
let bytes = resp.bytes().await?;
std::fs::write("/tmp/tool", &bytes)?;
Command::new("/tmp/tool").spawn()?;
use sha2::{Sha256, Digest};
async fn verified_download(
url: &str,
expected_sha256: &str,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let resp = reqwest::get(url).await?;
let data = resp.bytes().await?;
let hash = Sha256::digest(&data);
let actual = hex::encode(hash);
if actual != expected_sha256 {
return Err(format!(
"Integrity check failed: expected {}, got {}",
expected_sha256, actual
).into());
}
Ok(data.to_vec())
}
build.rs Security (Writing Safe Build Scripts)
If your crate needs a build.rs:
Minimal build.rs Principles
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=proto/schema.proto");
println!("cargo:rustc-cfg=feature=\"custom\"");
}
If External Commands Are Needed
fn main() {
let output = std::process::Command::new("protoc")
.arg("--version")
.env_clear()
.env("PATH", "/usr/bin:/bin")
.output()
.expect("protoc must be installed");
}
File Access Security
Path Traversal Prevention
use std::path::{Path, PathBuf};
fn safe_path(base: &Path, user_path: &str) -> Result<PathBuf, String> {
let resolved = base.join(user_path);
let canonical = resolved.canonicalize()
.map_err(|e| format!("Invalid path: {}", e))?;
if !canonical.starts_with(base.canonicalize().unwrap()) {
return Err("Path traversal detected".into());
}
Ok(canonical)
}
Credential File Awareness
Network Security
Outbound Request Validation
use url::Url;
use std::collections::HashSet;
fn validate_url(raw_url: &str, allowed_hosts: &HashSet<&str>) -> Result<Url, String> {
let url = Url::parse(raw_url)
.map_err(|e| format!("Invalid URL: {}", e))?;
let host = url.host_str()
.ok_or("No host in URL")?;
if !allowed_hosts.contains(host) {
return Err(format!("Blocked: unauthorized host {}", host));
}
let blocked = ["169.254.169.254", "metadata.google.internal"];
if blocked.contains(&host) {
return Err(format!("Blocked: metadata endpoint {}", host));
}
Ok(url)
}
Code Review Checklist
High Priority
Medium Priority
Low Priority (Defense in Depth)
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
cargo metadata --format-version 1 | \
jq '.packages[] | select(.targets[].kind[] == "proc-macro") | .name'
cargo geiger 2>/dev/null
gh api repos/{owner}/{repo}/actions/runners --jq '.runners[] | {name, status}'
find .github/workflows -name "*.yml" -newer Cargo.toml
cargo build --locked 2>&1 | grep -i "error"
References