Input validation and contextual output encoding
Two separate jobs: reject junk input at the door, then escape whatever you print so it cannot turn into code where it lands.
See it
What it is
These two jobs get mashed into one word, 'sanitization', and that mashup is where most injection bugs are born. Validation happens on the way in: is this an email, is this integer between 1 and 100, is this one of five allowed values. Allowlists, not blocklists. Encoding happens on the way out, and the correct escape depends entirely on the destination: one apostrophe needs different treatment in HTML text, an HTML attribute, a JavaScript literal, a CSS value, and a URL component.
SQL and shell commands are the other half of the story, and they are not an encoding problem. Do not escape your way into a query or a command line: bind parameters so the database parses your SQL and treats the value as data, and run programs through an argument array or an API that never spawns a shell at all. Reach for escaping there only when the platform gives you nothing else, and be suspicious when it does.
So: validate at every trust boundary, then hand each output to the layer that knows the context. JSX or your template engine's auto-escaping for HTML, encodeURIComponent for URL pieces, DOMPurify with an explicit allowlist for the rare field you really do render as HTML, parameterized queries for the database, and execFile or equivalent for subprocesses. The moment you build any of those by concatenating strings, you own the bug.
The misconception worth killing: validation does not replace encoding. O'Brien is a perfectly valid surname, and it will still break a hand-built SQL query and still execute if you drop it raw into an inline script tag. Also, client-side validation is a courtesy to users, not a control: anyone can post straight to the endpoint with curl, so the server validates or nothing does.
Ask AI for it
Harden this endpoint and its render path with input validation plus contextual output encoding, treated as two separate passes. On the way in: add a zod schema that validates every field server-side (type, length, format, allowlisted enum values) and rejects with a 400 before any business logic runs. On the way out: use parameterized queries for every database call with zero string concatenation, rely on JSX escaping for HTML text, encodeURIComponent for anything placed into a URL, and DOMPurify with an explicit tag and attribute allowlist for fields we intentionally render as HTML. Finish by listing each point where data crosses into a new context and which encoder now covers it.