Validation & Injection
Never trust the client — schema validation at the boundary, parameterised queries below it.
40 minDifficulty 2/5security · apiAI-writtenWritten by a model on 16 August 2026 and not yet read by a person. Checked automatically: schema, the pedagogical rules the hand-written material is held to, and every diagram parsed for real.
Before this
Why this exists
A login form concatenates a username directly into a SQL query: `"SELECT * FROM users WHERE name = '" + name + "'"`. Someone types `' OR '1'='1` as the username, and the query becomes `WHERE name = '' OR '1'='1'` — true for every row, logging them in as the first user in the table. This isn't a clever hack; it's the direct, predictable consequence of trusting a string the client sent.
The mental model
Every piece of data crossing a trust boundary — from the client, from a third-party API, from a file upload — is untrusted until proven otherwise. Validation checks the SHAPE and content are acceptable (is this actually an email, is this number in range); sanitization or parameterization prevents the data from being interpreted as CODE or COMMANDS by whatever processes it next, which is a related but distinct concern.
How it works
Parameterized queries eliminate SQL injection structurally
`db.query('SELECT * FROM users WHERE name = $1', [name])` sends the query structure and the user's value SEPARATELY to the database driver — the value is never interpreted as part of the SQL syntax, no matter what characters it contains. This isn't about 'escaping dangerous characters' correctly; it removes the entire class of vulnerability by never concatenating untrusted data into a query string at all.
Schema validation at the boundary catches malformed data before it goes anywhere
Validating a request body against a schema — this field must be a string, that one an integer between 1 and 100 — as the very first thing a handler does means every line of code after that point can trust the data's shape, rather than each downstream function needing its own defensive checks for the same thing.
Cross-site scripting (XSS) is injection into HTML, not SQL
Rendering user-provided text directly into HTML — `innerHTML = comment.text` — lets a comment containing `<script>stealCookies()</script>` execute as real JavaScript in every other visitor's browser who views that comment. The fix is the same PRINCIPLE as SQL injection: never let untrusted data be interpreted as executable syntax (HTML/JS here, SQL there) — use text-only insertion (`textContent`) or a library that escapes HTML-significant characters.
Validate on the server even when the client already validated
Client-side validation (see `forms-validation`) is bypassable by design — anyone can send a raw HTTP request directly to the API, skipping the browser and any JavaScript validation entirely. The server is the actual trust boundary, and it's the only validation that can't be circumvented, because it's the last line standing between untrusted input and whatever the server does with it.
The mechanism
Data arriving at a server boundary is checked against a defined schema before any business logic runs — wrong shape, wrong type, or out-of-range values are rejected immediately with a clear error. Data destined for a query, a shell command, or an HTML page is never concatenated directly into that target's syntax; instead, it's passed through a mechanism (parameterized queries, an escaping function) that keeps it strictly as DATA, never as executable structure.
What people get wrong
- Escaping single quotes in user input is sufficient protection against SQL injection.
- Manual escaping is fragile and easy to get subtly wrong (different databases and encodings have different escaping rules), whereas parameterized queries eliminate the vulnerability structurally by never treating user data as part of the query syntax in the first place. History is full of 'escaping' implementations that missed an edge case (a different quote character, an encoding trick) — parameterization removes the need to get this exactly right by removing the mechanism entirely.
- Input validation is only about preventing malicious attacks.
- Validation also catches entirely innocent mistakes — a typo'd email, a negative number where only positive makes sense, a missing required field — that would otherwise cause confusing downstream errors or silently corrupt data, with no attacker involved at all. Framing validation as purely a security measure undersells its role in basic data integrity and error messaging, which matters even in a system with zero adversarial users.
- If the frontend only lets users select from a dropdown of valid options, the backend doesn't need to validate that field.
- A request can be crafted and sent directly to the API, bypassing the frontend UI entirely — the backend has no way to know a request actually came through the dropdown rather than a hand-crafted request with an arbitrary value in that field. This is the input-validation-specific instance of the broader principle that the client is never a trust boundary, restated in `forms-validation` — the UI constraining choices is a UX nicety, not a security control.
When not to use it
- Data is being inserted into a shell command rather than a SQL query.
- Avoid shell string concatenation entirely — use an API that passes arguments as an array (never interpreted by a shell) rather than building a command string, which prevents shell injection the same way parameterized queries prevent SQL injection.
- The data is coming from an internal, fully trusted service you control end-to-end, with no external input path.
- Validation can be lighter here, though 'fully trusted' is worth double-checking — a service that later gains any external-facing input path retroactively needs the same rigor.
Terms
- SQL injection
- — A vulnerability where untrusted input is concatenated into a SQL query and interpreted as query syntax rather than data, allowing an attacker to alter the query's logic.
- Parameterized query
- — A query where the structure and the data values are sent separately to the database, preventing the values from ever being interpreted as SQL syntax.
- Cross-site scripting (XSS)
- — A vulnerability where untrusted input is rendered as executable HTML/JavaScript in a browser, letting an attacker's script run in another user's session.
- Trust boundary
- — A point where data crosses from a domain you don't control (the client, an external API) into one you do — every trust boundary requires validating the data as untrusted.
In an interview
Why is `db.query("SELECT * FROM users WHERE id = " + userId)` dangerous even if the frontend only ever sends a numeric ID?
- the frontend's behaviour doesn't constrain what a request actually sent to the API can contain
- a request can be crafted directly (bypassing the frontend) with a malicious string instead of a number
- string concatenation lets that malicious string be interpreted as SQL syntax, altering the query's meaning
Can you recall it?
Why does a parameterized query prevent SQL injection structurally, rather than just making it harder?
Connected ideas
Also part of
This idea matters in more than one area — which is usually why it matters.