Escape HTML Entities
How to escape HTML entities to prevent XSS attacks in Python, Java, and JavaScript.
Overview
HTML entity escaping converts characters with special meaning in HTML (<, >, &, ", ') into their corresponding entity references (<, >, &, ", '). Without escaping, untrusted data can inject markup or scripts, leading to cross-site scripting (XSS). Below is a practical approach to HTML escaping in Python, JavaScript, and Java.
When to Use
Use this resource when:
- Rendering user-generated content inside HTML templates
- Building live HTML strings from external data (APIs, databases, files)
- Generating HTML emails that include recipient names or addresses
- Embedding JSON data inside
<script>tags safely
Solution
Python
# html.escape (Python 3.2+)
import html
user_input = '<script>alert("xss")</script>'
safe = html.escape(user_input)
print(safe)
# Output: '<script>alert("xss")</script>'
# MarkupSafe for Jinja2 templates (automatic escaping)
# pip install markupsafe
from markupsafe import Markup, escape
def render_comment(text):
return Markup('<p>{}</p>').format(escape(text))
JavaScript
// Manual entity map for lightweight escaping
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, char => map[char]);
}
const userInput = '<img src=x onerror=alert(1)>';
console.log(escapeHtml(userInput));
// Output: '<img src=x onerror=alert(1)>'
// Using DOM API in browser environments
function escapeHtmlDom(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
Java
// Apache Commons Text StringEscapeUtils
// Maven: org.apache.commons:commons-text
import org.apache.commons.text.StringEscapeUtils;
public class HtmlEscaper {
public static String escape(String input) {
return StringEscapeUtils.escapeHtml4(input);
}
}
// OWASP Java Encoder
// Maven: org.owasp.encoder:encoder
import org.owasp.encoder.Encode;
public class SafeHtml {
public static String escapeForBody(String input) {
return Encode.forHtml(input);
}
public static String escapeForAttribute(String input) {
return Encode.forHtmlAttribute(input);
}
}
Explanation
HTML escaping is a context-specific encoding. In the body of an HTML element, < must become < so browsers treat it as literal text, not the start of a tag. Inside an HTML attribute delimited by double quotes, " must become " to prevent the attribute from closing early. Inside a <script> block, additional JavaScript encoding is needed because </script> can terminate the script context even if HTML-escaped.
Python’s html.escape covers the five critical characters. MarkupSafe is the engine behind Jinja2’s auto-escaping and is battle-tested. In JavaScript, manual replacement with a regex is sufficient for most cases; the DOM API approach is safer but only works in browsers. Java’s StringEscapeUtils handles HTML4 entities thoroughly, while OWASP Encoder provides fine-grained context control.
Variants
| Technology | Library / Approach | Context | Notes |
|---|---|---|---|
| Python | html.escape | HTML body | Stdlib, covers < > & " ' |
| Python | markupsafe.escape | Templates | Used by Jinja2, auto-escapes by default |
| JavaScript | Manual regex | HTML body | Lightweight, no dependencies |
| JavaScript | DOM textContent | HTML body | Browser only, handles all entities |
| Java | StringEscapeUtils.escapeHtml4 | HTML body | Apache Commons, covers many entities |
| Java | Encode.forHtml | HTML body + attributes | OWASP, context-specific variants |
What Works
- Escape at the point of rendering, not at storage: Escaped data in a database makes search and display inconsistent
- Use auto-escaping template engines: Jinja2, Django templates, React JSX, and Vue templates escape by default
- Context matters: HTML body, HTML attribute, CSS, JavaScript, and URL contexts each require different encoding rules
- Avoid
innerHTMLwith raw strings: UsetextContentor template literals with escaping functions - Audit third-party components: Libraries that bypass escaping (e.g.,
dangerouslySetInnerHTMLin React) must be reviewed carefully
Common Mistakes
- Escaping too early: Sanitizing on input and storing escaped text breaks full-text search and sorting
- Double escaping:
<rendered again becomes&lt;, displaying literal<to users - Wrong context encoding: HTML-encoded strings are unsafe inside JavaScript contexts without additional JS encoding
- Using
innerHTMLfor user text: Even if the source is “trusted,”innerHTMLis unnecessary and risky; prefertextContent - Ignoring attribute context:
href="{{ userUrl }}"needs URL encoding, not just HTML encoding
Advanced Solutions
Context-aware escaping for script context
Embedding JSON data inside <script> tags requires more than HTML escaping. The sequence </script> terminates the script block regardless of HTML entity encoding:
function safeJsonInScript(data) {
// 1. Stringify the JSON
let json = JSON.stringify(data);
// 2. Escape the forward slash in </script> sequences
json = json.replace(/</g, '\\u003c');
// 3. Also escape <!-- to prevent HTML comment injection
json = json.replace(/-->/g, '--\\u003e');
return json;
}
// Usage in a server-rendered template
const userData = { name: 'John</script><script>alert(1)</script>', role: 'admin' };
const safeJson = safeJsonInScript(userData);
// Output: {"name":"John\\u003c/script>\\u003cscript>alert(1)\\u003c/script>","role":"admin"}
import json
import re
def safe_json_for_script(data):
"""Serialize JSON safe for embedding in <script> tags."""
json_str = json.dumps(data)
# Escape <, >, and line separators to prevent script context breakout
json_str = json_str.replace('<', '\\u003c')
json_str = json_str.replace('>', '\\u003e')
json_str = json_str.replace('\u2028', '\\u2028') # Line separator
json_str = json_str.replace('\u2029', '\\u2029') # Paragraph separator
return json_str
# Flask/Jinja2 usage
@app.route('/dashboard')
def dashboard():
user_data = {'name': 'Alice', 'permissions': ['read', 'write']}
return render_template('dashboard.html',
safe_data=safe_json_for_script(user_data))
URL context escaping
URLs in href and src attributes need URL encoding, not just HTML escaping. Using javascript: URIs, attackers can execute scripts:
from urllib.parse import quote, urlparse
def safe_url(url):
"""Validate and sanitize URLs for href attributes."""
# Reject javascript: and data: schemes
parsed = urlparse(url)
if parsed.scheme not in ('http', 'https', 'mailto', 'tel', ''):
return '' # Reject dangerous schemes
# Re-encode the URL safely
return quote(url, safe=':/?&=%#')
# Usage
user_url = 'javascript:alert(1)'
print(safe_url(user_url)) # Output: '' (rejected)
user_url2 = 'https://example.com/path?q=test'
print(safe_url(user_url2)) # Output: 'https://example.com/path?q=test'
function safeUrl(url) {
try {
const parsed = new URL(url, window.location.origin);
const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
if (!allowedProtocols.includes(parsed.protocol)) {
return '';
}
return parsed.href;
} catch {
return '';
}
}
// Usage in DOM
const link = document.createElement('a');
link.href = safeUrl(userInput);
if (link.href) {
document.body.appendChild(link);
}
CSS context escaping
Injecting user data into CSS requires its own encoding. Unescaped data can break out of the CSS context and inject markup:
import org.owasp.encoder.Encode;
public class SafeCss {
// For CSS string context
public static String forCssString(String input) {
return Encode.forCssString(input);
}
// For CSS URL context
public static String forCssUrl(String input) {
return Encode.forCssUrl(input);
}
}
// Usage: <div style="color: {{userColor}}">
String safeColor = SafeCss.forCssString(userInput);
// Escapes backslash, quotes, angle brackets, and newlines
Go html escaping in templates
package main
import (
"html"
"html/template"
"net/http"
)
func renderTemplate(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.New("page").Parse(`
<h1>{{.Title}}</h1>
<p>{{.Content}}</p>
<a href="{{.URL}}">Link</a>
`)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := struct {
Title string
Content string
URL string
}{
Title: "<script>alert(1)</script>",
Content: "User <b>comment</b> & more",
URL: "https://example.com",
}
// html/template auto-escapes by context
tmpl.Execute(w, data)
}
// Manual escaping with html.EscapeString
func manualEscape(s string) string {
return html.EscapeString(s)
}
React dangerouslySetInnerHTML safe wrapper
When you must render HTML in React, wrap it with sanitization:
import DOMPurify from 'dompurify';
function SafeHtml({ html, ...props }) {
const cleanHtml = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
ALLOW_DATA_ATTR: false,
});
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} {...props} />;
}
// Usage
function Comment({ text }) {
// If text is plain text, use children (auto-escaped)
// If text contains HTML from a trusted source, use SafeHtml
return <SafeHtml html={text} />;
} Frequently Asked Questions
What is the difference between HTML escaping and HTML sanitization?
Escaping transforms every special character into an entity reference, preserving the original text but making it inert. Sanitization removes or alters dangerous markup (e.g., stripping <script> tags) while preserving safe HTML like <b>. Escape when you do not need HTML; sanitize when you accept a subset of HTML.
Do I need to escape data inside JSON responses?
No. JSON responses are not HTML contexts. Escape JSON only when embedding it inside an HTML page, such as in a <script> tag or an HTML attribute. In those cases, escape the JSON string for the HTML context, and if inside <script>, also avoid </script> sequences.
Should I escape single quotes (') or just double quotes (")?
Escape both. In HTML attributes, single quotes can delimit attributes (attr='value'), so unescaped single quotes break out of the attribute. The OWASP Encoder escapes both by default. Python's html.escape escapes single quotes when quote=True (default since Python 3.8).
Related Resources
Sanitize User Input
How to sanitize and validate user input in Python, Java, and JavaScript to prevent injection attacks.
RecipeParse Markdown Files
How to parse Markdown to HTML and extract structured data in Python, Java, and JavaScript.
PatternVoucher Pattern
Validate claims and delegate access using signed vouchers without exposing sensitive data. A security pattern for token-based authorization between services.
PatternMulti-Tenant Data Isolation Pattern
Isolate tenant data in shared infrastructure using row-level security, schema-per-tenant, or database-per-tenant strategies. A pattern for SaaS applications.
PatternFederated Identity Pattern
Delegate authentication to external identity providers. A pattern for integrating OAuth2, OIDC, SAML, and SSO across multiple services and organizations.