Articles

Input Validation vs Sanitization vs Escaping

Validation rejects bad input, sanitization removes the dangerous parts, and escaping neutralizes them for a specific output context. Most bugs mix these up.

Chisato Chisato · · 5 min read
A stylized digital security shield icon

Validation, sanitization, and escaping are three distinct defenses against untrusted input, and they operate at different points in a request’s lifecycle: validation decides whether input is acceptable at all, sanitization strips or transforms the dangerous parts of input you’ve decided to accept, and escaping neutralizes special characters for a specific output context right before you use them. Treating any one of the three as a substitute for the others is one of the most common root causes behind SQL injection and XSS bugs that pass code review.

Validation: does this input meet the shape you expect?

Validation checks that input conforms to an expected format, type, length, or range — and rejects it outright if it doesn’t. It happens as early as possible, ideally right at the system boundary where untrusted data enters your application.

  • Is this string a syntactically valid email address?
  • Is this number within the range your business logic allows?
  • Is this file under the size limit, and does its content actually match its claimed MIME type?
  • Does this JSON payload match the expected schema?

Validation is binary: input either passes or it’s rejected, typically with an error returned to the caller. It doesn’t try to fix or clean anything — a value that fails validation should never be silently modified into something that passes; that’s sanitization’s job, not validation’s, and conflating the two hides bugs. Allowlisting (defining what’s acceptable) is almost always safer than denylisting (trying to enumerate what isn’t), because a denylist has to anticipate every dangerous pattern, while an allowlist just has to describe what’s actually valid.

Sanitization: cleaning input you’ve decided to keep

Sanitization takes input you’re going to accept and store or process, and removes or transforms the parts that are dangerous in the contexts you know it will end up in. Unlike validation, sanitization doesn’t reject — it modifies. A classic example is stripping disallowed HTML tags from user-submitted rich text: you want to keep <b> and <a href>, but strip <script> tags and on* event attributes, leaving the rest of the content intact.

Sanitization is inherently context-aware and harder to get right than it looks. A denylist of “dangerous” tags and attributes is fragile — browsers have a long history of parsing malformed or obscure markup in ways that let a blocked pattern slip through in a different form. This is why battle-tested, actively maintained sanitization libraries exist and why rolling your own HTML sanitizer is a common source of stored XSS vulnerabilities: the library’s job is to track every edge case a browser’s parser might exploit, which is not a one-time exercise.

Escaping: safe for one specific output context

Escaping transforms special characters into a representation that’s inert in a particular output context, without changing the data’s meaning or removing anything. It happens as late as possible — right before the data is written into that context — because the correct escaping depends entirely on where the data is going:

  • HTML body context — escape <, >, &, ", ' to their entity equivalents so a value like <script> renders as literal text instead of being parsed as a tag.
  • HTML attribute context — escaping requirements differ from the HTML body; an unescaped quote inside an attribute value can break out of the attribute entirely.
  • SQL context — use parameterized queries (prepared statements) rather than string escaping wherever possible; escaping SQL by hand is notoriously easy to get subtly wrong, and a parameterized query sidesteps the whole problem by never mixing data into the query string in the first place.
  • URL context — percent-encode characters that are meaningful in a URL, or they can alter the path or query string an attacker controls.
  • JavaScript string context — escaping for HTML doesn’t protect a value interpolated into an inline <script> block; that needs JavaScript-string escaping instead.

The critical point is that escaping is not one operation — it’s a family of operations, one per output context, and using the wrong one for the context you’re actually writing into provides no protection at all. This context-specificity is exactly why templating engines that auto-escape based on where a value is being inserted are safer defaults than manual string concatenation.

Why the distinction actually matters

ValidationSanitizationEscaping
What it doesAccepts or rejects inputRemoves/transforms dangerous partsNeutralizes for one output context
When it runsAt the input boundaryBefore storing/processingRight before output
Changes the data?No — rejects, doesn’t modifyYes — strips or transformsYes — encodes, doesn’t remove
Context-dependent?Depends on business rulesDepends on what you’re keepingDepends entirely on output sink
Typical failure modeDenylist misses an edge caseBypassable via parser quirksWrong escaping for the sink used

A concrete failure pattern: an application validates that a “name” field contains only letters and spaces, concludes the input is therefore “safe,” and writes it unescaped into an HTML page. Validation did its job — the name really is just letters and spaces — but that was never a defense against injection in the first place, because the vulnerability wasn’t in what characters the input contained; it was in writing untrusted data into an HTML context without escaping. Validation constrains what data you accept; escaping protects how you use it. They answer different questions, and satisfying one tells you nothing about the other.

Applying all three together

A well-defended input path uses all three at the layer where each one belongs:

  1. Validate at the boundary — reject malformed, oversized, or out-of-range input before it enters your system at all.
  2. Sanitize anything you’re accepting that has structure you need to preserve but partially distrust, like rich text — strip what shouldn’t be there.
  3. Escape at every output boundary, using the encoding appropriate to that specific sink — HTML body, HTML attribute, SQL (via parameterization), URL, or JavaScript string.

None of the three is optional because the others exist. A field that passes strict validation still needs to be escaped on output; sanitized HTML still needs contextual escaping if any of it is later reinterpolated elsewhere; and escaping at output never excuses skipping validation at input, since unvalidated data can still cause business-logic problems that have nothing to do with injection.

The takeaway

Validation, sanitization, and escaping solve different problems and belong at different points in the data’s path: validate to reject what you shouldn’t accept, sanitize to clean what you’ve decided to keep, and escape — using the encoding specific to the destination — right before untrusted data reaches any output context. Treating one as a substitute for the others, or applying the wrong context’s escaping, is how technically “validated” input still ends up causing an injection vulnerability.

The Lycoris Team The Lycoris Team · · 5 min read

Thundering Herd Problem (Cache Stampede) Explained

The thundering herd problem hits when a cached value expires and every waiting request floods the backend at once. Here's why it happens and how to stop it.

#Web Development #Performance #Backend
Takina Takina · · 4 min read

HTTP Content Negotiation Explained

Content negotiation lets a client and server agree on a response's format, language, or encoding using Accept headers, before any body is sent.

#Web Development #Networking #Backend
Chisato Chisato · · 4 min read

Trusted Types API: Stopping DOM XSS at the Sink

The Trusted Types API blocks DOM-based XSS by forcing risky DOM sinks like innerHTML to accept only vetted objects instead of raw strings.

#Security #Web Development #JavaScript