# Injection and parameterized queries

> Never build a query or command by pasting in user input; pass the input as a parameter instead.

Concept · Application & cloud security · https://levelcode.org/concepts/sql-injection

**Injection** happens when untrusted input is mixed into something that gets *interpreted*, such as a SQL query, a shell command, an LDAP filter or a template, so that the input changes the instruction instead of being treated as data.

```go
// ✗ Vulnerable: the input becomes part of the SQL
q := "SELECT * FROM users WHERE email = '" + email + "'"
// email = "' OR '1'='1"   → returns every user
// email = "'; DROP TABLE users; --"   → worse
```

## The fix: keep code and data separate

**Parameterized queries** (prepared statements) send the SQL text and the values separately. The database parses the query *before* it sees the values, so a value can never become SQL:

```go
row := db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE email = $1", email)
```

```python
cur.execute("SELECT id, name FROM users WHERE email = %s", (email,))
```

Escaping by hand isn't a fix. It's easy to get wrong for some encoding, quote style or context.

## Where it still gets through

- **Identifiers can't be parameters.** Table names, column names and `ORDER BY` direction can't be bound. Check them against an **allowlist**: `if sort not in {"created_at", "total"}: reject`.
- **ORM escape hatches.** Raw-query helpers, string-built `WHERE` fragments and `.extra()` calls bring the risk back.
- **Dynamic `IN` lists.** Use the driver's array support, such as `= ANY($1)` in PostgreSQL, rather than joining strings.

## Same bug, other interpreters

| Interpreter | Safe pattern |
|---|---|
| Shell | Run the program directly with an argument list: `exec.Command("convert", in, out)`, never `sh -c` with string building |
| HTML | Context-aware escaping in the template engine; see [XSS](https://levelcode.org/concepts/xss-and-csp) |
| NoSQL | Typed query builders; reject objects where you expect strings (`{"$ne": null}`) |
| LLM prompts | No full separation exists; see [prompt injection](https://levelcode.org/concepts/prompt-injection) |

Defence in depth limits the damage when a bug slips through. Give the app's database user [least privilege](https://levelcode.org/concepts/least-privilege) (no `DROP`, no access to other schemas), and let SAST tools flag string-built queries in review. The [secure coding lesson](https://levelcode.org/roadmaps/devsecops/secure-coding) covers the rest of the OWASP Top 10.
