Cross-Site Scripting (XSS)
XSS lets an attacker run JavaScript in a victim's session on a vulnerable site. From there: stealing cookies, hijacking actions, exfiltrating data, defacing the page.
The three variants
- Reflected XSS. The payload is in the request (typically a query parameter) and is reflected back unescaped in the response. The attacker delivers a crafted URL.
- Stored XSS. The payload is persisted (database, file) and served back to every visitor. The most impactful variant.
- DOM-based XSS. The vulnerability is in client-side JavaScript that takes attacker-controlled data and writes it into the DOM without sanitization — e.g.
element.innerHTML = location.hash.
Defenses (in order)
- Context-aware output encoding. Encode user data for the exact HTML context it lands in:
- Inside text → encode
& < > - Inside an attribute → also encode quotes
- Inside a URL → percent-encode
- Inside a script context → don't do this. Re-architect.
- Inside text → encode
- Use a templating engine that auto-escapes. Modern frameworks (React, Vue, Svelte, Django, Rails) escape by default. Treat any
dangerouslySetInnerHTML/v-html/{{ raw }}as suspicious in code review. - Sanitize untrusted HTML. If you must accept HTML from users, run it through a maintained sanitizer like DOMPurify with a strict allow-list.
- Set HttpOnly cookies. Even if XSS happens, scripts can't read session cookies. Doesn't prevent everything, but it cuts off the easiest impact.
- Content Security Policy. A strong CSP blocks inline scripts and limits what an injected script can reach. See CSP.
!
Don't trust the URL. URLs in href and src attributes can carry javascript: schemes. Always validate that user-supplied URLs use http://, https://, or your specific allow-list before placing them.