Cross-site scripting was first described in 2000. Twenty-six years later, it remains the most frequently reported vulnerability in bug bounty programs and the most commonly exploited injection attack on the web. The persistence of XSS isn't due to a lack of awareness — it's because the attack surface keeps expanding, modern applications introduce new injection contexts, and the fundamental tension between dynamic content and security remains unresolved.
Why Frameworks Haven't Solved It
Modern frameworks like React, Vue, and Angular auto-escape output by default, which has dramatically reduced simple reflected XSS. But auto-escaping has blind spots:
- dangerouslySetInnerHTML in React: When developers need to render rich HTML content — blog posts, email templates, WYSIWYG editor output — they bypass the framework's built-in escaping. The name is a warning, but production codebases use it routinely.
- Server-side rendering: SSR introduces contexts where user input is inserted into the HTML document before the framework's client-side sanitisation runs. Template injection on the server bypasses client-side protections entirely.
- URL injection: Frameworks escape HTML entities but don't validate URL schemes. An attacker can inject
javascript:alert(1)into anhrefattribute that the framework renders without warning. - CSS injection: User-controlled CSS values can exfiltrate data through
background-imageURLs,@importrules, and attribute selectors that send requests based on page content.
The Three Flavours of XSS
XSS comes in three distinct variants, each with different persistence and exploitation characteristics:
Reflected XSS requires the victim to click a crafted link. The payload is in the URL and executes when the server reflects it in the response. Modern browsers' XSS auditors have reduced but not eliminated this variant — they miss many encoding tricks and context-specific payloads.
Stored XSS is more dangerous because the payload persists. An attacker injects the script into a database — via a comment field, profile name, or message — and every user who views that content executes the attacker's code. One injection, unlimited victims.
DOM-based XSS never touches the server. The vulnerability exists entirely in client-side JavaScript that reads from an attacker-controllable source (URL fragment, postMessage, localStorage) and writes to a dangerous sink (innerHTML, document.write). Server-side protections and WAFs can't detect or prevent it.
Modern XSS Targets
XSS payloads in 2026 go far beyond alert(1). Real-world exploitation includes:
- Session theft: Exfiltrating session cookies to impersonate the victim (blocked by
HttpOnlycookies, but many apps still don't set this flag). - Credential harvesting: Injecting a fake login form overlay that captures credentials when the user "re-authenticates."
- Keylogging: Recording every keystroke on the page and exfiltrating the data — capturing passwords, credit card numbers, and private messages in real time.
- Cryptocurrency mining: Running miners in the victim's browser, consuming CPU resources without consent.
- Worm propagation: Self-replicating XSS that posts itself to other users' profiles, creating viral spread across the platform. The Samy worm on MySpace infected over a million profiles in under 20 hours.
Content Security Policy: The Real Defence
Output encoding prevents most XSS, but CSP is the defence-in-depth layer that matters when encoding fails. A strict CSP with nonce-based script loading makes most XSS payloads structurally impossible — even if an attacker finds an injection point, the browser refuses to execute scripts that don't carry the correct nonce.
Content-Security-Policy: script-src 'nonce-randomValue123' 'strict-dynamic'; object-src 'none'; base-uri 'self'
This policy allows only scripts with the matching nonce attribute, blocks plugins, and prevents base tag injection. An injected script tag is ignored because it lacks the nonce. Inline event handlers are blocked as well.
The challenge is deployment. Legacy applications with dozens of inline scripts, third-party widgets, and dynamic script loading require careful migration to nonce-based loading. But the security benefit is transformative — CSP converts XSS from a critical vulnerability to a contained annoyance.
The Testing Gap
Automated XSS scanners find the obvious cases — reflected parameters that appear unescaped in the response. They miss DOM-based XSS, context-specific injection, and stored XSS that requires multi-step interaction. The gap between what automated tools find and what skilled attackers exploit remains wide, which is why XSS continues to dominate bug bounty payouts despite decades of automated scanning.
ShieldReport evaluates your domain's defences against XSS — Content-Security-Policy configuration, X-Content-Type-Options, and cookie security attributes — identifying the missing protections that determine whether an XSS vulnerability is a critical exploit or a contained finding.