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.