Skip to content
UniCircle
MediumConceptualWeb security (XSS)

Cross-site scripting (XSS): stored, reflected and DOM-based, and how to prevent it

Question CYB-WEB-007

What is cross-site scripting? Explain the stored, reflected and DOM-based types conceptually, what harm XSS can do, and the layered defences a web developer should use.

Show the explanation

XSS happens when an application includes untrusted data in a web page without proper encoding or sanitisation, so the browser treats it as active content (script or markup) instead of text. The script then runs with the privileges of the vulnerable site in the victim's browser.

Types (by where the untrusted data comes from)

  • Stored (persistent): the input is saved on the server (a comment, a profile name, a support ticket) and later shown to other users. It can affect everyone who views the page.
  • Reflected: the input arrives in the request, for example in a URL parameter or form field, and the server immediately echoes it back in the response.
  • DOM-based: the flaw is entirely in client-side JavaScript. It reads attacker-influenced data (such as location.hash) and writes it into a dangerous sink such as innerHTML, document.write or eval.

Impact: script running as the site can read data shown on the page, act as the user (submit forms, change settings), steal session tokens that are not HttpOnly, show fake login forms, or deface the page.

Defences, in layers

  1. Context-aware output encoding (the main one). Encode data for the place it goes: HTML text, attribute, JavaScript string, URL or CSS. Templates and frameworks that auto-escape (React JSX, Angular, Vue templates, Jinja2, Django, Thymeleaf) do this by default. Keep it on.
  2. Avoid dangerous sinks. Use textContent or setAttribute instead of innerHTML, and never use eval or new Function on data. Be careful with framework escape hatches (dangerouslySetInnerHTML, v-html, |safe).
  3. Sanitise rich HTML with a maintained, allow-list-based library (such as DOMPurify) when users really must submit HTML.
  4. Validate input: types, lengths, and allowed URL schemes (https: only for user links, never javascript:).
  5. Content Security Policy (CSP): a response header that limits which scripts can run, ideally with nonces or hashes and no unsafe-inline. A missed bug then becomes much harder to exploit.
  6. Cookie flags: HttpOnly (scripts cannot read the cookie), Secure, SameSite.
  7. Newer browser features such as Trusted Types make DOM sinks refuse plain strings.

In one sentence: treat all user data as text, encode it for the context where you output it, and add CSP as a backstop.

Original UniCircle question. Last reviewed 22 Oct 2026.