HTML Entity Cheat Sheet: Resolve Unescaped Quotes
The Vulnerable Form vs The Sanitized One: A Tale of Two Submissions
Picture two contact forms on the same server. Same CSS. Same backend. The first one accepts a user's name field and renders it directly into the admin dashboard: <h1>John</h1> becomes a styled heading. The second form passes that same input through htmlspecialchars() before rendering, so <h1>John</h1> becomes <h1>John</h1> — visible as literal text on the screen.
The difference between these two forms is the difference between a patched vulnerability and an open door. One is a working XSS vector. The other is a properly encoded output. This cheat sheet exists because most developers know they "should encode things," but far fewer know exactly which entities to use, where to use them, and — critically — where their encoding strategy silently fails.
Why Generic Encoding Advice Falls Short
Most developer tooling documentation says "escape your output" and moves on. That advice is technically correct and practically useless. Encoding for an HTML body context requires a different entity set than encoding for a JavaScript string literal inside an event handler. Encoding for a URL attribute requires a different approach than encoding for a CSS property value. If you apply body-context encoding to a JavaScript context, you leave the door open.
Body Context: The Five Entities That Actually Matter vs The Thirty That Don't
Let's draw a line. On one side: a developer who memorizes all 252 named HTML entities. On the other: a developer who knows exactly which five entities close 99% of XSS vectors in body context.
The second developer is more secure. Here's why.
In HTML body context — meaning content rendered between opening and closing tags like <p>, <div>, or <td> — only five characters can break out of the current context and inject markup:
| Character | Named Entity | Numeric Entity | Why It Matters |
|---|---|---|---|
| < | < | < | Opens new tags |
| > | > | > | Closes existing tags |
| & | & | & | Starts entity references |
| " | " | " | Closes attribute values |
| ' | ' | ' | Closes single-quoted attributes |
That's the complete set for body context. If your template engine encodes these five characters, body-context XSS is closed. The other 247 named entities — ©, , — — exist for typography and display, not security.
The Pitfall: Over-Encoding vs Under-Encoding
A common mistake in custom sanitization tools: encoding " and ' in body context where they're harmless, while forgetting to encode & because "it's just an ampersand." The ampersand is the one that enables entity injection. If a user submits <script> and your encoder doesn't first encode the ampersand, the browser decodes < back to < during parsing — and you're back to a working injection vector.
The fix is order-dependent. Encode ampersands first, then the remaining four characters. Most modern libraries handle this correctly, but if you're building a custom encoder in your toolchain, this sequence matters.
Attribute Context: Where Body Encoding Works vs Where It Breaks
Consider two implementations of a search results page. Implementation A renders user input into a paragraph: <p>Results for: {query}</p>. Implementation B renders the same input into an attribute: <input value="{query}">.
Both use the same encoder. Implementation A is safe. Implementation B is vulnerable.
Here's the attack: a user submits a search query containing " onfocus="alert(1). In body context, the quotes are rendered as literal characters. In attribute context, those quotes break out of the value and inject a new event handler. The encoder that only handles body context fails here because attribute context requires the same five entities — but the encoder must actually be applied in the attribute context, not just the body.
URL Attributes: The Double-Encoding Trap
When user input lands in an href attribute, developers often reach for encodeURIComponent() first, then HTML-encode the result. This is correct. The pitfall is the reverse order: HTML-encoding first, then URL-encoding. If you HTML-encode & to & and then URL-encode the result, you get %26amp%3B — a mangled string that breaks links and still passes through the browser's URL parser in unexpected ways.
The rule: URL-encode for the URL context first, then HTML-encode the result for the attribute context. Two layers, applied in the correct order.
JavaScript Context: Named Entities vs Backslash Escaping
Here's where most cheat sheets go wrong. They list HTML named entities and imply they work everywhere. They don't work in JavaScript.
A developer reads a cheat sheet, sees that < encodes a less-than sign, and writes:
<script>var name = "{userInput}";</script>
They encode the user's input using HTML entities. The browser parses the <script> block, and during script parsing, HTML entity decoding does not occur. So if a user submits </script><script>alert(1)</script>, the HTML encoder turns it into </script>... — but the HTML parser inside the script element stops at the first literal </script> sequence, which the encoder didn't touch because it was focused on the JavaScript string.
The correct approach for JavaScript string context is backslash escaping:
<→\x3C>→\x3E"→\x22'→\x27\→\\/→\/(optional but recommended)- Newline →
\n - Carriage return →
\r - Tab →
\t
These are not HTML entities. They are JavaScript string escapes. The distinction matters because the browser applies different parsing rules depending on context, and applying the wrong encoding layer gives a false sense of security.
Inside Event Handlers: The Triple-Layer Problem
When user input lands inside an event handler attribute like onclick="doSomething('{userInput}')", you need three encoding layers: JavaScript string escaping first, then HTML attribute encoding for the outer attribute context. Miss either layer and you have an injection point. This is why modern frameworks moved toward addEventListener in JavaScript rather than inline event handlers — it eliminates the triple-layer problem entirely.
Common Typography Entities: Display vs Security
Not all entities exist for security. Many exist because keyboards can't easily produce certain characters. A developer building a CMS or content tool needs both categories, but confusing them leads to either missing typography or unnecessary encoding overhead.
Typography Entities You'll Actually Use
| Entity | Character | Use Case |
|---|---|---|
| Non-breaking space | Prevent unwanted line breaks |
— | — | Em dash for parenthetical breaks |
– | – | En dash for ranges |
… | … | Horizontal ellipsis |
© | © | Copyright symbol |
™ | ™ | Trademark symbol |
® | ® | Registered trademark |
° | ° | Degree symbol |
€ | € | Euro currency |
£ | £ | British pound |
« | « | Left angle quote |
» | » | Right angle quote |
These entities are for rendering. They do not prevent XSS. A common pitfall in beginner sanitization code is assuming that converting < to < is the same category of operation as converting © to ©. They serve entirely different purposes and should be treated as separate concerns in your toolchain.
Numeric Entities: Decimal vs Hexadecimal vs Named
Every named entity has a numeric equivalent. < is < is <. All three render identically in every modern browser. The question is which form your tooling should standardize on.
Named entities are more readable in source code. Numeric entities are more reliable in edge cases — specifically when you're dealing with characters that don't have named equivalents, or when you're operating in a parser that doesn't recognize the full HTML5 entity set. The HTML5 specification defines 2,231 named character references. Most developers use fewer than 30.
The Decimal vs Hex Decision
Decimal numeric entities (—) and hexadecimal entities (—) produce identical output. Hex is shorter for large code points. Decimal is more familiar to developers who think in ASCII tables. Neither has a security advantage. Pick one and standardize across your codebase — mixed usage makes audits harder.
UTF-8 vs Entity Encoding: When You Don't Need Entities At All
Here's a scenario that surprises developers who inherited older codebases. A legacy CMS encodes every non-ASCII character as an HTML entity — é for é, “ for ", ’ for '. This made sense in 2003 when many servers served Latin-1 and browsers couldn't reliably decode UTF-8.
In 2024, with UTF-8 as the dominant encoding for 98.2% of all web pages (according to W3Techs), entity-encoding non-ASCII characters for display is unnecessary overhead. Your database stores UTF-8. Your server sends UTF-8 headers. Your HTML declares <meta charset="utf-8">. The browser renders UTF-8 correctly. Entity-encoding a curly quote in this environment adds bytes to the response without adding safety.
The exception: email templates. Some email clients still mangle UTF-8 in subject lines or certain HTML contexts. For email, entity-encoding non-ASCII characters remains a pragmatic defense.
What Still Needs Encoding in a UTF-8 World
Even with full UTF-8 support, the five security-critical characters (< > & " ') still need encoding in their respective contexts. UTF-8 solves the display problem. It does not solve the parsing problem. A < in UTF-8 is still a tag-opening character to the HTML parser. Encoding for display and encoding for security are separate concerns, and conflating them is one of the most common conceptual errors in developer tooling.
Building Your Encoder: The Checklist
If you're building a tool that processes user input — a markdown parser, a template engine, a form library, a CMS — here's the encoding checklist that covers the contexts most tools actually encounter:
- Body context: Encode
& < > " 'using named or numeric entities. Encode ampersand first. - Attribute context (double-quoted): Same five characters. Ensure the encoder is actually applied to attribute values, not just body content.
- Attribute context (single-quoted): Same five characters. The
'entity is critical here — it's the one developers most often forget. - URL attribute context:
encodeURIComponent()first, then HTML attribute encoding. Never reverse the order. - JavaScript string context: Use backslash escapes (
\x3C,\x22, etc.), not HTML entities. - JavaScript string inside HTML attribute: Backslash-escape for JS context, then HTML-encode for attribute context. Two layers.
- CSS context: Use CSS-specific escaping (
\00003Cformat). HTML entities do not work in CSS.
The pattern across all seven contexts: each context has its own encoding scheme, and applying the wrong scheme gives you a false sense of security while leaving the vulnerability intact. The cheat sheet isn't a list of entities to memorize. It's a map of contexts and their corresponding escape sequences, with the knowledge that crossing contexts without switching encoding strategies is the most common failure mode in developer tooling.
Frequently Asked Questions
What are HTML entities and why are they used?
HTML entities are special codes used to represent reserved characters or characters that cannot be easily typed on a keyboard. They are essential for displaying text correctly in browsers and preventing syntax errors when coding HTML.
How do HTML entities prevent XSS (Cross-Site Scripting) attacks?
HTML entity encoding neutralizes malicious scripts by converting dangerous characters like < and > into safe text representations like < and >. This ensures that user input is treated as plain text by the browser rather than executable code, forming a core defense against XSS vulnerabilities.
What is the difference between HTML encoding and URL encoding?
HTML encoding replaces special characters with HTML entities to ensure safe rendering on a webpage, while URL encoding translates characters into a format that can be safely transmitted over the internet. Both are necessary for web security, but they serve entirely different purposes in data transmission and rendering.
What are the most important HTML entities for web security?
The most critical HTML entities for security involve the less-than (<), greater-than (>), ampersand (&), and quotation marks (" and '). Encoding these characters prevents attackers from breaking out of HTML contexts and injecting malicious tags or attributes.
What is the difference between named, decimal, and hexadecimal HTML entities?
Named entities use descriptive words like ©, decimal entities use numbers like ©, and hexadecimal entities use hex values like © to represent the same characters. While all three work in modern browsers, decimal and hexadecimal formats are often preferred for security encoding because they cover the entire Unicode spectrum.
How do I escape HTML characters in JavaScript?
To escape HTML characters in JavaScript, you can use a simple string replace function or a dedicated library like DOMPurify to handle complex inputs. Replacing characters like <, >, and & with their respective HTML entities before injecting user input into the DOM is crucial for preventing client-side injection attacks.
Does HTML entity encoding prevent SQL injection?
No, HTML entity encoding does not prevent SQL injection because it is designed for browser rendering, not database querying. To prevent SQL injection, you must use parameterized queries or prepared statements in your backend code, rather than relying on frontend HTML encoding.
Where can I find a complete HTML entity cheat sheet?
A complete HTML entity cheat sheet provides a comprehensive list of named characters, numeric codes, and their visual representations for easy reference. Developers frequently use these lists to quickly look up symbols, mathematical operators, and security characters needed for proper text encoding.
When should I use HTML entities vs. UTF-8 character encoding?
You should use UTF-8 character encoding for general text storage and transmission, as it natively supports almost all global languages. However, you still must use HTML entities for reserved HTML characters like < and & to prevent parsing errors and maintain web security.
What is context-aware output encoding in web security?
Context-aware output encoding means applying the correct type of encoding based on where user input is placed in the document, such as HTML body, JavaScript, or CSS. Using HTML entities is correct for HTML contexts, but you need JavaScript or CSS specific escaping when placing data inside those respective tags.