d48cda4081
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
142 lines
4.3 KiB
Markdown
142 lines
4.3 KiB
Markdown
---
|
|
paths:
|
|
- "**/*.rs"
|
|
---
|
|
# Rust Security
|
|
|
|
> This file extends [common/security.md](../common/security.md) with Rust-specific content.
|
|
|
|
## Secrets Management
|
|
|
|
- Never hardcode API keys, tokens, or credentials in source code
|
|
- Use environment variables: `std::env::var("API_KEY")`
|
|
- Fail fast if required secrets are missing at startup
|
|
- Keep `.env` files in `.gitignore`
|
|
|
|
```rust
|
|
// BAD
|
|
const API_KEY: &str = "sk-abc123...";
|
|
|
|
// GOOD — environment variable with early validation
|
|
fn load_api_key() -> anyhow::Result<String> {
|
|
std::env::var("PAYMENT_API_KEY")
|
|
.context("PAYMENT_API_KEY must be set")
|
|
}
|
|
```
|
|
|
|
## SQL Injection Prevention
|
|
|
|
- Always use parameterized queries — never format user input into SQL strings
|
|
- Use query builder or ORM (sqlx, diesel, sea-orm) with bind parameters
|
|
|
|
```rust
|
|
// BAD — SQL injection via format string
|
|
let query = format!("SELECT * FROM users WHERE name = '{name}'");
|
|
sqlx::query(&query).fetch_one(&pool).await?;
|
|
|
|
// GOOD — parameterized query with sqlx
|
|
// Placeholder syntax varies by backend: Postgres: $1 | MySQL: ? | SQLite: $1
|
|
sqlx::query("SELECT * FROM users WHERE name = $1")
|
|
.bind(&name)
|
|
.fetch_one(&pool)
|
|
.await?;
|
|
```
|
|
|
|
## Input Validation
|
|
|
|
- Validate all user input at system boundaries before processing
|
|
- Use the type system to enforce invariants (newtype pattern)
|
|
- Parse, don't validate — convert unstructured data to typed structs at the boundary
|
|
- Reject invalid input with clear error messages
|
|
|
|
```rust
|
|
// Parse, don't validate — invalid states are unrepresentable
|
|
pub struct Email(String);
|
|
|
|
impl Email {
|
|
pub fn parse(input: &str) -> Result<Self, ValidationError> {
|
|
let trimmed = input.trim();
|
|
let at_pos = trimmed.find('@')
|
|
.filter(|&p| p > 0 && p < trimmed.len() - 1)
|
|
.ok_or_else(|| ValidationError::InvalidEmail(input.to_string()))?;
|
|
let domain = &trimmed[at_pos + 1..];
|
|
if trimmed.len() > 254 || !domain.contains('.') {
|
|
return Err(ValidationError::InvalidEmail(input.to_string()));
|
|
}
|
|
// For production use, prefer a validated email crate (e.g., `email_address`)
|
|
Ok(Self(trimmed.to_string()))
|
|
}
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
```
|
|
|
|
## Unsafe Code
|
|
|
|
- Minimize `unsafe` blocks — prefer safe abstractions
|
|
- Every `unsafe` block must have a `// SAFETY:` comment explaining the invariant
|
|
- Never use `unsafe` to bypass the borrow checker for convenience
|
|
- Audit all `unsafe` code during review — it is a red flag without justification
|
|
- Prefer `safe` FFI wrappers around C libraries
|
|
|
|
```rust
|
|
// GOOD — safety comment documents ALL required invariants
|
|
let widget: &Widget = {
|
|
// SAFETY: `ptr` is non-null, aligned, points to an initialized Widget,
|
|
// and no mutable references or mutations exist for its lifetime.
|
|
unsafe { &*ptr }
|
|
};
|
|
|
|
// BAD — no safety justification
|
|
unsafe { &*ptr }
|
|
```
|
|
|
|
## Dependency Security
|
|
|
|
- Run `cargo audit` to scan for known CVEs in dependencies
|
|
- Run `cargo deny check` for license and advisory compliance
|
|
- Use `cargo tree` to audit transitive dependencies
|
|
- Keep dependencies updated — set up Dependabot or Renovate
|
|
- Minimize dependency count — evaluate before adding new crates
|
|
|
|
```bash
|
|
# Security audit
|
|
cargo audit
|
|
|
|
# Deny advisories, duplicate versions, and restricted licenses
|
|
cargo deny check
|
|
|
|
# Inspect dependency tree
|
|
cargo tree
|
|
cargo tree -d # Show duplicates only
|
|
```
|
|
|
|
## Error Messages
|
|
|
|
- Never expose internal paths, stack traces, or database errors in API responses
|
|
- Log detailed errors server-side; return generic messages to clients
|
|
- Use `tracing` or `log` for structured server-side logging
|
|
|
|
```rust
|
|
// Map errors to appropriate status codes and generic messages
|
|
// (Example uses axum; adapt the response type to your framework)
|
|
match order_service.find_by_id(id) {
|
|
Ok(order) => Ok((StatusCode::OK, Json(order))),
|
|
Err(ServiceError::NotFound(_)) => {
|
|
tracing::info!(order_id = id, "order not found");
|
|
Err((StatusCode::NOT_FOUND, "Resource not found"))
|
|
}
|
|
Err(e) => {
|
|
tracing::error!(order_id = id, error = %e, "unexpected error");
|
|
Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"))
|
|
}
|
|
}
|
|
```
|
|
|
|
## References
|
|
|
|
See skill: `rust-patterns` for unsafe code guidelines and ownership patterns.
|
|
See skill: `security-review` for general security checklists.
|