Cross-site scripting occurs when data an application intended to display becomes executable browser content. The dangerous value may come from a form, URL, database, API, or compromised dependency. Output encoding is one of the strongest defenses, but it works only when it matches the destination context. HTML text, attributes, JavaScript, CSS, and URLs follow different grammars.

The browser parses several languages at once

An HTML document can contain markup, inline scripts, style rules, and URLs. A value safe as text between tags may be dangerous inside a script string. A value safely quoted in an attribute may still form a dangerous javascript: URL. Security depends on where the browser parser will interpret the bytes.

Framework auto-escaping protects common HTML text and attribute cases. Developers bypass it when they render raw HTML, concatenate strings, use unsafe DOM APIs, or place values inside inline code.

Escape at the final output boundary

Storing HTML-encoded user input in the database mixes presentation with data and often leads to double encoding. It can also fail when the same value is later used in a different context. Preserve the original semantic value, validate it according to business rules, and encode it when rendering.

The output layer knows the context and can choose the correct encoder. Templates should escape by default, requiring deliberate review for any raw-output operation.

Avoid dangerous contexts entirely

Some placements are difficult to secure with encoding alone, including untrusted text inside script blocks, style blocks, event-handler attributes, or markup fragments. Prefer passing data through JSON responses or data attributes and reading it with safe APIs. Use textContent rather than innerHTML when inserting plain text.

Safe design removes the opportunity for data to become code. Encoding is then a predictable boundary operation rather than a complex attempt to neutralize every possible syntax transition.

Sanitize intentionally allowed HTML

Rich-text editors and comments may need a limited set of markup. Escaping everything would remove the intended formatting, so a trusted HTML sanitizer should parse the content and enforce an allowlist. Sanitization must cover dangerous attributes, URL schemes, malformed markup, and browser parsing behavior.

Do not build a sanitizer from regular expressions or string replacements. HTML is a structured, error-tolerant language, and attackers exploit differences between filters and browser parsers.

URLs require validation as well as encoding

Escaping quotes can keep a value inside an href attribute, but it does not make the destination safe. Applications should parse and allow expected schemes, hosts, or internal paths. Dangerous schemes and confusing external redirects remain dangerous even when the HTML syntax is valid.

Normalize and validate URL meaning before rendering, then apply attribute encoding at output. Syntax safety and navigation policy are separate checks.

Content Security Policy limits damage

A strong Content Security Policy can restrict which scripts execute and reduce the impact of some injection flaws. Nonces or hashes allow approved scripts while blocking unexpected inline execution. CSP also provides reporting that can reveal attempted violations.

CSP is defense in depth. Policies may contain exceptions, legacy browser behavior varies, and an application still needs correct output encoding and safe DOM operations.

Test the rendering path

Security tests should include values containing quotes, angle brackets, ampersands, URL schemes, malformed tags, and Unicode edge cases. Verify the resulting DOM and browser behavior, not only the response source. Client-side code can introduce XSS after the server safely renders initial HTML.

Review every raw HTML operation and document why it is safe. Dependency updates and template changes can alter contexts, so XSS prevention is an ongoing property of the rendering pipeline.

DOM APIs have safe and unsafe families

Client-side applications often move values after initial rendering. APIs such as textContent and structured DOM creation keep data separate from markup. APIs such as innerHTML, outerHTML, and string-based script execution ask the browser to parse code-capable text.

Framework escape guarantees can be bypassed by direct DOM manipulation or third-party widgets. Inventory these sinks and wrap dangerous operations behind reviewed utilities when they cannot be removed.

Trusted Types can strengthen large applications

Trusted Types can restrict dangerous DOM sinks so they accept values created by approved policies rather than arbitrary strings. This makes accidental unsafe insertion easier to detect and turns hidden assumptions into enforceable boundaries.

Adopting such controls requires migration work and does not replace sanitization, but it helps teams preserve the separation between data and executable markup.

Keep data and code separate

Contextual output encoding works because it preserves a boundary: user-controlled values remain data while the application's templates and scripts remain code. Sanitization is the controlled exception for approved markup, and CSP supplies an additional barrier.

That boundary should appear in architecture and code review checklists, not only security documentation. Teams prevent more XSS by making safe APIs convenient than by expecting every developer to remember a list of payload examples.

The core practice is not memorizing one escape function. It is knowing the destination context, using APIs designed for that context, and avoiding placements where untrusted data can become executable at all.