XSS Prevention with HTML Encoding
What Is XSS?
Cross-site scripting (XSS) happens when an application includes untrusted data in a web page without proper escaping. An attacker injects a script into a page viewed by other users, stealing cookies, hijacking sessions, or performing actions on behalf of the victim.
XSS consistently ranks among the OWASP Top 10. It is not a server vulnerability — it is an output vulnerability. The problem occurs at the moment data leaves your application and enters the browser.
How HTML Encoding Stops XSS
HTML encoding converts characters that have structural meaning in HTML into safe entity references:
<script>alert('xss')</script>
→ <script>alert('xss')</script>
The browser now renders the encoded characters as visible text instead of interpreting them as markup. The script never executes.
This works because entity resolution happens before the browser builds DOM nodes. Once < becomes <, it can never retroactively become a tag opener.
Context Matters: One Encoding Does Not Fit All
HTML entity encoding only protects you inside HTML text content and quoted attribute values. It fails in every other context.
HTML text content — safe with entity encoding
<p>Hello, {{ username }}</p>
<!-- If username = "<script>alert(1)</script>" -->
<!-- After encoding: <p>Hello, <script>alert(1)</script></p> -->
<!-- Renders as text. Safe. -->
HTML attribute values — safe only when quoted
<!-- SAFE: quoted attribute -->
<input value="{{ encoded_data }}">
<!-- DANGEROUS: unquoted attribute -->
<input value={{ encoded_data }}>
<!-- A space in the data breaks out of the value -->
Always quote your attributes. Entity encoding inside unquoted attributes is not reliable protection.
JavaScript context — entity encoding does NOT work
<!-- DANGEROUS: entity encoding inside script tags -->
<script>
var name = "{{ encoded_data }}";
<!-- encoded_data = "'; alert(1); //" -->
<!-- After encoding, the semicolons and quotes remain valid JS -->
</script>
For JavaScript output contexts, use JSON serialization with proper Unicode escaping:
// Correct: use JSON.stringify for JS context
var name = <%= JSON.stringify(userInput) %>;
CSS context — entity encoding does NOT work
<!-- DANGEROUS -->
<div style="background: url({{ encoded_data }})">
<!-- An attacker can inject expression() or url() payloads -->
Never put user data in CSS style attributes. Use CSS classes instead.
URL context — use URL encoding, not HTML encoding
<!-- CORRECT: URL-encode data values -->
<a href="/search?q={{ url_encoded_data }}">Search</a>
The Five Output Contexts
| Context | Required Escaping | Example |
|---|---|---|
| HTML text | HTML entity encoding | <p>{{ encoded }}</p> |
| HTML attribute (quoted) | HTML entity encoding | <input value="{{ encoded }}"> |
| JavaScript | JSON serialization + Unicode escape | var x = <%= JSON.stringify(data) %> |
| CSS | Avoid user data entirely; use classes | <div class="{{ encoded_class }}"> |
| URL parameter | URL encoding (encodeURIComponent) | href="/page?q={{ url_encoded }}" |
If you encode for the wrong context, you have a vulnerability regardless of how thoroughly you encoded.
Defense in Depth
HTML encoding is your primary defense, but it should not be the only one.
Content Security Policy (CSP)
A CSP header tells the browser which scripts can execute. Even if an XSS payload slips through, a strict CSP blocks inline scripts:
Content-Security-Policy: default-src 'self'; script-src 'self'
Input validation
Validate and sanitize input before storing it. Reject unexpected characters early. Encoding is your output defense; validation is your input defense.
HTTP-only cookies
Mark session cookies as HttpOnly so JavaScript cannot read them. If an XSS attack executes, the attacker cannot steal the session token.
Framework auto-escaping
Modern frameworks escape by default:
| Framework | Auto-escaping mechanism | Raw output syntax |
|---|---|---|
| Vue | {{ }} interpolations | v-html directive |
| React | JSX expressions | dangerouslySetInnerHTML |
| Angular | Template bindings | [innerHTML] binding |
| Svelte | { } expressions | {@html} tag |
Use raw output directives only when you explicitly trust the content. Every v-html or dangerouslySetInnerHTML is a potential XSS vector.
Common XSS Encoding Mistakes
- Encoding once then decoding — if your pipeline decodes the data after encoding, the protection disappears
- Using the wrong encoding for the context — HTML entity encoding inside a
<script>block does nothing - Trusting sanitized HTML — HTML sanitization libraries have bypasses; combine with CSP
- Forgetting event handler attributes —
onclick,onerror, and similar attributes execute JavaScript; entity encoding inside them does not prevent execution - Assuming your framework handles everything — framework auto-escaping only covers template interpolations, not dynamic CSS or URL construction
Key Takeaways
- HTML entity encoding prevents XSS in HTML text content and quoted attributes only
- You must match the encoding to the output context — HTML, JS, CSS, and URL each need different escaping
- Never put user data in
<script>blocks using entity encoding; use JSON serialization instead - Defense in depth: combine encoding with CSP, input validation, and HttpOnly cookies
- Use your framework's auto-escaping; avoid raw output directives unless you fully trust the content
- Event handler attributes (
onclick,onerror) remain dangerous even with entity encoding
Related Guides
Try It Yourself
Encode user-supplied data safely with our free HTML Encoder/Decoder. Paste any string and get properly entity-encoded output — verify your escaping before you ship.