rust.sql_injection
Correctness
Critical
Common in Incidents
Detects SQL queries built with string formatting instead of parameterized queries.
Why It Matters
Section titled “Why It Matters”SQL injection remains a critical vulnerability:
- Data theft — Attackers read any data from your database
- Data destruction — DROP, DELETE, UPDATE at will
- Authentication bypass — Log in as any user
- Command execution — Some databases allow system commands
Even with Rust’s safety guarantees, SQL injection is still possible.
Example
Section titled “Example”// ❌ Beforelet query = format!("SELECT * FROM users WHERE id = {}", user_id);sqlx::query(&query).fetch_one(&pool).await?;
// Also badlet query = "SELECT * FROM users WHERE name = '".to_owned() + name + "'";// ✅ Aftersqlx::query("SELECT * FROM users WHERE id = $1") .bind(user_id) .fetch_one(&pool) .await?;
// Or with query macro (compile-time checked)sqlx::query!("SELECT * FROM users WHERE id = $1", user_id) .fetch_one(&pool) .await?;What Unfault Detects
Section titled “What Unfault Detects”format!()with SQL keywords- String concatenation building queries
query()with non-literal strings- Missing
.bind()calls
Auto-Fix
Section titled “Auto-Fix”Unfault can convert formatted queries to parameterized form with proper bindings when the transformation is unambiguous.
SQLx Best Practices
Section titled “SQLx Best Practices”// Compile-time checked queries (best)let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id) .fetch_one(&pool) .await?;
// Runtime queries with bindingslet users = sqlx::query_as::<_, User>("SELECT * FROM users WHERE status = $1") .bind(status) .fetch_all(&pool) .await?;
// Multiple bindingssqlx::query("INSERT INTO logs (user_id, action) VALUES ($1, $2)") .bind(user_id) .bind(action) .execute(&pool) .await?;Diesel Pattern
Section titled “Diesel Pattern”// Diesel uses type-safe query builderuse diesel::prelude::*;
users::table .filter(users::id.eq(user_id)) .first::<User>(&mut conn)?;