Part II β The Shield Β· Lesson 3.9
Input Validation and Sanitization
In one lineValidate shape on the way in, escape by context on the way out β they are different jobs.
Where you've seen itA rich-text comment editor that must preserve <b> and links while dropping <script> and every on* attribute.
Diagram
THE PIPELINE β store raw, transform at the boundary
user input
β
βΌ
ββββββββββββββββ reject if wrong TYPE, LENGTH, FORMAT, RANGE
β VALIDATE β "is this an email under 254 chars?"
ββββββββ¬ββββββββ β fail β reject with a clear message
β β pass
βΌ
ββββββββββββββββ store the ORIGINAL, unescaped
β PERSIST β (escaping on input corrupts data and breaks
ββββββββ¬ββββββββ the day you render it somewhere new)
β
βΌ
ββββββββββββββββ HTML? URL? SQL? shell? each escapes differently
β ESCAPE for β this is where injection is actually prevented
β the SINK β
ββββββββββββββββVALIDATE vs SANITIZE β not interchangeable
VALIDATE accept or reject, unchanged email, age, uuid, enum
fail closed on anything unknown
SANITIZE accept and modify rich text HTML only
allowlist tags + attributes, drop everything else
Use sanitization ONLY where markup is genuinely required.
Everywhere else, validate and escape.ALLOWLIST beats BLOCKLIST β always
β blocklist strip "<script>" β <ScRiPt>, <img onerror>, <svg onload>
the bypass list is infinite
β allowlist keep [b,i,em,strong,a,p,ul,li]
keep attrs [href, title]
drop everything else, including anything invented next yearHow it works
- Validate on both sides. Client for feedback, server as the gate β the client's copy can be bypassed entirely.
- Validate by type, not by pattern-matching bad input: schema libraries (Zod, Yup, JSON Schema) reject anything that doesn't fit.
- Store the raw value. Escaping at input time means you escape twice when it's later rendered into a different context.
- Escape at the sink β HTML-escape for the DOM, URL-encode for query strings, parameterise for SQL, never build shell commands from user data.
- Normalise before validating (trim, Unicode NFC) so equivalent inputs don't slip through a length or uniqueness check.
Trade-offs
| Rule | Why |
|---|---|
| Fail closed | Unknown input is rejected, not "cleaned" |
| One schema, both sides | Client and server can't drift apart |
| Limit length everywhere | Cheapest defence against payload and ReDoS abuse |
| Sanitize only rich text | Every other field just needs escaping |
Interview angle
"You sanitize on input. Why is that not enough?"
- The safe form depends on the destination β the same string needs different treatment in HTML, a URL, and SQL.
- Escaping early corrupts the stored data and double-escapes when rendered elsewhere.
- Correct order: validate shape on input, store raw, escape at each output sink.
Recap
- Validation rejects; sanitization modifies. Only rich text needs the second.
- Allowlists survive attacker creativity; blocklists don't.
- Escape at the sink, because the sink defines what "safe" means.