rust-security-review
Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features in Rust. Provides a comprehensive security checklist and patterns using idiomatic crates.
SKILL.md
| Name | rust-security-review |
| Description | Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features in Rust. Provides a comprehensive security checklist and patterns using idiomatic crates. |
name: rust-security-review description: | Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features in Rust. Provides a comprehensive security checklist and patterns using idiomatic crates.
Rust Security Review
This skill ensures all Rust code follows security best practices, leveraging the language's type safety while identifying potential logic-level vulnerabilities.
When to Activate
- Implementing authentication or authorization (JWT, session cookies)
- Handling user input via
serdeor public API endpoints - Working with secrets, credentials, or
.envfiles - Interacting with databases using
sqlx. - Implementing payment features or handling sensitive PII
- Integrating third-party crates or APIs
Security Checklist
1. Secrets Management
❌ NEVER Do This
// Hardcoded secret - will be compiled into the binary
const API_KEY: &str = "sk-proj-xxxxx";
✅ ALWAYS Do This
Use dotenvy for loading and envy for type-safe deserialization. Use the secrecy crate to prevent accidental logging.
use serde::Deserialize;
use secrecy::{Secret, ExposeSecret};
#[derive(Deserialize)]
struct Config {
database_url: Secret<String>,
api_key: Secret<String>,
}
fn main() {
dotenvy::dotenv().ok();
let config = envy::from_env::<Config>().expect("Missing env vars");
// Access safely: config.api_key.expose_secret()
}
Verification Steps
- No hardcoded keys in source code or
Cargo.toml. -
.envadded to.gitignore. - Sensitive strings wrapped in
secrecy::Secret. - CI/CD secrets injected via environment, not files.
2. Input Validation
Validate at the Serialization Boundary
Use the validator crate alongside serde to enforce business logic on input.
use serde::Deserialize;
use validator::Validate;
#[derive(Deserialize, Validate)]
struct SignupRequest {
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
#[validate(range(min = 18, max = 120))]
age: u16,
}
// In an Axum handler
async fn register(axum::Json(payload): axum::Json<SignupRequest>) -> impl IntoResponse {
if let Err(e) = payload.validate() {
return (StatusCode::BAD_REQUEST, format!("Validation error: {}", e));
}
// Proceed...
}
Verification Steps
- All public structs implement
Validate. - String length limits enforced (prevent memory exhaustion/ReDoS).
- Numeric bounds checked (prevent overflow/logic errors).
- Whitelist validation for enums and fixed strings.
3. SQL Injection Prevention
❌ NEVER Construct SQL with String Formatting
// DANGEROUS: SQL Injection vulnerability
let query = format!("SELECT * FROM users WHERE email = '{}'", user_email);
✅ ALWAYS Use Parameterized Queries
sqlx provides compile-time checked queries that are inherently safe.
// Safe: Parameters are bound via the database protocol
let user = sqlx::query!(
"SELECT id, name FROM users WHERE email = $1",
user_email
)
.fetch_optional(&pool)
.await?;
Verification Steps
- No
format!orconcat!used to build SQL strings. -
sqlx::query!orsqlx::query_as!macros used for compile-time safety. - Database user has minimal required permissions.
4. Authentication & Authorization
Cookie Security
use axum_extra::extract::cookie::{Cookie, SameSite};
// Set secure, httpOnly cookies
let cookie = Cookie::build(("session", token))
.path("/")
.http_only(true)
.secure(true)
.same_site(SameSite::Strict)
.finish();
Password Hashing
use argon2::{
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
Argon2
};
// Use Argon2id for hashing
let salt = SaltString::generate(&mut OsRng);
let password_hash = Argon2::default()
.hash_password(password.as_bytes(), &salt)?
.to_string();
Verification Steps
- Passwords hashed with
Argon2idorscrypt. - Cookies are
HttpOnly,Secure, andSameSite=Strict/Lax. - Authorization checks performed at the handler level.
- JWTs (if used) have short expiration and validated signatures.
5. XSS Prevention
Sanitize User-Provided HTML
Use ammonia to clean any HTML intended for rendering.
let unsafe_html = "<img src=x onerror=alert(1)><b>Hello</b>";
let clean_html = ammonia::clean(unsafe_html);
// Result: "<b>Hello</b>"
Verification Steps
- User-provided strings sanitized before being rendered as HTML.
- Content Security Policy (CSP) headers implemented.
- Templates (Tera/Askama) set to auto-escape by default.
6. Rate Limiting
API Throttling
Use tower-governor (for Axum) or governor to prevent brute force.
let governor_conf = Box::new(
GovernorConfigBuilder::default()
.per_second(2)
.burst_size(5)
.finish()
.unwrap(),
);
let app = Router::new()
.route("/api/login", post(login))
.layer(GovernorLayer { config: &governor_conf });
Verification Steps
- Rate limiting applied to all auth/sensitive endpoints.
- Stricter limits on heavy database queries.
7. Blockchain (Solana/Anchor)
Arithmetic Safety
// ❌ WRONG: Potential overflow
let total = a + b;
// ✅ CORRECT: Checked math
let total = a.checked_add(b).ok_or(error!(ErrorCode::Overflow))?;
Signer and Ownership Checks
// Anchor enforces this via attributes
#[derive(Accounts)]
pub struct UpdateConfig<'info> {
#[account(mut, has_one = admin)]
pub config: Account<'info, GlobalConfig>,
pub admin: Signer<'info>, // Must sign the transaction
}
Verification Steps
- All arithmetic uses
checked_,saturating_, orsafe_methods. - Signer checks verified for all authority accounts.
- Account ownership and discriminators verified.
8. Logging and Errors
❌ NEVER Log PII or Secrets
// DANGEROUS: Logs the password
error!("Failed login for {} with password {}", email, password);
✅ ALWAYS Redact Sensitive Info
error!("Failed login attempt for email: {}", email);
// Detailed error only in internal tracing spans
Verification Steps
- No PII (emails, names, tokens) in standard logs.
- Generic error messages returned to the user (no stack traces).
- Internal errors logged with enough context for debugging.
9. Dependency Security
Audit Crates
# Check for vulnerabilities in dependencies
cargo audit
# Deny crates with specific licenses or known issues
cargo deny check
Verification Steps
-
Cargo.lockis committed to Git. -
cargo auditruns in CI/CD. - Minimal use of
unsafecode; allunsafeblocks reviewed.
Pre-Deployment Checklist
- Environment: No
.envfiles in production; use platform secrets. - Audit:
cargo auditpassed with zero vulnerabilities. - Overflows: Compiled with overflow checks (enabled by default in dev, use
checked_math for prod). - Headers: HSTS, CSP, and X-Frame-Options configured.
- Database: RLS (if using Postgres) or minimal user roles set.
- Errors: User-facing errors are generic; logs are sanitized.
Remember: In Rust, memory safety is guaranteed, but logic safety is your responsibility. Always assume user input is malicious and that environment variables are missing.