Juicy Shop
Cross-Site Scripting (XSS)
XSS lets an attacker run JavaScript in another user's browser. Learn the three XSS types, why output encoding is the core defence, and how modern frameworks help — and where they still let you shoot yourself in the foot.
What XSS lets an attacker do
If an attacker can execute script in a victim's session, they can read cookies and tokens, perform actions as the victim, log keystrokes, or rewrite the page to phish credentials. XSS is injection into the HTML/JS context: untrusted input is placed into a page where the browser interprets it as code rather than text.
Three flavours of XSS
Stored XSS: the payload is saved (e.g. in a review) and served to every viewer. Reflected XSS: the payload rides in the request (e.g. a URL parameter) and is echoed back in the response — delivered via a crafted link. DOM-based XSS: client-side JavaScript writes untrusted data into the DOM (e.g. innerHTML) without encoding, so the payload never even has to reach the server.
The core defence: contextual output encoding
The fix is to encode data for the context where it is rendered. In HTML body context, characters like < > & " become < > & " so the browser shows them as text. React, Vue, and Angular auto-encode by default when you render a value as a text node — which is why {userInput} in JSX is safe. The danger is opting out: dangerouslySetInnerHTML / v-html / innerHTML hand raw markup to the browser.
When you must render HTML
Sometimes you genuinely need to render user-authored HTML (a rich-text comment). Then sanitise with a vetted library such as DOMPurify, which strips scripts and dangerous attributes, rather than trusting a hand-rolled regex. Add a Content-Security-Policy that forbids inline scripts as defence in depth, so even a missed encoding cannot easily execute.
Insecure vs secure
const params = useSearchParams();
const q = params.get("q") ?? "";
return <p dangerouslySetInnerHTML={{ __html: q }} />;The raw query parameter is injected as HTML. A link with ?q=<img src=x onerror=alert(document.cookie)> runs script in the victim's session.
const params = useSearchParams();
const q = params.get("q") ?? "";
return <p>{q}</p>;Rendered as a JSX text node, React HTML-encodes q automatically, so any markup is shown as inert text rather than executed.
Key takeaways
- XSS is code injection into the browser — it executes in the victim's session.
- Know the three types: stored, reflected, and DOM-based.
- Contextual output encoding is the primary defence; frameworks do it by default.
- Opting out (dangerouslySetInnerHTML/innerHTML) is where XSS sneaks back in.
Knowledge check
Practice
Apply this lesson hands-on in the matching find-it / fix-it challenge.
Open challenge →