29 · Database
SQL Injection Protection
Interpolations in sql"..." are always bound parameters — SQL metacharacters in the value are literals, never executed.
On this page
SQL injection occurs when an external value is concatenated into the query
string before reaching the database. Using sql"...", Zolo never concatenates:
every interpolation {expr} becomes a positional ? and the value is sent
separately by the driver. The database receives the query structure on one channel
and the data on another — SQL metacharacters in the value are treated as text,
not as code.
The classic payload '; DROP TABLE users; -- is inserted as a variable in a
sql"..." query. The table remains intact and no rows are found.
// Feature: `sql"..."` interpolations are *bound parameters*, not concatenated
// Syntax: `sql"SELECT ... WHERE x = {evil}"` — at compile time the
// `{evil}` becomes `?` and the value is sent as a parameter. SQL
// metacharacters in the value are literal, not parsed.
// When to use: ALWAYS. Never build queries with `+` or interpolation
// outside `sql"..."`. This file demonstrates why.
use std::database::Database
let db = Database.open("sqlite://:memory:")?
defer db.close()
db.execute(sql"CREATE TABLE users (name TEXT)")?
db.execute(sql"INSERT INTO users VALUES ('alice')")?
db.execute(sql"INSERT INTO users VALUES ('bob')")?
// The classic injection payload — in a string-concat builder it
// would terminate the query, drop the table, and comment out the rest.
// Inside `sql"..."` it's just a string parameter that doesn't match.
let evil = "'; DROP TABLE users; --"
let rows = sql"SELECT name FROM users WHERE name = {evil}".query(db)?
var count = 0
for _ in rows { count = count + 1 }
print("matched: {count}")
// expected: matched: 0
// Proof: the table is still there, both rows intact.
let total = sql"SELECT COUNT(*) FROM users".scalar(db)?
print("total: {total}")
// expected: total: 2
db.query("SELECT * FROM users") ?> .each(|u| print(u.name))
Requires the Zolo CLI/host — open in the playground or run locally.
The practical rule is simple:
- Use
sql"..."(or?+ bindings) always when a value comes from outside the source code. - Never build queries with
+or with interpolation outside thesql"..."literal.
Challenge
Try building the same query without sql"..." using string concatenation and
observe what happens to the table. What error message does Zolo emit?