# Rust Security

> When to activate: Rust security, unsafe code, input validation, secret handling, cryptography, supply chain, audit, injection prevention

- Skill: `mattakushi432/rust-security` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/rust-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/rust-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/rust-security

---


# Rust Security Patterns

## Safe Defaults

Rust's type system prevents many vulnerability classes in safe code:
- Memory safety: no buffer overflows, use-after-free, or dangling pointers
- No null pointer dereferences: `Option<T>` forces explicit handling
- Thread safety: `Send`/`Sync` prevent data races at compile time
- Integer overflow: caught in debug; use `checked_*` / `saturating_*` in release

```rust
fn safe_multiply(a: u64, b: u64) -> Option<u64> {
    a.checked_mul(b)
}
```

## Handling Secrets

```toml
[dependencies]
secrecy = "0.10"
zeroize = "1"
subtle = "2"
```

```rust
use secrecy::{Secret, ExposeSecret};

struct Credentials {
    username: String,
    password: Secret<String>, // will not appear in Debug output
}

fn authenticate(creds: &Credentials, input: &str) -> bool {
    let pwd = creds.password.expose_secret();
    // Constant-time comparison prevents timing attacks
    use subtle::ConstantTimeEq;
    pwd.as_bytes().ct_eq(input.as_bytes()).into()
}
```

## Input Validation

```rust
use validator::Validate;

#[derive(Debug, Validate, serde::Deserialize)]
struct CreateUserRequest {
    #[validate(length(min = 2, max = 64))]
    name: String,

    #[validate(email)]
    email: String,

    #[validate(length(min = 12))]
    password: String,
}

async fn create_user(Json(req): Json<CreateUserRequest>) -> Result<Json<User>, ApiError> {
    req.validate().map_err(|e| ApiError::BadRequest(e.to_string()))?;
    todo!()
}
```

## SQL Injection Prevention

Always use parameterized queries — never string-interpolate user input into SQL.

```rust
// NEVER DO THIS
let query = format!("SELECT * FROM users WHERE name = '{}'", user_input);

// ALWAYS DO THIS (sqlx)
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE name = $1", user_input)
    .fetch_optional(pool).await?;
```

## Path Traversal Prevention

```rust
fn safe_join(base: &std::path::Path, user_input: &str) -> anyhow::Result<std::path::PathBuf> {
    let clean = user_input.trim_start_matches(['/', '.', '']);
    let candidate = base.join(clean);
    let canonical = candidate.canonicalize()?;
    anyhow::ensure!(
        canonical.starts_with(base),
        "path traversal attempt: {:?} outside {:?}", canonical, base
    );
    Ok(canonical)
}
```

## Password Hashing

```toml
[dependencies]
argon2 = "0.5"
rand = "0.8"
```

```rust
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use argon2::password_hash::{rand_core::OsRng, SaltString};

fn hash_password(password: &str) -> anyhow::Result<String> {
    let salt = SaltString::generate(&mut OsRng);
    Ok(Argon2::default().hash_password(password.as_bytes(), &salt)?.to_string())
}

fn verify_password(password: &str, hash: &str) -> anyhow::Result<bool> {
    let parsed = PasswordHash::new(hash)?;
    Ok(Argon2::default().verify_password(password.as_bytes(), &parsed).is_ok())
}
```

## Secure Random Tokens

```rust
use rand::Rng;
fn generate_token() -> String {
    let bytes: Vec<u8> = (0..32).map(|_| rand::thread_rng().gen()).collect();
    hex::encode(bytes)
}
```

## Supply Chain Security

```bash
cargo install cargo-audit && cargo audit   # known CVEs
cargo install cargo-deny                  # license/ban/source policies
cargo install cargo-outdated && cargo outdated
```

## unsafe Code Guidelines

```rust
unsafe fn dangerous(ptr: *const u8, len: usize) -> &'static [u8] {
    // SAFETY:
    // - `ptr` is valid for `len` bytes (caller's responsibility)
    // - the lifetime is correct (caller ensures data outlives 'static)
    // - no mutable aliasing exists (caller's guarantee)
    std::slice::from_raw_parts(ptr, len)
}
```

## Common Anti-Patterns

- **Logging secrets or PII** — redact before logging; use `secrecy::Secret`
- **`unwrap()` on user-controlled deserialization** — handle errors explicitly
- **`md5`/`sha1` for passwords** — use `argon2`, `bcrypt`, or `scrypt`
- **Trusting `Content-Type` headers** — validate the actual content, not the declared type
- **Not committing `Cargo.lock` for services** — commit it for binaries/services; omit for libraries

