Context-Based XSS Escapes
HTML entity context, JS string context, URL context, CSS context. Each context demands a different escape sequence — the attacker finds one; the defender must close all of them.
HTML entity context, JS string context, URL context, CSS context. Each context demands a different escape sequence — the attacker finds one; the defender must close all of them.
A single escaping strategy does not work everywhere. The characters that are dangerous in an HTML element body are different from the ones that break a JavaScript string. A URL context forbids javascript:protocols, which are perfectly safe in CSS. Understanding how the browser parses each context — and choosing the correct escape for each — is the difference between a secure application and one that fails to a creative payload.
When a web application inserts user-supplied data into a page, the data lands in one of four parsing contexts. The browser has different parsing rules for each:
< and > open and close elements. HTML entity encoding is the correct defence.> can close the tag silently. HTML entity encoding works, but attribute-specific validation (e.g. blocking event handlers) is also needed.<script> block or an event handler. The quote character and backslash are dangerous. JavaScript string escaping (backslash escapes) plus unicode escapes for non-ASCII is the correct approach.javascript: protocol executes JS. Protocol whitelisting plus URL encoding is required.<!-- HTML element context → entity encode < > & -->
<div>USER_INPUT escapes to &lt;img src=&gt;</div>
<!-- JavaScript string context → backslash-escape quotes and newlines -->
<script>var x = "USER_INPUT escapes to \" + \n + \t";</script>
<!-- URL context → protocol whitelist + URL-encode -->
<a href="USER_INPUT esc. to https%3A%2F%2Fsafe.com">link</a>
<!-- CSS context → CSS-escape hex values -->
<style>.cls { color: USER_INPUT\000020escaped }</style>Select a context and type in some input. The visualiser highlights the injection boundary — the exact point where your input enters the HTML template. Toggle context-aware escaping to see how the characters change when properly encoded for that specific context.
<div class="content">
<span class="text-ink-subtle/60">(your input)</span>
</div>The OWASP XSS Filter Evasion Cheat Sheet is the definitive living document that catalogs context-specific injection techniques. It documents payloads for every parsing context and tracks how browsers and sanitizers evolve around them. As browser vendors patch one vector, researchers document a new one, and the cheat sheet expands. It currently contains dozens of distinct payload classes, from basic HTML entity bypasses to esoteric SVG namespace injections that only work in specific browser versions.
The cheat sheet's existence underscores a fundamental truth: context-aware escaping is not a one-time fix but an ongoing discipline. Each new browser version, each new HTML specification feature (like<template> orShadowDOM), and each new framework introduces new parsing behaviours that an attacker can exploit if the defender is not encoding correctly for the specific context.
Most modern frameworks provide context-aware escaping automatically. React's JSX escapes HTML element and attribute contexts by default. Vue's template syntax does the same. The danger arises when developers opt out: dangerouslySetInnerHTML in React, v-html in Vue, andinnerHTML in vanilla JS. Each opt-out must be audited to ensure the data is either safe or correctly escaped for its target context.
For server-side templates, libraries like OWASP Java Encoder and Microsoft AntiXSS provide context-specific encoding functions. The guiding principle is: never concatenate user input into any structured language (HTML, JS, CSS, URL) without using a context-aware encoder.
// VULNERABLE - generic escaping for all contexts
function escape(input) {
return input.replace(/</g, '<').replace(/>/g, '>');
}
// This does NOT protect inside a JavaScript string!
// SAFE - context-specific escaping
function escapeJsString(input: string): string {
return input
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/'/g, "\\'")
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r');
}
function escapeUrl(input: string): string {
const allowed = ['http:', 'https:'];
try {
const url = new URL(input);
if (!allowed.includes(url.protocol)) return '#';
return encodeURI(input);
} catch { return '#'; }
}dangerouslySetInnerHTMLor v-html usage.1.Which escaping scheme is correct for user input placed inside a JavaScript string literal?
2.Why is HTML entity encoding insufficient for a URL (href) context?
3.What is the purpose of the OWASP XSS Filter Evasion Cheat Sheet?