XSS stands for cross-site scripting, a vulnerability where a site puts untrusted input into a page in a way that lets a browser run it as script. The script runs with the site's own origin, so it can read the DOM and make requests as the logged-in user. OWASP notes the name is a misnomer, since the term now covers injection of nearly any content. The fix is context-aware output encoding, with Content Security Policy as a second layer.
How it works
XSS happens when data crosses from data to code. The browser cannot tell your template from attacker text unless the text is encoded for the place it lands. There are three types:
- Reflected XSS: the payload is in the request, such as a search term in a URL, and the server echoes it back in the response. It takes one request and response, usually triggered by a crafted link.
- Stored XSS: the server saves the payload, for example in a comment or profile, and serves it to every visitor. MDN calls this particularly severe because every viewer is affected each time.
- DOM-based XSS: the flaw is in client-side code that reads a source like the URL fragment and writes it to a sink like innerHTML. The server's response does not contain the payload.
The main defense is output encoding that matches the context: HTML body, attribute, JavaScript, CSS or URL. OWASP also advises assigning text through textContent instead of innerHTML, using the auto-escaping of your framework, and sanitizing any HTML you must allow with a maintained library such as DOMPurify.
This example shows an unsafe string and an escaped one, produced by a small HTML escape function. The input is only a harmless illustration.
const esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
const q = "<img src=x onerror=alert(1)>";
console.log("<p>Results for " + q + "</p>");
console.log("<p>Results for " + esc(q) + "</p>");
<p>Results for <img src=x onerror=alert(1)></p>
<p>Results for <img src=x onerror=alert(1)></p>
The first line would run script in a browser. The second shows the text and nothing more. This escape fits HTML body and quoted attributes only. It does not make input safe inside script blocks, event handlers or URLs.
How does CSP help against XSS?
Content Security Policy limits which scripts the browser will run, so it blunts an XSS bug that slips through. OWASP says CSP should not be your primary defense, only an extra layer. A nonce-based policy allows a script only when its nonce attribute matches the per-response random value in the header. The CSP Header Builder can help you assemble such a header directive by directive.
Content-Security-Policy: script-src 'nonce-R4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'
Common pitfalls
- Encoding for the wrong context: HTML escaping does not protect a value placed inside a script block, an event handler attribute or a URL. Encode for the exact context, and avoid putting untrusted data in those places.
- Using innerHTML for text: assigning user text to innerHTML parses it as markup. Use textContent, or sanitize first.
- Framework escape hatches: React's dangerouslySetInnerHTML, Angular's bypassSecurityTrust functions and Lit's unsafeHTML turn off the built-in protection. OWASP lists them among insecure framework uses.
- Relying on input filters: OWASP warns that filtering alone is insufficient, and blocklists break legitimate input. Encode at output, where the context is known.
- CSP with unsafe-inline: allowing inline scripts removes most of the protection. Use nonces or hashes, and generate nonces per response with a real template engine.
- Cookies readable by script: set HttpOnly on session cookies so an XSS bug cannot read them directly. It does not stop the injected script from acting as the user.
Related terms
- CSRF — a different attack that abuses the browser's automatic cookies instead of injecting script.
- DOM — the tree that DOM-based XSS manipulates through unsafe sinks.
- HTTP — headers like Content-Security-Policy and Set-Cookie flags are set here.
- CORS — controls cross-origin reads, but does not stop injected script on your own origin.
- URL encoding — one of the encodings required for URL contexts.
- JWT — a token that script can read is exposed if script runs on your page.
See also
- Tool: CSP Header Builder — builds and validates strict Content Security Policy headers directive by directive.
- Cheatsheet: Content Security Policy Cheatsheet — directive reference for locking down script sources.