flawopen.com/SQL Injection/Rust
Imagine a form that only ever expects a ticket number, like 482. SQL injection is what happens when someone types something sneaky into that box instead of a number — a trick phrase that makes the system say "show me every ticket" instead of just ticket 482, because the system never checked that what it received was actually just a number.
SQL injection happens when user-controlled input gets inserted directly into a database query's text. In Rust, the trap is the format! macro — the idiomatic, safe-looking way to build almost any string in the language — used to build a query instead of passing the value through the database crate's own parameter binding.
In 2015, UK telecom TalkTalk suffered a breach affecting over 150,000 customers after attackers exploited a SQL injection flaw in a legacy web page inherited through a company acquisition. The UK's data protection regulator fined TalkTalk £400,000, describing the failure as preventable and basic.
Source: UK Information Commissioner's Office enforcement notice, 2016 — see References below.// user_id comes straight from the request
let query = format!(
"SELECT * FROM users WHERE id = {}",
user_id
);
conn.execute(&query, [])?;
// value bound, never formatted into the query
conn.execute(
"SELECT * FROM users WHERE id = ?1",
params![user_id],
)?;
The placeholder ?1 tells the database crate to bind user_id as a separate parameter at the protocol level — the query's structure is fixed before the value is attached. format! can't provide this: it's pure string construction with no awareness that its output will be interpreted as SQL.
Just as Python's f-strings and Go's Sprintf are idiomatic everywhere else in those languages, format! is Rust's default reach for string building — nothing about its syntax signals when it's being used to build something dangerous.
sqlx::query! validates a query's shape against your actual database schema at compile time — a genuinely distinctive Rust feature — but this check only runs against the placeholders you give it. Building the SQL string with format! first and passing the result to a plain query() call sidesteps that protection entirely.
Rust's ownership and borrow checker guarantee memory safety at compile time — they say nothing about whether a runtime string happens to be a syntactically valid, attacker-controlled SQL fragment. These are unrelated properties enforced by unrelated mechanisms.
Rust's safety guarantees are about memory (no null pointers, no data races, no buffer overflows) — SQL injection is a logic bug about string construction, a category Rust's type system doesn't address by default.
They validate that a parameterized query matches your schema — they don't retroactively make an unparameterized, format!-built query safe.
grep -rn "format!(" --include="*.rs" . | grep -i "select\|insert\|update\|delete"
grep -rn "conn.execute(&query\|conn.query(&query" --include="*.rs" .
No — a String that happens to contain an injected SQL fragment is still a perfectly valid, well-typed String. Type safety and query-construction safety are independent properties.
Diesel's query builder parameterizes by default, similar to an ORM in other languages. Its sql_query() raw-SQL escape hatch carries the same risk as any other language's raw-query method if built with format!.