An Old Vulnerability Class That Remains Extremely Common
Cross-site scripting lets an attacker inject malicious scripts into a web page viewed by other users — stealing session cookies, defacing content, or performing actions on behalf of a victim without their knowledge or consent. Despite being well-understood for decades, XSS remains consistently common because modern applications render user-influenced content in many different, often overlooked contexts, each with its own specific escaping requirements.
The Three Main Types
Stored XSS occurs when malicious script gets saved to a database (a comment, a profile field) and then served to other users who view that content — genuinely the most dangerous type, since it affects every single subsequent viewer automatically without any further attacker action needed. Reflected XSS occurs when malicious script is immediately reflected back in a response, typically via a crafted URL a victim is tricked into clicking. DOM-based XSS happens entirely client-side, when JavaScript takes user-influenced data and unsafely inserts it directly into the page without any server round trip involved at all.
Output Encoding: The Fundamental Defense
The core defense against XSS is encoding output appropriately for the specific context it’s being rendered into — HTML encoding for content placed inside HTML tags, JavaScript encoding for content placed inside a script context, URL encoding for content placed inside a URL. Using the wrong encoding for the actual context, or forgetting it entirely in even one single place, reopens the vulnerability — modern templating engines handle this automatically by default in most cases, but explicit awareness matters considerably for the exceptions and edge cases.
Content Security Policy as Defense in Depth
A Content Security Policy (CSP) header restricts which sources of scripts, styles, and other resources a browser will actually execute or load, providing a genuinely powerful additional defense layer even if an injection somehow slips past your primary output encoding. A strict CSP that disallows inline scripts entirely and only allows scripts from explicitly trusted, whitelisted sources meaningfully limits what an attacker can actually accomplish even in the event of a successful injection.
Framework Protections and Their Real Limits
Modern frontend frameworks (React, Vue, Angular) automatically escape content rendered through their standard templating by default, meaningfully reducing XSS risk compared to manually constructing raw HTML strings. But every framework provides an explicit escape hatch — dangerouslySetInnerHTML in React, v-html in Vue — for cases genuinely needing to render raw HTML, and these escape hatches reintroduce the exact same risk if user-influenced content ever flows into them without separate, deliberate sanitization first.
Sanitizing Rich Text Content
Applications that genuinely need to accept and render user-provided HTML — a rich text editor, markdown that gets converted to HTML — need a proper, well-maintained sanitization library (like DOMPurify) that allows a genuinely safe, explicitly defined subset of HTML and strips anything else, rather than attempting to write custom sanitization logic yourself. Custom sanitization is a well-known, notorious source of subtle bypasses, since the space of what constitutes genuinely dangerous HTML is far larger and more nuanced than it initially appears.
HttpOnly and Secure Cookie Flags
Setting the HttpOnly flag on sensitive cookies (particularly session cookies) prevents JavaScript from accessing them entirely, meaning even a successful XSS injection can’t directly steal the session cookie through script access. This doesn’t prevent XSS itself, but it meaningfully limits the actual impact of a successful attack — a genuinely valuable defense-in-depth measure that costs essentially nothing to implement and should be standard practice for any sensitive cookie.
Testing for XSS Vulnerabilities
Automated scanning tools can catch a meaningful share of XSS vulnerabilities, particularly straightforward reflected XSS in obvious, easily-tested locations. Manual testing and code review remain genuinely important for catching more subtle cases — particularly DOM-based XSS and injection through less obvious data flows that automated tools with limited context often miss entirely, especially in complex single-page applications with many possible data paths.
Practical Checklist
- Use context-appropriate output encoding consistently — HTML, JavaScript, and URL contexts each need genuinely different encoding.
- Implement a strict Content Security Policy as a meaningful defense-in-depth layer, not a replacement for proper output encoding.
- Use a well-maintained sanitization library for any user-provided rich text content — never write custom sanitization logic yourself.
- Set HttpOnly and Secure flags on sensitive cookies to limit the real impact of any successful XSS that does occur.