Cross-site scripting (XSS): how it works and how to stop it

XSS is untrusted text written into a page as HTML. The browser cannot tell your script from one an attacker smuggled in, so it runs both — on your origin, with the visitor's session.

Example

Vulnerable
app.get("/search", (req, res) => {
  res.send("<h1>Results for " + req.query.q + "</h1>");
});

// client side
results.innerHTML = comment.body;
Fixed
import escapeHtml from "escape-html";

app.get("/search", (req, res) => {
  res.send("<h1>Results for " + escapeHtml(String(req.query.q)) + "</h1>");
});

// client side
results.textContent = comment.body;
// or, when HTML is required: results.innerHTML = DOMPurify.sanitize(comment.body);

How it happens

Reflected XSS sends a crafted link whose parameter is echoed into the response. Stored XSS saves the payload — a comment, a profile field — and serves it to everyone who views it. DOM-based XSS happens entirely in the browser, through innerHTML, document.write or insertAdjacentHTML. Templates with autoescaping switched off turn every rendered variable into a candidate.

How to fix it

Escape on output, and limit what an injected script could do.

  • Render text as text: textContent, escaped template variables, React's default rendering
  • Keep template autoescaping on; mark only sanitised values as safe
  • Sanitise with DOMPurify when HTML is genuinely required
  • Add a Content-Security-Policy without 'unsafe-inline', and HttpOnly session cookies

How RepoVerse finds it

  • HighRequest input is written into the page as HTML

    Script in the input runs in the victim's browser on your origin, with their session.

    data flow · CWE-79

  • HighRequest input is sent back inside an HTML response

    A crafted link makes the response carry the attacker's script, which then runs as your site.

    data flow · CWE-79

  • HighTemplate autoescaping switched off

    Every variable rendered into a template is emitted as raw HTML, so any user-supplied field shown on a page becomes stored or reflected XSS.

    data flow · CWE-79

  • ModerateMarkup written from a value

    A variable is written into the DOM as HTML rather than as text.

    pattern

  • HighLink target taken from a value

    An `href` or `src` is set from a variable.

    pattern · CWE-79

  • ModerateCSP allows inline scripts

    `'unsafe-inline'` in `script-src` lets injected `<script>` tags and event handlers run, which is the main thing a CSP is there to stop.

    website · CWE-79

Questions

Does React prevent XSS?
React escapes values rendered in JSX, which prevents most XSS. dangerouslySetInnerHTML, href values starting with javascript: and server-side string building bypass that protection.
Is a Content-Security-Policy enough against XSS?
It is a second layer: a strict CSP stops most injected scripts from running, but the injection is still a bug. Fix the output encoding and keep the CSP.

Related

Updated 2026-10-11