Output Encoding
Now the defences — and the first one is the most important. Output encoding (also called output escaping) is the primary, foundational defence against XSS: transform untrusted data, at the moment it's written into a page, so the browser treats it as inert data rather than executable code. Get this right and the vast majority of XSS simply cannot occur, because nothing attacker-supplied is ever interpreted.
The Principle: Encode on Output, for the Context
Two words carry the whole chapter. "On output": encode at the point data is inserted into the page, not when it's received. "For the context": use the encoder matching where the data lands (Chapter 3 — HTML, attribute, JS, URL, CSS). Encoding converts the characters that would break out of the current context into safe equivalents the browser displays literally.
<); (3) it conflates storage with presentation. Best practice: store the raw, original input; encode at each output site for that site's context. Input validation still has a role (Chapter 7) but it is not the XSS defence — output encoding is.
The Five Encoders
| Context | Encode | Example transform |
|---|---|---|
| HTML body | HTML entity encode | < > & → < > & |
| HTML attribute (quoted) | attribute encode | also " ' → " ' |
| JavaScript string | JS string encode | " → \x22, / → \/, hex-escape |
| URL (parameter) | URL encode + scheme allowlist | encodeURIComponent; reject javascript: |
| CSS value | CSS encode / allowlist | avoid; restrict to known-safe values |
The non-negotiable rule from Chapter 3 holds: the encoder must match the context. HTML-encoding a value that lands in a JavaScript string or a URL scheme does not protect it.
In Practice: Don't Hand-Roll It
You rarely write these encoders yourself — and shouldn't. Use established, context-aware tools:
- Template engines auto-encode by default — Handlebars
{{value}}, EJS<%= value %>, Jinja/Twig, Razor. The raw forms ({{{value}}},<%- %>) bypass it — those are the danger spots to audit. - Frameworks auto-encode interpolated values — React
{value}, Vue{{ }}, Angular — covered in Chapter 9. - Libraries for manual cases — OWASP Java Encoder, the
helibrary (Node), Python'smarkupsafe.
Encoding vs Sanitization — Not the Same Thing
A crucial distinction that the next chapter expands: encoding makes data display literally (good when the value should be shown as text — a username, a comment, a search term). Sanitization (Chapter 7) removes dangerous parts while keeping some markup functional (needed when the value is meant to be HTML — a rich-text comment). Use encoding by default; reach for sanitization only when you genuinely must render user-provided HTML.
<b>, HTML-encoding it produces the literal text "<b>" on the page — correct for safety, wrong for the feature. That's the signal you have an HTML-output requirement, which needs sanitization, not encoding (or better, a non-HTML markup format like Markdown rendered safely). Don't respond to "the tags show up as text" by disabling encoding — that reintroduces XSS. The two tools solve different problems; pick by whether the value is text or markup.
Why Encoding Is the Foundation
Recall Chapter 4: blocklist filtering loses because the attacker has infinite payload variants. Output encoding wins for the mirror-image reason — it doesn't try to recognize bad input; it neutralizes a small, closed set of breakout characters for the current context, so every variant of every payload is rendered inert at once. It's complete by construction, not by enumeration. That's why it's the primary defence, with sanitization (Ch. 7) and CSP (Ch. 8) layered on top — defence in depth, but encoding is the load-bearing wall.
Hands-On Exercises
Explain "encode on output, for the context" and give two concrete reasons encoding at output time is better than encoding/storing data at input time. Include the double-encoding and "unknown context" problems.
📄 View solutionTake the value "><img src=x onerror=alert(1)> and show what correct encoding produces in (a) an HTML body context and (b) a quoted HTML attribute context. Confirm why neither executes, and what the browser displays.
Distinguish encoding from sanitization. For each requirement, say which to use and why: (a) display a user's chosen display-name; (b) render a rich-text comment that allows <b> and <a>; (c) show a search query back to the user. Explain why using the wrong tool either breaks the feature or reintroduces XSS.
Chapter 6 Quick Reference
- Output encoding = transform untrusted data on insertion so the browser treats it as data, not code — the primary XSS defence
- Two rules: encode on output (at insertion, not input) and for the context (the right encoder for where it lands)
- Store raw, encode late — input-time encoding causes double-encoding bugs & can't know the eventual context
- Five encoders: HTML body · HTML attribute (quoted) · JS string · URL (+scheme allowlist) · CSS — must match the context
- Don't hand-roll — use auto-encoding templates/frameworks (audit the raw escape hatches) or libraries (
he, OWASP Encoder) - Encoding ≠ sanitization: encoding shows data literally (text); sanitization keeps safe markup (HTML) — use encoding by default
- If encoded HTML "shows as tags," you have an HTML-output need → sanitize (Ch. 7); don't disable encoding
- Encoding is complete by construction (closed set of breakout chars), the mirror of why blocklists fail — the foundation
- Next chapter: sanitizing HTML & input validation — DOMPurify, allowlists, and where validation fits