Rust Security Code Review Guide
1. Introduction
I put this guide together as a structured approach to security-focused code review for Rust applications. Whether you’re just starting to identify security vulnerabilities in Rust code or you’re an experienced developer looking for a language-specific checklist, I’ve tried to make it useful at both levels.
Rust’s ownership model, borrow checker, and type system prevent entire classes of bugs, use-after-free, null pointer dereferences, and data races in safe code. However, what I found when I started reviewing Rust codebases is that unsafe blocks, third-party crate choices, and application-level logic errors still introduce serious vulnerabilities. What follows covers manual review strategies, common anti-patterns, recommended tooling, and vulnerability patterns organised by class, with cross-references to the intentionally vulnerable examples in this project.
Audience: Security trainees, application developers, code reviewers, and anyone evaluating Rust codebases for security weaknesses.
2. Manual Review Best Practices
2.1 Audit Every unsafe Block
Every unsafe block is a promise that the developer has manually verified invariants the compiler cannot check. Treat each one as a high-priority review target. Verify:
- Raw pointer dereferences stay within allocation bounds
ptr::read,ptr::write, andptr::copy_nonoverlappinguse correct lengths- Manual
Send/Syncimplementations are sound std::alloc::allocandLayout::from_size_align_uncheckedreceive validated inputs
2.2 Trace Data from Entry to Sink
Actix-web, Rocket, and Axum accept user input through web::Query, web::Json, web::Form, web::Path, headers, and cookies. The approach I’ve found most effective is to trace every external input to where it’s consumed, shell commands, HTML output, HTTP requests, or file operations. Any path from source to sink without validation is a potential vulnerability.
2.3 Inspect Command::new and Shell Invocations
Rust’s std::process::Command is safe when used with argument lists, but passing user input through Command::new("sh").arg("-c").arg(format!(...)) reintroduces shell injection. Flag every use of sh -c or cmd /C with format! string interpolation.
2.4 Review format! in HTML Contexts
Rust has no built-in HTML templating auto-escape. When Actix-web handlers build HTML responses with format! and user-controlled data, every interpolation point is a potential XSS vector. Check for manual escaping of <, >, ", ', and &.
2.5 Check for Hardcoded Secrets
Search for const or static string literals that look like passwords, API keys, tokens, or connection strings. Common patterns include variables named SECRET, KEY, PASSWORD, TOKEN, or connection strings containing ://user:pass@host.
2.6 Evaluate Cryptographic Choices
Flag uses of the md5 or sha1 crates for password hashing or integrity verification. Check for the des crate, ECB mode encryption, or hardcoded encryption keys. Verify that session tokens use OsRng or rand::thread_rng() rather than custom PRNGs or time-based seeds.
2.7 Assess Access Control Logic
For every Actix-web handler, verify:
- Authentication is checked before any business logic executes
- Authorisation checks use server-side session data, not client-supplied headers (e.g.,
X-User-Role) - Object-level access control prevents users from accessing resources belonging to others (IDOR)
#[derive(Serialize)]on structs does not leak sensitive fields, use#[serde(skip_serializing)]or separate response structs
2.8 Review CORS Configuration
Check actix-cors::Cors builder calls. The combination of allow_any_origin() with supports_credentials() effectively disables the same-origin policy. Verify that origins are restricted to specific trusted domains.
2.9 Examine Error Handling and .unwrap() Usage
Look for .unwrap() on Result or Option in request handlers, these cause panics that crash the server or leak stack traces. In production code, prefer .map_err(), ? operator, or explicit error responses. Also check that error responses do not include internal paths, connection strings, or stack traces.
3. Common Security Pitfalls
3.1 Injection
| Anti-Pattern | Risk |
|---|---|
Command::new("sh").arg("-c").arg(format!("ping -c 3 {}", host)) |
Command injection via shell interpolation |
format!("dig {} {} +short", record_type, domain) passed to sh -c |
Command injection in helper function |
format!("tail -n {} {} | grep '{}'", lines, path, keyword) via sh -c |
Multi-parameter injection in shell pipeline |
format!("tar czf {} {}", archive, path) via sh -c |
Command injection via path parameter |
User input rendered via format!("<h2>{}</h2>", title) in HTML |
Stored and reflected XSS |
format!("{}({})", callback, json_data) in JSONP response |
XSS via callback injection |
3.2 Broken Access Control
| Anti-Pattern | Risk |
|---|---|
Handler returns full struct via HttpResponse::Ok().json(user) with #[derive(Serialize)] including SSN, salary |
Excessive data exposure |
Authorisation reads role from X-User-Role header instead of session |
Client-side role spoofing |
| No ownership check on order/document access after authentication | IDOR, any authenticated user can read/modify any resource |
| Debug/config endpoints with no authentication | Information disclosure of secrets and credentials |
3.3 Cryptographic Failures
| Anti-Pattern | Risk |
|---|---|
md5::compute(password.as_bytes()) for password storage |
Weak, unsalted password hashing |
Des::new_from_slice(&DES_KEY) with block-by-block ECB encryption |
Deprecated cipher in insecure mode |
const DES_KEY: [u8; 8] = *b"s3cr3t!!" |
Hardcoded encryption key |
Custom LCG PRNG seeded with SystemTime nanoseconds for session tokens |
Predictable token generation |
SHA-1 hash of format!("tkn-{}-{}", seq, timestamp) for API tokens |
Predictable inputs produce predictable tokens |
3.4 Insecure Design
| Anti-Pattern | Risk |
|---|---|
Plaintext passwords stored in HashMap structs |
No hashing at all |
Error responses including DB_CONNECTION constant with embedded credentials |
Credential leakage via error messages |
| Password reset token returned in API response body | Token leakage, bypasses email verification |
#[derive(Serialize)] on User struct with password field serialized in debug endpoint |
Full user record exposure |
const DB_CONNECTION: &str = "postgresql://user:pass@host/db" |
Hardcoded connection string with credentials |
3.5 Security Misconfiguration
| Anti-Pattern | Risk |
|---|---|
Cors::default().allow_any_origin().allow_any_method().supports_credentials() |
Overly permissive CORS disables same-origin policy |
| XML parsing without DTD rejection (defence-in-depth failure) | Latent XXE risk if XML library is swapped |
PUT /api/settings with no authentication accepting arbitrary key-value pairs |
Unauthenticated modification of security settings |
Diagnostics endpoint returning DB_HOST, DB_USER, DB_PASS |
Credential exposure via admin endpoint |
std::env::vars() collected and returned in JSON response |
All environment variables exposed |
Hardcoded ADMIN_TOKEN constant for admin endpoint protection |
Static credential in source code |
3.6 Vulnerable Components
| Anti-Pattern | Risk |
|---|---|
| Pinning actix-web to 3.x when 4.x is current | Outdated framework with known issues |
| Pinning serde/serde_json to significantly old versions | Missing security patches and hardening |
Not running cargo audit in CI |
Known CVEs in dependencies go undetected |
3.7 Auth Failures
| Anti-Pattern | Risk |
|---|---|
const ADMIN_BOOTSTRAP_KEY: &str = "abk_prod_2024_f8e7d6c5" |
Hardcoded admin credential |
const INTER_SERVICE_TOKEN: &str = "ist-mesh-auth-prod-2024" |
Hardcoded service-to-service token |
| No rate limiting on login endpoint | Unrestricted brute-force attacks |
| Reset token returned in API response instead of sent via email | Account takeover via token leakage |
| MD5 for password hashing in authentication flow | Weak hash easily reversed |
3.8 Logging and Monitoring Failures
| Anti-Pattern | Risk |
|---|---|
No log crate imported, no logging macros in authentication handlers |
Failed logins go undetected |
| Successful login returns password and API key in response with no audit log | Credential leakage and invisible account takeover |
| Role changes performed without audit logging | Silent privilege escalation |
| Financial transactions processed without persistent logging | Fraud goes undetected |
| Data export endpoint with no logging of who requested the export | Mass exfiltration invisible |
3.9 SSRF
| Anti-Pattern | Risk |
|---|---|
reqwest::get(&body.url) without any URL validation |
Unrestricted SSRF |
Blocklist checking only localhost and 127.0.0.1 (missing [::1], 169.254.x.x, private ranges) |
SSRF blocklist bypass |
| Webhook callback URL validated at registration but not re-validated at delivery | Two-step SSRF |
Hostname string check for 169.254 without DNS resolution (DNS rebinding bypass) |
Metadata endpoint access via DNS rebinding |
Service proxy with fallback to user-supplied base_url |
Open relay to internal services |
3.10 Memory Safety (unsafe)
| Anti-Pattern | Risk |
|---|---|
for i in 0..=len with ptr::write(ptr.add(i), ...) |
Off-by-one out-of-bounds write |
ptr::copy_nonoverlapping(src, dest, name.len()) into fixed-size array without length check |
Heap buffer overflow |
let total_len: u16 = parts.iter().map(|s| s.len() as u16).sum() for allocation size |
Integer truncation causes undersized allocation |
Layout::from_size_align_unchecked with attacker-controlled sizes |
Undefined behaviour from invalid layout |
unsafe impl Send for UnsafeSendCell<T> {} / unsafe impl Sync |
Bypasses borrow checker, enables data races |
3.11 Race Conditions
| Anti-Pattern | Risk |
|---|---|
Read-yield-write on shared data through UnsafeCell without synchronization |
Lost updates, balance corruption |
Check-then-act (if available > 0 then yield then decrement) through unsafe cell |
TOCTOU, overselling, double-spend |
static mut flag-guarded singleton initialisation without synchronization |
Multiple initialisations, data loss |
| Non-atomic counter increment through unsafe shared access | Lost counter updates |
4. Recommended SAST Tools & Linters
4.1 cargo-audit
cargo-audit checks Cargo.lock for crates with known security vulnerabilities reported to the RustSec Advisory Database.
Installation:
cargo install cargo-audit
Basic usage, audit the current project:
cargo audit
Generate a JSON report:
cargo audit --json
Check a specific Cargo.lock file:
cargo audit -f /path/to/Cargo.lock
What cargo-audit catches: Known CVEs in dependencies, unmaintained crates, yanked crate versions, and advisories from the RustSec database. It is particularly effective at detecting outdated frameworks (e.g., actix-web 3.x) and crates with published security advisories.
4.2 clippy
Clippy is the official Rust linter, included with rustup. It provides hundreds of lint checks including several security-relevant ones.
Installation (included with rustup):
rustup component add clippy
Basic usage, lint the current project:
cargo clippy
Treat all warnings as errors:
cargo clippy -- -D warnings
Run with all lint groups enabled:
cargo clippy -- -W clippy::all -W clippy::pedantic -W clippy::nursery
Lint a specific file (via project):
cargo clippy -p my_crate
What clippy catches: Unnecessary unsafe blocks, .unwrap() on Result/Option in library code, suspicious arithmetic (integer overflow patterns), redundant clones, missing error handling, as casts that may truncate, and many idiomatic Rust issues. Security-relevant lints include clippy::unwrap_used, clippy::expect_used, clippy::cast_possible_truncation, and clippy::transmute_ptr_to_ref.
4.3 Semgrep
Semgrep is a multi-language static analysis tool with pattern-based rules. It supports Rust and has community rules for common vulnerability patterns.
Installation:
pip install semgrep
# or
brew install semgrep
Scan with the default Rust security ruleset:
semgrep --config "p/rust" security-bug-examples/
Scan with OWASP Top 10 rules:
semgrep --config "p/owasp-top-ten" security-bug-examples/
Scan a single file:
semgrep --config "p/rust" injection/command-injection/rust/main.rs
Run with auto configuration (recommended for first-time scans):
semgrep --config auto security-bug-examples/
What Semgrep catches: Command injection via Command::new("sh").arg("-c") with format strings, hardcoded credentials, use of weak cryptographic crates (md5, des), unsafe block patterns, and framework-specific misconfigurations. Semgrep’s pattern matching is effective at detecting string-formatting-based injection in Rust that clippy does not flag.
5. Language-Specific Vulnerability Patterns
5.1 Command Injection (CWE-78)
Pattern: Shell execution via Command::new("sh").arg("-c") with format!
// Vulnerable, user input interpolated into shell command
let cmd = format!("ping -c 3 {}", host);
Command::new("sh").arg("-c").arg(&cmd).output();
Pattern: Helper function with shell command construction
// Vulnerable, domain parameter passed through without sanitization
fn run_dig(domain: &str, record_type: &str) -> String {
let cmd = format!("dig {} {} +short", record_type, domain);
Command::new("sh").arg("-c").arg(&cmd).output()
// ...
}
Pattern: Multi-parameter shell pipeline
// Vulnerable, three user-controlled parameters in a pipeline
let cmd = format!("tail -n {} {} | grep '{}'", lines, log_path, keyword);
Command::new("sh").arg("-c").arg(&cmd).output();
Safe alternative:
// Use argument list, no shell interpretation
Command::new("ping").args(["-c", "3", &host]).output();
5.2 Cross-Site Scripting, XSS (CWE-79)
Pattern: Stored XSS via format! in HTML
// Vulnerable, post content rendered without escaping
page.push_str(&format!("<h2>{}</h2>", post.title));
page.push_str(&format!("<div>{}</div>", post.content));
Pattern: Reflected XSS in search results
// Vulnerable, query reflected back into page
page.push_str(&format!("<h2>Results for: {}</h2>", q));
Pattern: XSS via href attribute (javascript: protocol)
// Vulnerable, website URL injected into href without validation
page.push_str(&format!("<a href='{}'>{}</a>", profile.website, profile.website));
Pattern: JSONP callback injection
// Vulnerable, callback parameter not validated
HttpResponse::Ok()
.content_type("application/javascript")
.body(format!("{}({})", cb, json_data));
Safe alternative:
fn escape_html(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
page.push_str(&format!("<h2>{}</h2>", escape_html(&post.title)));
5.3 Broken Access Control (CWE-200, CWE-284, CWE-639)
Pattern: No authentication on sensitive endpoint
async fn get_user_profile(path: web::Path<u32>, state: web::Data<AppState>) -> HttpResponse {
let users = state.users.lock().unwrap();
match users.get(&user_id) {
Some(user) => HttpResponse::Ok().json(user), // Returns SSN, salary, no auth
None => HttpResponse::NotFound().json(json!({"error": "Not found"})),
}
}
Pattern: Client-controlled authorisation header
let client_role = req.headers()
.get("X-User-Role")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
if client_role != "admin" {
return HttpResponse::Forbidden().json(json!({"error": "Admin access required"}));
}
Pattern: Missing object-level authorisation (IDOR)
// Authenticated but no ownership check, any user can access any order
let order = orders.get(&order_id);
HttpResponse::Ok().json(order)
5.4 Cryptographic Failures (CWE-327, CWE-328, CWE-330)
Pattern: MD5 for password hashing
fn compute_md5(input: &str) -> String {
format!("{:x}", md5::compute(input.as_bytes()))
}
// Used for: user.password_hash = compute_md5(&password);
Pattern: DES in ECB mode with hardcoded key
const DES_KEY: [u8; 8] = *b"s3cr3t!!";
// Block-by-block encryption without chaining
let cipher = Des::new_from_slice(&DES_KEY).unwrap();
for chunk in encrypted.chunks_mut(8) {
cipher.encrypt_block(block);
}
Pattern: Custom LCG PRNG for session tokens
fn simple_prng_next(state: &mut u64) -> u64 {
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
*state >> 33
}
// Seeded with SystemTime nanoseconds, predictable
Safe alternative:
use bcrypt::{hash, verify, DEFAULT_COST};
let hashed = hash(password, DEFAULT_COST).unwrap();
use rand::Rng;
let token: String = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(32)
.map(char::from)
.collect();
5.5 Insecure Design (CWE-209, CWE-522)
Pattern: Plaintext password storage
m.insert(1, User {
id: 1, username: "admin".into(),
password: "Adm1n_Pr0d!".into(), // Plaintext, no hashing
// ...
});
Pattern: Verbose error responses leaking credentials
HttpResponse::InternalServerError().json(serde_json::json!({
"error": "Report generation failed",
"details": e,
"connection_string": DB_CONNECTION // Contains password
}))
Pattern: User enumeration via distinct error messages
// Different responses reveal whether username exists
HttpResponse::NotFound().json(json!({"error": format!("No account found for '{}'", username)}))
// vs.
HttpResponse::Unauthorized().json(json!({"error": "Incorrect password", "attempts": count}))
5.6 Security Misconfiguration (CWE-16, CWE-611)
Pattern: Overly permissive CORS with credentials
let cors = Cors::default()
.allow_any_origin()
.allow_any_method()
.allow_any_header()
.supports_credentials();
Pattern: Unauthenticated settings modification
// No auth check, anyone can disable TLS verification, rate limiting
async fn update_settings(body: web::Json<HashMap<String, Value>>, state: web::Data<AppState>) -> HttpResponse {
let mut settings = state.settings.lock().unwrap();
for (k, v) in body.into_inner() {
settings.insert(k, v);
}
// ...
}
Pattern: Environment variables dumped to response
let env_vars: HashMap<String, String> = std::env::vars().collect();
HttpResponse::Ok().json(json!({"env": env_vars}))
5.7 Vulnerable and Outdated Components (CWE-1104)
Pattern: Outdated framework version in Cargo.toml
[dependencies]
actix-web = "3.3.3" # Major version behind, unmaintained
serde = "1.0.130" # Significantly outdated
serde_json = "1.0.68" # Missing parsing fixes
Safe alternative:
[dependencies]
actix-web = "4.4.0"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
5.8 Auth Failures (CWE-287, CWE-307, CWE-798)
Pattern: Hardcoded credentials as constants
const ADMIN_BOOTSTRAP_KEY: &str = "abk_prod_2024_f8e7d6c5";
const INTER_SERVICE_TOKEN: &str = "ist-mesh-auth-prod-2024";
Pattern: No rate limiting on login
// Login handler processes unlimited attempts, no throttling or lockout
async fn login(body: web::Json<LoginRequest>, state: web::Data<AppState>) -> HttpResponse {
// ... direct credential check with no rate limit
}
Pattern: Reset token returned in response
HttpResponse::Ok().json(json!({
"message": "Reset email sent",
"token": token, // Should only be sent via email
}))
5.9 Logging and Monitoring Failures (CWE-778)
Pattern: No logging crate imported, no audit trail
// Entire application has no `use log::*;` or logging macro calls
// Failed logins, role changes, data exports, all silent
Pattern: Credentials in API responses
HttpResponse::Ok().json(json!({
"token": token,
"password": user.password, // Plaintext password in response
"api_key": user.api_key, // API key in response
}))
5.10 SSRF (CWE-918)
Pattern: Unrestricted URL fetch
let resp = reqwest::get(&body.url).await?;
// No validation of URL destination
Pattern: Incomplete blocklist
let blocked = ["localhost", "127.0.0.1"];
// Missing: [::1], 0.0.0.0, 169.254.x.x, 10.x.x.x, 172.16.x.x, 192.168.x.x
Pattern: Service proxy with user-controlled fallback
let base = match service_map.get(service) {
Some(url) => url.to_string(),
None => query.base_url.clone(), // Attacker-controlled fallback
};
let full_url = format!("{}{}", base, path);
reqwest::get(&full_url).await?;
Safe alternative:
use std::net::ToSocketAddrs;
if let Some(host) = parsed.host_str() {
if let Ok(addrs) = (host, 0).to_socket_addrs() {
for addr in addrs {
if addr.ip().is_loopback() || addr.ip().is_private() {
return HttpResponse::Forbidden().json(json!({"error": "Blocked"}));
}
}
}
}
5.11 Out-of-bounds Write (CWE-787)
Pattern: Off-by-one with inclusive range in unsafe pointer arithmetic
unsafe {
let ptr = data.as_mut_ptr();
let len = data.len();
for i in 0..=len { // BUG: should be 0..len
let val = ptr::read(ptr.add(i));
ptr::write(ptr.add(i), val * factor);
}
}
Pattern: Unbounded copy into fixed-size buffer
unsafe {
let dest = self.names[idx].as_mut_ptr();
let src = name.as_ptr();
let len = name.len(); // Not clamped to buffer size
ptr::copy_nonoverlapping(src, dest, len);
}
Pattern: Integer truncation in allocation size
// u16 wraps around for combined lengths > 65535
let total_len: u16 = parts.iter().map(|s| s.len() as u16).sum();
let layout = Layout::from_size_align_unchecked(total_len as usize, 1);
let buf = alloc(layout);
// Copy loop writes full data into undersized buffer
Safe alternative:
// Avoid unsafe entirely for common operations
fn scale_buffer(data: &mut [f64], factor: f64) {
for val in data.iter_mut() {
*val *= factor;
}
}
5.12 Race Conditions (CWE-362)
Pattern: Unsafe Send/Sync wrapper bypassing borrow checker
struct UnsafeSendCell<T>(UnsafeCell<T>);
unsafe impl<T> Send for UnsafeSendCell<T> {}
unsafe impl<T> Sync for UnsafeSendCell<T> {}
// Enables data races the compiler would otherwise prevent
Pattern: Check-then-act without synchronization
unsafe {
let pool = &mut *cell.get();
if pool.available > 0 {
thread::yield_now(); // Window for race
pool.available -= 1;
pool.sold += 1;
}
}
Pattern: static mut singleton initialisation race
static mut CONFIG_INITIALIZED: bool = false;
static mut GLOBAL_CONFIG: Option<Vec<(String, String)>> = None;
unsafe fn get_global_config() -> &'static mut Vec<(String, String)> {
if !CONFIG_INITIALIZED {
thread::yield_now(); // Multiple threads pass the check
GLOBAL_CONFIG = Some(Vec::new());
CONFIG_INITIALIZED = true;
}
GLOBAL_CONFIG.as_mut().unwrap()
}
Safe alternative:
use std::sync::{Arc, Mutex, OnceLock};
// For shared mutable state
let account = Arc::new(Mutex::new(BankAccount::new("Shared", 10000)));
// For lazy singleton initialization (Rust 1.70+)
static GLOBAL_CONFIG: OnceLock<Mutex<Vec<(String, String)>>> = OnceLock::new();
fn get_global_config() -> &'static Mutex<Vec<(String, String)>> {
GLOBAL_CONFIG.get_or_init(|| Mutex::new(Vec::new()))
}
6. Cross-References to Examples
The table below maps each vulnerability class to the Rust source file and companion documentation in this project. All paths are relative to the docs/ directory.
Note: Rust is excluded from SQL Injection (type system makes raw SQL uncommon), Integrity Failures (serde is safe by default), Use After Free (ownership system prevents it), NULL Pointer Dereference (no null in safe Rust), and Integer Overflow (checked in debug, wrapping in release, covered separately in the integer-overflow category for Rust).
7. Quick-Reference Checklist
Use this checklist during Rust code reviews. Each item maps to a vulnerability class covered in this guide.
-
unsafeaudit, Everyunsafeblock is justified, minimally scoped, and its invariants are documented and verified (bounds checks, valid pointers, correct lifetimes) - No shell injection,
Command::new("sh").arg("-c")is never used withformat!containing user input; use argument lists instead - Output encoding, All user-controlled data rendered in HTML responses is escaped (
<,>,",&); consider using a templating engine with auto-escaping - Authentication on every endpoint, All sensitive handlers verify the session/token before processing
- Server-side authorisation, Role and permission checks use server-side session data, not client-supplied headers
- Object-level access control, Resource access verifies the requesting user owns or is authorized for the specific resource
- No sensitive fields in responses,
#[derive(Serialize)]structs returned in responses do not include passwords, SSNs, or API keys; use#[serde(skip_serializing)]or separate response structs - Strong password hashing, Passwords are hashed with
bcrypt,argon2, orscrypt, nevermd5orsha1 - Modern encryption, AES-GCM via the
aes-gcmcrate is used; nodescrate, no ECB mode - Cryptographic randomness, Tokens and secrets use
rand::thread_rng()withOsRngor thegetrandomcrate, not custom PRNGs or time-based seeds - No hardcoded secrets, API keys, passwords, encryption keys, and connection strings are loaded from environment variables or a secrets manager, not
constorstaticliterals - SSRF protection, Outbound HTTP requests via
reqwestvalidate URLs against an allowlist; resolve hostnames and block private/loopback/metadata IPs before fetching - Minimal error responses, Error responses do not include stack traces, database connection strings, or internal configuration
- Secure CORS,
actix-cors::Corsis configured with specific trusted origins, notallow_any_origin()combined withsupports_credentials() - XML safety, XML parsers reject DTD declarations; if using libraries other than
roxmltree, explicitly disable entity resolution - Audit logging, Authentication events, privilege changes, financial transactions, and data exports are logged using the
logortracingcrate - No credentials in responses, API responses do not include passwords, API keys, or other secrets
- Thread safety, Shared mutable state uses
Arc<Mutex<T>>,RwLock, or atomics; nounsafe impl SynconUnsafeCellwrappers; nostatic mutfor shared state - Safe pointer arithmetic,
unsafepointer operations use exclusive ranges (0..len, not0..=len); copy lengths are clamped to destination buffer sizes - No integer truncation in allocations, Allocation sizes use
usize, notu16oru32;ascasts are checked for truncation - Dependency hygiene,
Cargo.tomlpins reasonable versions;cargo auditruns in CI; outdated major versions are upgraded - Rate limiting, Authentication endpoints use middleware like
actix-governorto throttle brute-force attempts