Skip to content

rust.sql_injection

Correctness Critical Common in Incidents

Detects SQL queries built with string formatting instead of parameterized queries.

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.

// ❌ Before
let query = format!("SELECT * FROM users WHERE id = {}", user_id);
sqlx::query(&query).fetch_one(&pool).await?;
// Also bad
let query = "SELECT * FROM users WHERE name = '".to_owned() + name + "'";
// ✅ After
sqlx::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?;
  • format!() with SQL keywords
  • String concatenation building queries
  • query() with non-literal strings
  • Missing .bind() calls

Unfault can convert formatted queries to parameterized form with proper bindings when the transformation is unambiguous.

// 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 bindings
let users = sqlx::query_as::<_, User>("SELECT * FROM users WHERE status = $1")
.bind(status)
.fetch_all(&pool)
.await?;
// Multiple bindings
sqlx::query("INSERT INTO logs (user_id, action) VALUES ($1, $2)")
.bind(user_id)
.bind(action)
.execute(&pool)
.await?;
// Diesel uses type-safe query builder
use diesel::prelude::*;
users::table
.filter(users::id.eq(user_id))
.first::<User>(&mut conn)?;