Security basics every engineer needs path · Application flaws · Topic 8 of 16

Injection: keep data out of the code

Not started

Injection happens when untrusted input is pasted into a string that something else will interpret: a SQL query, a shell command, an HTML page, a template, an LDAP filter, a log line. If the input contains characters the interpreter treats as syntax, it changes the meaning of the command. Every fix comes down to the same rule: **send data as data**, through an API that keeps it separate from code.

Why it matters: Injection has been on every OWASP Top 10. It happens whenever untrusted text is glued into something an interpreter runs, and the fix is the same each time: keep data and code separate.

  1. 1The root cause

    A query, command or template is built by concatenating strings, so input can change its structure instead of just filling a value.

  2. 2Parameterise

    Send the query and the values separately. The database never parses the values as SQL, whatever they contain.

  3. 3Avoid the interpreter

    Call programs with an argument array instead of a shell string; use DOM APIs like textContent instead of innerHTML.

  4. 4Validate as a second layer

    Allow-list formats (an ID is digits, a sort column is one of three names). Validation helps, but it doesn't replace parameterisation.

1

How it works

The explanation, with examples you can run.

SQL: parameterise

Look at what a string-built query actually becomes when the input contains a quote:

⌘/Ctrl + Enter
OutputNot run yet

Press Run to see what your code prints, in order.

Runs in this browser, in an isolated frame with no access to this page. console.log, info, warn and error are shown; code runs as a classic script, so use an async function for await.

The second input merely breaks the query; the third turns the WHERE clause into one that is always true. With a parameterised query ($1, ? or :email, depending on the driver) the SQL text is fixed before any value arrives, so the database treats the whole input as one string to compare, whatever it contains.

Every mainstream driver and ORM supports parameters. The traps are the escape hatches: raw-query helpers, string templates passed to query(), and parts of SQL that can't be parameters, such as table names, column names and ORDER BY direction. For those, map user choices to a fixed allow-list in code.

Shell commands: skip the shell

exec("convert " + name) starts a shell, and the shell interprets ;, |, $( ) and backticks in name. Prefer a library that does the job directly. If you must run a program, use the form that takes an argument array (execFile/spawn in Node, subprocess.run([...]) in Python with shell=False), so no shell parses anything. Validate arguments and use -- before file names so a name like -rf isn't read as an option.

HTML: escape for the context

Cross-site scripting is injection into HTML or JavaScript. Use your framework's default escaping, textContent and setAttribute instead of innerHTML, and a Content Security Policy as a second wall. The browser security topic covers this in detail.

Other interpreters

The same pattern shows up in templates (server-side template injection when user input becomes a template rather than a value passed to one), NoSQL queries (a JSON body where a string was expected becomes an operator such as {"$ne": null}), LDAP and XPath filters, HTTP headers (newlines in values), and log files (newlines that forge log lines). The fix each time: the API's parameter mechanism, type checks on input shape, and encoding for the output context.

Layers

  1. Safe APIs that separate data from code: the main defence.
  2. Input validation on type, length and format, using allow-lists.
  3. Least privilege for the database user or process, so a mistake can do less.
  4. Tests and tools: static analysis that flags string-built queries, and tests that send quotes and metacharacters to every input.
2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Which line is safe from SQL injection?
    db.query("SELECT * FROM users WHERE email = '" + email + "'")   // Adb.query("SELECT * FROM users WHERE email = $1", [email])       // Bdb.query(`SELECT * FROM users WHERE email = '${email.replace("'", "")}'`) // C
  2. 2.You need to let users sort by a column name, which can't be a query parameter. What is the safe approach?
  3. 3.How should a program call `convert` with a user-supplied file name?

0 of 3 right so far. Get all three to finish this topic.