JavaScript: preventing XSS by escaping output
Turn special HTML characters into entities so user text is shown as text, never run as markup.
const entities = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
function escapeHtml(text) {
return text.replace(/[&<>"']/g, (ch) => entities[ch]);
}
const comment = '<img src=x onerror="alert(1)">';
console.log("<p>" + comment + "</p>");
console.log("<p>" + escapeHtml(comment) + "</p>");
console.log("<p>" + escapeHtml("Tom & Jerry") + "</p>");<p><img src=x onerror="alert(1)"></p> <p><img src=x onerror="alert(1)"></p> <p>Tom & Jerry</p>
Explanation
Cross-site scripting (XSS) happens when a page inserts untrusted text into HTML without encoding it. An attacker submits a comment such as an img tag with an onerror handler, and the browser runs the script in every visitor's session. The fix is to encode output for the context it is inserted into.
For HTML text and attribute values, the characters &, <, >, double quote, and single quote are replaced by entities, so the browser shows them instead of interpreting them. The better habit is to use safe APIs such as textContent, or a template system that escapes by default, and to add a Content-Security-Policy as defence in depth. Hand-written escaping is shown here to make the mechanism visible.
Explore the dry run
const entities = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }; function escapeHtml(text) { return text.replace(/[&<>"']/g, (ch) => entities[ch]);} const comment = '<img src=x onerror="alert(1)">';console.log("<p>" + comment + "</p>");console.log("<p>" + escapeHtml(comment) + "</p>");console.log("<p>" + escapeHtml("Tom & Jerry") + "</p>");Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Each dangerous character has a safe entity. | — | — | |
| Every match is replaced by its entity. The ampersand must be handled too, or the entities themselves would be damaged. | — | — | |
| Unescaped, the browser would parse this as an image tag with a script handler. | — | <p><img src=x onerror="alert(1)"></p> | |
| Escaped, the same text is displayed as harmless characters. | — | <p><img src=x onerror="alert(1)"></p> <p><img src=x onerror="alert(1)"></p> | |
| Ordinary text is correct HTML after escaping. | — | <p><img src=x onerror="alert(1)"></p> <p><img src=x onerror="alert(1)"></p> <p>Tom & Jerry</p> |
Cost and common mistakes
O(m) for a string of m characters.
- Escape for the context. Text inside a script block, a URL, or a CSS value needs different encoding from HTML text.
- Escape when you output, not when you store, and use a vetted library or framework escaping instead of your own where you can.
- innerHTML with user input is a common source of XSS. Prefer textContent.
Try it yourself
Why is the ampersand escaped as well?
Reveal answer and reasoning
An unescaped & could begin an entity written by an attacker. Escaping it ensures that the browser shows the characters literally.