XSS for Frontend Engineers
How cross-site scripting reaches UI sinks, what React/HTML escaping does and doesn’t save, and layered defenses.
- security
- xss
- web
Cross-site scripting (XSS) means an attacker gets script (or equivalent) to run in your users’ origin. On the frontend, we create most of the sinks — the places untrusted data becomes executable or dangerous markup.
If you only say “React escapes everything,” you’ll miss dangerouslySetInnerHTML, markdown renderers, rich text, URL attributes, and server-built HTML.
Flavors you’ll hear in interviews
| Type | Story |
|---|---|
| Stored | Payload saved (comment, profile) and served later to others |
| Reflected | Payload in URL/query bounced into the response |
| DOM-based | Client JS reads untrusted input (location, postMessage, …) and writes it into a sink |
Same outcome: attacker code in your origin, with your users’ cookies/storage privileges (depending on cookie flags).
Sinks frontend code keeps inventing
// HTML sinks
el.innerHTML = userInput;
el.outerHTML = userInput;
document.write(userInput);
// Often safer for pure text
el.textContent = userInput;
// URL / navigation sinks
location.href = userInput;
a.href = userInput; // javascript: URLs still surprise people
Framework defaults help when you stay in the happy path:
// React escapes text children
<p>{userInput}</p>
// You opted out of safety
<div dangerouslySetInnerHTML={{ __html: userInput }} />
Markdown → HTML, PDF preview, chart tooltips, email clients… every “we render user HTML” feature needs a real sanitizer policy, not hope.
Defense in depth (stack them)
- Don’t treat untrusted data as HTML. Prefer text nodes / escaped templates.
- Sanitize only when rich HTML is a product requirement — use a maintained library and a strict allowlist.
- CSP — reduce blast radius (
script-srcwith nonces/hashes; avoidunsafe-inlinewhen you can). - Cookie flags —
HttpOnly,Secure,SameSitefor session cookies so stolen JS can’t always read the session. - Validate URLs — block
javascript:and unexpected schemes onhref/src.
No single control is enough. CSP without safe sinks still fails; safe sinks without CSP still fail open on the next bug.
A DOM XSS path you can narrate
- Source:
location.hashor query param. - Flow: read in JS, maybe “decode” it.
- Sink: assign to
innerHTMLor build HTML with string concat. - Fix:
textContent, or sanitize, or stop reflecting raw input.
That four-step story scores better than a laundry list of acronyms.
Further reading
Related
- Focus management
- Core Web Vitals — performance work should not weaken CSP
Related guides
- Frontend Security Threat ModelFrontend Security Threat Model explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- XSS Prevention ChecklistXSS Prevention Checklist explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- CSRF Basics for SPAsCSRF Basics for SPAs explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- Content Security PolicyContent Security Policy explained for frontend engineers — mental model, examples, common mistakes, and interview tips.
- Secure Cookie FlagsSecure Cookie Flags explained for frontend engineers — mental model, examples, common mistakes, and interview tips.