~/VibeHandbook
$39

Chapter 18 · 02

Injection: when input becomes code

Injection is the oldest and still most common serious bug. It happens when data from a user gets treated as instructions instead of data. Two flavors matter for most apps.

injection — SQL (Structured Query Language) is the language apps use to ask a for data — is when user input slips into a database query. The classic vulnerable pattern:

// VULNERABLE: the user's input is glued straight into the query
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query(`SELECT * FROM users WHERE name = '${name}'`);
});
// If someone passes  name = '; DROP TABLE users; --
// your query becomes a command to delete the table.

The fix is a parameterized query (also called a prepared statement). You send the query and the data separately, so the database never confuses one for the other:

// SAFE: the value is passed as a parameter, never as code
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query("SELECT * FROM users WHERE name = ?", [name]);
});

The rule: never build a query by string concatenation. If you see backticks or + assembling SQL around a variable in a diff, stop and ask for the parameterized version.

The two paths put the same user input into the database, but only one keeps the database from mistaking that input for commands:

  user input:  '; DROP TABLE users; --
        │
        ├─────────────────────────┬───────────────────────────┐
        ▼                         ▼                            
  ✗ CONCATENATED            ✓ PARAMETERIZED                    
  "...WHERE name=             "...WHERE name = ?", [input]      
     '" + input + "'"          (query and data sent separately)
        │                         │
        ▼                         ▼
  ┌───────────────┐         ┌───────────────┐
  │   DATABASE    │         │   DATABASE    │
  │ reads input   │         │ treats input  │
  │ AS COMMANDS   │         │ as DATA only  │
  │  → table gone │         │  → safe lookup │
  └───────────────┘         └───────────────┘
       BREACH                    SAFE

XSS (cross-site scripting) is the same idea in the browser. If you take user input and drop it into a page as raw HTML (HyperText Markup Language, the code that tells a browser what to display), an attacker can inject a <script> tag that runs in your other users' browsers — stealing their sessions, for example. The fix is escaping: render user content as text, not HTML. Modern frameworks like React escape by default, which is great, until the AI reaches for an escape hatch like dangerouslySetInnerHTML or innerHTML to "make it work." Those bypass the protection. Treat any such call in a diff as a thing to question.

The unifying principle behind every injection bug: keep data and code separate. Whenever user input crosses into a query, a command, a template, or HTML, something must escape or parameterize it.

Want it offline?

Get the PDF + EPUB + downloadable prompt library + version updates.

$ Get the PDF — $39