Security · XSS · Guide

HTML Entities & XSS

HTML entity encoding is the single most effective defense against XSS in HTML contexts — but only if you use it in the right place. Here is how to think about escaping by context.

Unsafe (name comes from user input)
<div>Hello, ${name}!</div>
Safe (entity-encoded)
<div>Hello, ${encodeHtml(name)}!</div>

What XSS Really Is

Cross-site scripting happens when an attacker's text ends up executed as code in another user's browser. The usual vehicle is HTML:<script>fetch('/steal?c=' + document.cookie)</script>inserted into a comment or a profile field, rendered by a server that forgot to escape it.

Entity Encoding Breaks the Vehicle

Replacing < with &lt;, > with &gt;, & with &amp;, and the two quote characters with &quot; and &#39; turns markup into literal text. The browser renders it, but never parses it as HTML — the attack becomes a visible string instead of a running script.

Context Matters More Than You Think

HTML entities protect only HTML text and most attribute values. Different contexts need different escaping:

  • HTML body / text — HTML entity encode (< > & " ')
  • HTML attribute (quoted) — HTML entity encode; always quote your attributes
  • URL in href / src — URL-encode the value and validate the scheme (block javascript:)
  • Inside <script> — JavaScript string escape (\u00XX) or render data as JSON
  • Inline CSS — allow a strict whitelist only; never substitute untrusted strings
  • Inside <style> — same as inline CSS; prefer separate stylesheets

Templates Already Do This — Mostly

React, Vue, Svelte, Angular, Handlebars, Jinja2 (with autoescape), Mustache ({{name}}), and ERB (<%= h name %>) all HTML-escape text interpolations by default. The holes are:

  • React's dangerouslySetInnerHTML, Vue's v-html, Svelte's {@html}
  • Writing to element.innerHTML directly in JavaScript
  • Attribute values interpolated without quoting
  • href="${url}" without scheme validation (allows javascript:)
  • Templates that disable autoescape (don't)

Content Security Policy Buys You Depth

Even with perfect escaping, a strict CSP (script-src 'self', no 'unsafe-inline') limits the damage from any bug that slips through. Think of entity encoding as the fence and CSP as the moat; use both.

Common Mistakes

  • Encoding on input instead of on output — same string ends up in HTML, JSON, logs, and the DB, all with different escaping needs
  • Double-encoding — the browser already decodes once, so &amp;amp; renders as &amp; instead of &
  • Trusting a sanitizer to strip "bad" tags; keep an allowlist, don't guess what is bad
  • Thinking Base64 is encoding for safety — it isn't. See Base64 explained.

Try It

Encode or decode HTML entities instantly with our free HTML Entity Encoder. For URLs, use the URL Encoder.

Encode on output

Not on input — same string lives in HTML, JSON, logs with different needs.

Context-specific

HTML, attribute, URL, JS, CSS all need different escaping.

CSP as depth

Strict Content-Security-Policy limits any slip-ups.