Stop 14 of 42 · The Teller Window · Cross-site scripting (XSS)

Notes that boss around every reader

The bank has a public noticeboard where customers can pin notes. Mallory pins one that looks harmless but hides instructions in it. Every customer’s browser reads the note and follows them, because the board never separated “words to show” from “things to do.”

Severity Low–CriticalCategory Injection RiskThe Teller WindowSources checked 25 Oct 2026

Watch it happen

Play it as

Mallory pins a note with hidden instructions on the public noticeboard. When Alice reads it, her browser follows them and sends her badge to Mallory.

Arrival

Alice and Mallory walk into the lobby. A public noticeboard lets anyone pin a note, and everyone who walks past can read it. That is how comments, reviews and profiles work.

1 / 5

In bank terms and app terms

In the bankIn your app
NoticeboardAny page that shows user content: comments, profiles, search results
Hidden instruction in a noteA <script> tag or event handler placed in user input
Printing notes as plain wordsEscaping output (React does this for text by default, but not inside dangerouslySetInnerHTML)
House rule from the guardContent-Security-Policy header

Are you exposed?

You might be exposed if…
  • You use dangerouslySetInnerHTML, innerHTML, v-html or document.write with anything a user typed.
  • Your search page or error message repeats the user’s text back inside the HTML.
  • You store the login token in localStorage, where any injected script can read it.
What to do about it
  1. Show user text as text. Let React, Vue or your template engine escape it, and avoid raw HTML insertion.
  2. If you must allow HTML (rich text), clean it with a sanitizer library such as DOMPurify.
  3. Add a Content-Security-Policy header as a second layer, and keep session cookies HttpOnly so scripts cannot read them. That limits theft, but injected script can still act as the user.

Track these in your to-do list →

It has happened to real companies

Real-world breaches

British Airways (2018) — Attackers who got into British Airways' systems through a compromised contractor account planted a card skimmer on its payment page. British Airways said at the time that up to 380,000 card payments were affected. The UK data regulator (ICO) fined it £20 million in 2020, after finding that basic protections such as multi-factor authentication were not in place. Script skimming like this is the kind of attack that a strict Content Security Policy and careful control of the scripts on a payment page are meant to make harder.

eBay (2016) — Researchers at Check Point showed that eBay's filter for listing descriptions could be bypassed to run JavaScript from a listing. In February 2016 Netcraft reported criminals using it: listings on hijacked seller accounts redirected visitors to fake eBay pages, including a fake login page that harvested usernames and passwords.

What it looks like in code

// BAD: User input reflected directly in HTML
export async function GET(request: Request) {
  const query = new URL(request.url).searchParams.get("q")
  return new Response(`<h1>Results for: ${query}</h1>`, {
    headers: { "Content-Type": "text/html" }
  })
}

// BAD: React dangerouslySetInnerHTML with user data
function Comment({ text }) {
  return <div dangerouslySetInnerHTML={{ __html: text }} />
}

Ask your AI tool to fix it

Paste this into Cursor, Claude, Lovable, Bolt or whatever you build with.

Prompt
Find every place the frontend or server renders user-controlled content as HTML (innerHTML, dangerouslySetInnerHTML, v-html, document.write, unescaped template output). Replace with safe text rendering, or sanitize with DOMPurify where rich text is required. Add a strict Content-Security-Policy header and make session cookies HttpOnly, Secure and SameSite. List the changes.

Where this comes from

The facts, figures and fixes on this page were checked against these sources on 25 Oct 2026. They are linked so you can check for yourself.

Spot a mistake or something out of date? Report a correction · How these guides are made and checked