← Back to OWASP Top 10 for Web Applications
A03:20214 min read read

Injection

Prevent SQL injection, XSS, command injection, and other cases where untrusted input is interpreted as code.

*URL: trusteed.io/academy/web-security/injection* **Injection** occurs whenever untrusted data is sent to an interpreter — a SQL database, a shell, an HTML renderer, an LDAP directory, an OS command processor — as part of a command or query, without being properly separated from that data's role, allowing an attacker to alter the command's meaning. SQL injection and cross-site scripting (XSS) are the most widely known variants, but the same root cause spans command injection, LDAP injection, NoSQL injection, and template injection. ## What Is Injection? Injection happens when an application concatenates or interpolates untrusted input directly into a command string, rather than treating the input strictly as data. A classic SQL injection example: building a query with string concatenation (`"SELECT * FROM users WHERE id = " + userInput`) lets an attacker supply input that changes the query's actual structure, not just its parameters. Cross-site scripting works the same way in a different interpreter — inserting attacker-controlled input directly into HTML output lets that input execute as script in another user's browser. The fix in every case follows the same principle: separate the command structure from the data using parameterization, escaping, or context-aware output encoding — never string concatenation of untrusted input into an interpreted context. ## Why AI Coding Assistants Get This Wrong String concatenation is often the most immediately readable way to build a query or a shell command, and it's extremely common in quick examples, tutorials, and even production code written under time pressure — `db.query("SELECT * FROM users WHERE email = '" + email + "'")` reads naturally and works correctly for every normal input a developer tests with. Parameterized queries require slightly more boilerplate and a specific driver API, which an assistant will only reliably reach for if instructed that concatenation is disallowed rather than merely "not preferred." ## Vulnerable Pattern ```javascript // SQL injection — string concatenation into a query app.get('/api/users', async (req, res) => { const query = `SELECT * FROM users WHERE email = '${req.query.email}'`; const results = await db.raw(query); // attacker input: ' OR '1'='1 res.json(results); }); // Reflected XSS — untrusted input rendered directly into HTML app.get('/search', (req, res) => { res.send(`

Results for: ${req.query.q}

`); // attacker: }); // Command injection — user input passed to a shell const { exec } = require('child_process'); app.post('/api/convert', (req, res) => { exec(`convert ${req.body.filename} output.png`, callback); // attacker: "; rm -rf / #" }); ``` ## Secure Pattern ```javascript // Parameterized query — input is bound as data, never interpreted as SQL app.get('/api/users', async (req, res) => { const results = await db('users').where({ email: req.query.email }); // query builder parameterizes automatically res.json(results); }); // Context-aware output encoding — modern templating engines auto-escape by default app.get('/search', (req, res) => { res.render('search-results', { query: req.query.q }); // template engine escapes automatically }); // Avoid shell interpolation entirely — use an argument array, not a concatenated string const { execFile } = require('child_process'); app.post('/api/convert', (req, res) => { const filename = validateFilename(req.body.filename); // allow-list validated first execFile('convert', [filename, 'output.png'], callback); // arguments passed directly, no shell parsing }); ``` ## Copy-Paste Rules for Cursor, Claude Code, and Codex ```markdown --- description: Injection prevention — SQL, XSS, command injection, and interpreter safety alwaysApply: true --- # Injection Prevention Rules - Never build a SQL query (or any query language) using string concatenation or template literals with untrusted input. Always use parameterized queries, prepared statements, or a query builder/ORM that parameterizes automatically. - Never insert untrusted input directly into HTML output without context-aware escaping. Rely on the templating engine's automatic escaping rather than manually constructing HTML strings with interpolated input, and never disable auto-escaping for user-controllable content. - Never pass untrusted input to a shell interpreter (`exec`, `system`, backticks) via string concatenation. Prefer APIs that accept arguments as an array (`execFile`, `spawn`) so input is never parsed by a shell. - Validate untrusted input against an explicit allow-list of expected formats (e.g., filenames, identifiers) before using it in any sensitive context, in addition to parameterization — defense in depth, not either/or. - For NoSQL databases, apply the same discipline: never construct query objects by directly merging unvalidated user input into operators like `$where` or `$gt` without validation. - When generating any code that constructs a query, command, or markup string, default to the safe/parameterized API for that context — treat string concatenation into an interpreter as disallowed, not merely discouraged. ``` ## Verification Checklist - [ ] All database queries use parameterization, prepared statements, or an ORM/query builder — no raw string concatenation - [ ] Output rendered into HTML relies on automatic, context-aware escaping; auto-escaping is never disabled for user-controllable content - [ ] Any shell command execution uses argument-array APIs, never string-interpolated shell commands - [ ] User input used in file paths, filenames, or system commands is validated against an explicit allow-list - [ ] NoSQL query construction validates input types before merging into query objects - [ ] Automated and manual testing includes injection payloads across all input surfaces — see [Trusteed's vulnerability scanner](https://trusteed.io/vulnerability-scanner) and [WAAP](https://trusteed.io/waap) for continuous injection testing

Frequently asked questions

Is XSS still relevant if a framework like React auto-escapes output by default?

Yes — modern frameworks significantly reduce risk by escaping by default, but explicit "danger" APIs (`dangerouslySetInnerHTML` in React, `v-html` in Vue) bypass that protection intentionally, and injection risk returns wherever those APIs are used with untrusted input.

What's the difference between input validation and parameterization — do I need both?

Yes, both — parameterization prevents the input from being interpreted as command syntax, which is the core defense. Input validation (allow-listing expected formats) is a complementary layer that catches unexpected input early and reduces the attack surface, but doesn't replace parameterization on its own.

Can a WAF fully prevent injection attacks?

A WAF can block many known injection patterns at the network edge, providing valuable defense in depth, but it operates on signature matching and can be bypassed by novel encoding or obfuscation — secure coding at the application layer (parameterization, escaping) remains the primary defense, not the WAF alone.