code-review-rust
Security review of Rust code — where a memory-safe language still has real bugs: `unsafe`, FFI, panics, and the usual injection/logic sinks. Load when reviewing Rust source/PR, on .rs / Cargo.toml in
SKILL.mdskills/code-review/code-review-rust/SKILL.md
GitHubRust source review
When it applies
Reviewing Rust (a service, a CLI, a library). Safe Rust removes most memory bugs, so the review focuses on the places safety is opted out of, the ways Rust code still panics or mis-handles input, and ordinary injection/logic in web handlers.
Why it works
Rust's guarantees hold only outside unsafe and only for memory safety — they don't stop SQL built
by string, a command run via a shell, a .unwrap() that panics on attacker input (DoS), or a logic
error. Concentrating on those boundaries finds the real bugs efficiently.
Sinks & patterns (grep, then reason about the boundary)
unsafeblocks: every one is a manual proof obligation — raw pointer deref,get_unchecked,slice::from_raw_parts,mem::transmute, uninitialised memory. Check the invariant it assumes.- FFI:
extern "C"/bindgenboundaries — lengths, lifetimes, and NUL handling across the C edge (the C side has none of Rust's guarantees; pair withcode-review-cpp). - Panics as DoS:
unwrap/expect/panic!/indexingv[i]/unreachable!on attacker-controlled input; integerascasts that truncate; arithmetic overflow (panics in debug, wraps in release — both can be bugs). Prefer?/checked ops. - Injection: SQL via
format!into a query instead of parameter binding (sqlx/diesel);std::process::Commandwithsh -cand concatenated input;Commandarg vs shell form. - Path / SSRF: user paths joined without canonicalisation + prefix check; HTTP clients (reqwest) fetching user URLs.
- Deserialization / web:
serdeinto types from untrusted data (resource exhaustion, unexpected variants); actix/axum/rocket extractors bound to over-broad structs (mass assignment); missing auth middleware on state-changing routes.
Method
- Run
cargo audit(known-vuln deps),cargo clippy, andsemgrep;miriforunsafeUB where feasible. rg 'unsafe|unwrap\(\)|expect\(|transmute|Command::new|format!\(.*(SELECT|INSERT|UPDATE)'→ review each.- Justify every
unsafeblock's invariant; if you can't, that's a finding. - Confirm exploitable injection/logic with the matching runtime skill.
Gotchas
- Idiomatic Rust is genuinely safe — don't invent memory bugs in safe code; spend effort on
unsafe, FFI, panics, deps, and logic. - Release-mode integer overflow wraps silently — a
checked_*/saturating_*audit matters for size/index math. cargo auditflags vulnerable crates you'd never see by reading — always run it.
References
Rustonomicon (unsafe); RustSec advisory DB / cargo-audit; Clippy lint set; Secure Rust Guidelines (ANSSI).