KB Building XSS prevention

XSS prevention researched

Every XSS bug is the same shape: attacker-craftable input reaches the page in a context where it can run as code. Encoding closes that gap; CSP is only the backup. This is the mechanics reference behind the security baseline.

Use this when

You are writing custom JavaScript, accepting URL/form/CMS input into a page, rendering author-supplied HTML (WYSIWYG), or reviewing someone else's embed before it ships.

Definition of done

No untrusted value is interpolated into a <script> tag, event handler, or eval()-family sink. Framework auto-encoding is intact and every escape hatch is justified. Any author-supplied HTML passes through DOMPurify. A strict CSP backs it up (see Content Security Policy).

The three shapes of XSS

An XSS attack tricks a site into executing attacker code within its own origin, subverting the same-origin policy. Once it runs it can read and modify all page content and storage, and make authenticated requests as the user — impersonation and data theft. Every variant depends on the same two conditions: the site accepts craftable input, and it puts that input in a page without ensuring it can't run.

TypeWhere it's injectedMechanism
Reflected (client-side)A URL parameter reflected straight into the pageUnsanitized input assigned to an unsafe sink like innerHTML. Fires per crafted link.
Stored / persistentServer-side, during templatingUntrusted input is stored, then served unsanitized to every visitor — the most severe class.
DOM-basedClient-side, entirely in the browserDangerous Web APIs (innerHTML, document.write(), eval()) called with unsanitized input. Never touches the server, so a WAF can't see it.

Defense is layered — there is no single fix

OWASP is explicit: no single technique solves XSS. The durable defense stacks three layers, in this order of reliance:

  1. Let the framework auto-encode. React/JSX and Django escape interpolated values by default. The whole risk is the escape hatches — audit every one: React dangerouslySetInnerHTML, Angular bypassSecurityTrustAs*, Lit unsafeHTML.
  2. Encode by output context wherever you step outside the framework (table below).
  3. Sanitize HTML you must render — when encoding would break legitimate author markup.

CSP sits underneath all three as a backup that blocks injected scripts even if they reach the page — it is not a substitute for any layer above. See Content Security Policy.

Output encoding by context

The correct escaping differs by where the data lands — encoding for an HTML body is wrong for a JS string or a URL. Match the rule to the context:

ContextEncoding rule
HTML bodyConvert & < > to entities
HTML attributeQuote the value; encode all chars in &#xHH; form
JavaScriptOnly place data in quoted values; encode as \uXXXX
CSSHex-encode (\XX); only ever in property values
URLPercent-encode parameters only

Some contexts are effectively unsafe at any encoding. Never interpolate untrusted data into <script> tags, HTML comments, CSS selectors, event handlers (onclick), or eval()/setTimeout(). Prefer safe sinks: .textContent, .setAttribute() with a hardcoded name, createTextNode().

Sanitizing author HTML

When users legitimately author HTML (a WYSIWYG editor, rich CMS field), encoding it would break the formatting they intend. Sanitize instead with DOMPurify, which OWASP endorses:

const clean = DOMPurify.sanitize(dirty);
element.innerHTML = clean;

On Chromium you can go further with Trusted Types, which forces every DOM sink through a vetted policy — turning an entire class of DOM-based XSS into a load-time error rather than a runtime exploit.

Gotchas

WAFs do not catch DOM-based XSS. The payload never reaches the server, so a firewall can't inspect it. Code review of custom JS is non-negotiable regardless of what's in front of the site.

Webflow exports are static. You get no server-side templating, so nonce-based CSP isn't available — encoding and sanitization in any custom embed carry more of the load there. See Embeds & custom code.

OWASP — the reference body

OWASP (the Open Worldwide Application Security Project) is the volunteer non-profit that publishes the field's de-facto standards. When scoping or justifying security work, these are the documents to cite:

Why & sources

Full cited write-up: Web security for client sites, distilled from MDN — Cross-Site Scripting, the OWASP XSS Prevention Cheat Sheet, and OWASP Foundation. For the backup layer see Content Security Policy; for the ship gate see the security baseline.