Overview
Start with the honest ledger. What memory safety buys: buffer overflows, use-after-free, double-free and data races — the bug classes behind a large share of historical CVEs — cannot occur in safe Rust, and with them goes the classic RCE route. What it doesn't: everything above the memory model — injection, broken authz, SSRF, CSRF, misconfiguration, dependency compromise — exists here exactly as in Java and Go. Rust's honest differentiators on that upper layer are structural: parameterization and escaping are the default paths of its libraries, and the type system makes several mistake shapes unrepresentable. The new liability it adds: panics and resource exhaustion as the ecosystem's characteristic DoS surface.
Key points
- Injection: sqlx/diesel are
parameterized by construction — sqlx's macros cannot interpolate values into SQL
text; string-built SQL requires visibly unusual code. Command injection: prefer
std::process::Commandarg arrays; never shell out with formatted strings. - XSS: the mainstream template engines auto-escape — askama (compile-time
templates, type-checked variables), maud (markup as Rust macros), tera
(runtime, Jinja-style) — with explicit
|safe-style opt-outs as the audit points; API-first services mostly render JSON and inherit the browser-side story. - CSRF:
SameSite=Lax/Strictcookies first (tower-sessions/axum-extra typed cookies); token-based via axum_csrf/actix equivalents when cross-site posts are real; bearer-token APIs are structurally immune, as everywhere. - CORS, headers, limits are tower-http layers:
CorsLayer(explicit origins — resist theAny+ credentials footgun; the types actually forbid that combo),SetResponseHeaderLayerfor CSP/HSTS/nosniff (no helmet-bundle crate has become standard — keep an internal preset),RequestBodyLimitLayer+TimeoutLayer+tower::limitfor request hygiene. - Sessions: tower-sessions with a server-side store (Redis, sqlx stores) — signed/encrypted cookie stores exist but server-side is the default posture; rotate on privilege change, same rules as everywhere.
- Panics are the Rust-flavored DoS: an
unwrapreachable from user input is a remote crash (and a task-poisoner under some architectures). Mitigations: clippy'sunwrap_usedlint on handler crates,catch_panic-style recovery layers at the boundary, and fuzzing the parsers — the testing side of this same coin. - serde-boundary hardening:
deny_unknown_fieldswhere strictness matters, bounded collection sizes (body limits do the heavy lifting), untagged-enum performance traps on hostile input, and#[serde(with)]validation at parse time — "parse, don't validate" is the idiom's native form here. - SSRF and path traversal: unchanged from the neighbours — allow-list outbound
URL construction (the
urlcrate for parsing, not string prefixes); tower-http'sServeDirhandles traversal for static files. - What's not here: no memory-unsafe deserialization gadget chains (no runtime
reflection — Java's deserialization nightmare has no Rust analog), and no
unsafe-free path to corruption; theunsafethat does exist is auditable and testable.
Details
The composed stack, sketched
let app = Router::new()
.merge(routes)
.layer(TimeoutLayer::new(Duration::from_secs(10)))
.layer(RequestBodyLimitLayer::new(1 << 20)) // 1 MiB
.layer(cors_layer()) // explicit origins
.layer(security_headers_layer()) // CSP, HSTS, nosniff
.layer(TraceLayer::new_for_http());
The same one-obvious-place property as Go's middleware stack — and the same silent failure mode if a layer is missing, which is why the internal-preset habit matters.
Related
- Security in Rust applications — the map — parent map.
- OAuth2 & OIDC in Rust — the identity perimeter around these concerns.
- REST in Rust — the serde/validation boundary this note hardens.
- AppSec fundamentals — the threat taxonomy.
- Web security in Java apps and in Go — the counterparts; same list, different defaults.