Lukas' Notes

The web keeps being called cursed, and the reason is not aesthetic. It is that the platform was designed to mix code and data in the same string, then hand that string to an interpreter that decides which is which by parsing it. Almost every server-side injection is one shape of this mistake.

Consider four seemingly different vulnerabilities. Path traversal concatenates user input into a filesystem path that the kernel parses a level at a time. Command injection concatenates it into a shell string that the shell parses into commands. Code injection concatenates it into source that the host language evaluates. SQL injection concatenates it into a query that the database parses as SQL. The interpreters differ — kernel, shell, eval engine, SQL parser — but the act is identical: a value supplied as data is placed where an interpreter expects code.

The shared root cause explains why the defences look alike too. Prepared statements send a compiled template to the database and only then supply parameter values, so a quote in a value can never become SQL syntax. Avoiding eval and dynamic shell commands, and substituting an allow-listed value or a bound parameter for the concatenated slot, are the same idea wearing different clothes. In every case the fix enforces the boundary that string concatenation removed: the value is treated as data by a downstream interpreter that no longer re-parses it.

The subtlety is that block-list sanitisation — escaping the dangerous characters you already know about — defends the concatenation rather than removing it. Filtering ../ misses ..././; escaping single quotes misses the second-order variant when the data is echoed back later; removing ; from shell input misses backticks, &&, $(), and whatever the shell grows next. Sanitising a concatenated string keeps the code-data boundary inside the string and asks you to enumerate every metacharacter the interpreter cares about, which is exactly what you cannot do. Allow-listing and parameterisation move the value outside the parsed instruction stream entirely, so the set of dangerous characters stops mattering.

This is also why the threat keeps shifting. New interpreters enter the web stack — template engines, query-object builders for NoSQL, YAML, JSON-path, AI prompt scaffolds — and each one reopens the same seam: a value that arrives as data but is evaluated as code. The vulnerability class is old; only the parser changes.

The corrected mental model: do not ask “what characters should I escape?” Ask “is this value ever parsed as code?” If it is, the fix is to move it out of the parsed stream, not to scrub the stream of the characters it acts on.