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 URL, JavaScript, SQL, and a shell command.
Validate at every trust boundary, then hand encoding to the layer that knows the context: parameterized queries for SQL, JSX or your template engine's auto-escaping for HTML, encodeURIComponent for URL pieces, and DOMPurify with an explicit allowlist for the rare field you really do render as HTML. The moment you build any of those by concatenating strings, you own the escaping 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.