Text parsing · · updated
Client-side HTML/XML escaping against DOM-based XSS
DOM-based XSS happens when attacker-controlled strings reach a sink like innerHTML, document.write, or a URL assigned to script.src. HTML entity encoding converts < to < so a browser text parser will not open a tag. The correct default in UI is textContent. Encoders exist to produce snippets for other contexts, still locally. Use the HTML entity encoder and decode with the entity decoder.
OWASP DOM XSS sinks
innerHTML, outerHTML, insertAdjacentHTML, eval, setTimeout(string), location, href on script/iframe. Sources: location, postMessage, datasets. Zero-server does not remove XSS in the live tab. A merge tool that previews filenames with innerHTML is a bug even if the PDF never uploaded.
Minimum escape set
In HTML text: < > &. In attributes: quotes. In JS string context: a different encoder entirely. One function cannot serve all contexts. Log lines from the regex filter are a frequent innerHTML footgun.
function escapeHtml(s) {
return s.replace(/[&<>"']/g, (c) =>
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
}Showing encoded output safely
Put the encoded string in textContent of a pre. If you innerHTML the encoded form, you are testing the encoder with a sink. Easy to get backwards. JWT claims named name or html are the same class — decode locally and still escape before display.
Markdown to HTML
marked plus a sanitizer is the pattern. Markdown to HTML should not be a raw innerHTML of user Markdown. Untrusted Markdown is HTML with extra steps.
XML
Different rules (no HTML named entities unless defined). XML to JSON uses DOMParser; still do not execute scripts from SVG/XML. Minimization (GDPR article) does not excuse XSS.
Tools
HTML entity encoder, decoder, HTML formatter, markdown to HTML.
Worked example: rendering user-controlled names
A JWT payload has name: "<img src=x onerror=alert(1)>". The inspector must not innerHTML that claim. textContent or the entity encoder first. This is the same bug as a log highlighter and as a CSV grid that builds HTML rows from cells. CSV editor cells are data, not markup.
Markdown in docs is a chosen sink. Markdown to HTML should sanitize. If you skip sanitizer “because it is my README,” you will one day paste a README from the internet. Fail closed. Pretty-printing with HTML formatter is for trusted HTML; it is not a security boundary.
URL context is separate: an href built from user input needs percent-encoding and an allowlist of schemes, not HTML entities. Mixing the two encoders is how you get https://example.com/?q=<script> that still does something surprising downstream. See percent-encoding.
Zero-upload does not mean zero XSS. A static origin can still execute in the victim tab and read the File they just selected. Minimization reduces breach of stored files; escaping reduces breach of the current tab. You need both. Network-tab quiet plus escaped previews is the standard.
Frequently asked questions
Is encodeURIComponent enough for HTML?
No. That is URL context (percent-encoding). HTML needs entities or a framework that default-escapes.
Does DOMParser sanitize?
It parses. It does not remove script. Parsing untrusted HTML to pretty-print can still execute if you assign innerHTML. Use HTML formatter only on trusted snippets, or sanitize first.
Named versus numeric entities?
& < > " ' cover the HTML specials. Numeric forms are safer for obscure Unicode. The encoder should at least cover the five specials.