Back to Blog
high SEVERITY8 min read

How CSRF bypass happens in React Router RSC mode and how to fix it

A high-severity CSRF bypass vulnerability (GHSA-qwww-vcr4-c8h2) in React Router's RSC (React Server Components) mode allowed attackers to execute actions before the framework returned a 400 response. This vulnerability affected React Router versions prior to 7.18.2 and 8.3.0, enabling cross-site request forgery attacks that could bypass standard CSRF protections in applications using RSC mode.

O
By Orbis AppSec
Published August 21, 2026Reviewed August 21, 2026

Answer Summary

The React Router RSC Mode CSRF Bypass (GHSA-qwww-vcr4-c8h2, CWE-352) is a high-severity vulnerability in React Router versions before 7.18.2 and 8.3.0 that allows attackers to execute server actions before the framework returns a 400 error response. The vulnerability occurs in RSC (React Server Components) mode where CSRF validation timing allows malicious cross-site requests to trigger actions. The fix upgrades react-router and react-router-dom to version 7.18.2, which properly validates CSRF tokens before action execution and includes an updated cookie parser (from 0.7.2 to 1.1.1) to strengthen request validation.

Vulnerability at a Glance

cweCWE-352 (Cross-Site Request Forgery)
fixUpgrade to React Router 7.18.2/8.3.0 with improved CSRF validation timing and cookie parsing
riskAttackers can execute unauthorized server actions via cross-site requests before CSRF validation completes
languageJavaScript/TypeScript (React)
root causeAction execution occurs before 400 response is returned in React Router's RSC mode
vulnerabilityCSRF Bypass in RSC Mode

Introduction

In a Go service with a React UI frontend, we discovered a high-severity CSRF bypass vulnerability in core/http/react-ui/bun.lock affecting React Router version 7.18.1. The vulnerability, tracked as GHSA-qwww-vcr4-c8h2, exists in React Router's RSC (React Server Components) mode where server actions could execute before CSRF validation completed and returned a 400 error response. This timing flaw created a window for attackers to bypass CSRF protections and execute unauthorized actions on behalf of authenticated users.

The vulnerable dependency chain was identified in the lock file:

"react-router": ["react-router@7.18.1", "", { 
  "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, 
  "peerDependencies": { "react": ">=18", "react-dom": ">=18" }
}]

This matters because modern React applications increasingly use RSC mode for server-side rendering and data fetching. Any application using React Router 7.18.1 or earlier in RSC mode was vulnerable to CSRF attacks that could manipulate user data, trigger state changes, or execute privileged operations—all without valid CSRF tokens.

The Vulnerability Explained

CSRF (Cross-Site Request Forgery) attacks exploit the browser's automatic inclusion of cookies in HTTP requests. In a typical CSRF attack, a malicious website tricks a victim's browser into making authenticated requests to a target application. Modern frameworks defend against this by requiring CSRF tokens that attackers cannot obtain.

However, React Router versions prior to 7.18.2 had a critical flaw in their RSC mode implementation: actions would begin execution before CSRF validation completed. Here's what the vulnerable code path looked like:

// Vulnerable flow in React Router 7.18.1
1. Request arrives with potentially invalid CSRF token
2. Action handler begins execution immediately
3. CSRF validation runs in parallel
4. If validation fails, 400 response is prepared
5. But action may have already completed by this point

The dependency on the older cookie package (version 0.7.2) compounded the issue:

"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="]

Attack Scenario

Consider a React application using React Router 7.18.1 in RSC mode with a server action that updates user preferences:

// Server action in RSC mode (vulnerable)
export async function updateUserSettings(formData) {
  const userId = getCurrentUser();
  const theme = formData.get('theme');

  // This executes BEFORE CSRF validation completes!
  await database.updateUserTheme(userId, theme);

  return { success: true };
}

An attacker could craft a malicious page:

<form id="csrf-attack" action="https://victim-app.com/settings" method="POST">
  <input type="hidden" name="theme" value="attacker-controlled">
  <input type="hidden" name="_csrf" value="invalid-token">
</form>
<script>
  document.getElementById('csrf-attack').submit();
</script>

When a victim visits this page:

  1. The form submits to the victim's authenticated session
  2. React Router 7.18.1 begins executing updateUserSettings()
  3. The database update completes
  4. CSRF validation finally runs and fails
  5. A 400 response is returned—but the damage is done

The real-world impact is severe: attackers could modify user settings, trigger financial transactions, change passwords, or execute any action the application exposes through RSC server actions—all without valid CSRF tokens.

The Fix

The fix upgrades both react-router and react-router-dom to version 7.18.2, along with updating the cookie dependency from 0.7.2 to 1.1.1. Here's the specific change in core/http/react-ui/bun.lock:

Before (Vulnerable):

"react-router": ["react-router@7.18.1", "", { 
  "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, 
  "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, 
  "optionalPeers": ["react-dom"] 
}, "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg=="]
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="]

After (Fixed):

"react-router": ["react-router@7.18.2", "", { 
  "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, 
  "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, 
  "optionalPeers": ["react-dom"] 
}, "sha512-[updated-hash]"]
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="]

The corresponding package.json changes enforce these versions:

"dependencies": {
  "react-router": "7.18.2",
  "react-router-dom": "7.18.2"
}

How This Solves the Problem

React Router 7.18.2 implements a critical timing fix in RSC mode:

// Fixed flow in React Router 7.18.2
1. Request arrives with CSRF token
2. CSRF validation runs FIRST and completes
3. If validation fails, immediately return 400
4. Action handler executes ONLY after validation passes
5. No race condition possible

The security improvement is concrete: CSRF validation now acts as a gate that must pass before any action code executes. The updated cookie package (1.1.1) also provides more robust cookie parsing, reducing the attack surface for cookie manipulation techniques that could bypass validation.

The fix was applied to two files:
- core/http/react-ui/bun.lock: Updates the dependency lock to enforce React Router 7.18.2 and cookie 1.1.1
- core/http/react-ui/package.json: Specifies exact versions to prevent accidental downgrades

This dual-file approach ensures both the declared dependencies and the resolved dependency tree are secure, preventing package managers from installing vulnerable versions.

Prevention & Best Practices

1. Keep Dependencies Updated

Monitor security advisories for your frontend framework dependencies. React Router, like many frameworks, releases security patches regularly. Use tools like:

  • Dependabot: Automatically creates PRs for dependency updates
  • Snyk: Scans for known vulnerabilities in your dependency tree
  • npm audit or bun audit: Command-line vulnerability scanning

2. Implement Defense in Depth for CSRF

Don't rely solely on framework-provided CSRF protection:

// Additional CSRF defenses
app.use((req, res, next) => {
  // Verify Origin header
  const origin = req.get('Origin');
  if (origin && !isAllowedOrigin(origin)) {
    return res.status(403).json({ error: 'Forbidden origin' });
  }

  // Check Referer for state-changing operations
  if (req.method !== 'GET' && req.method !== 'HEAD') {
    const referer = req.get('Referer');
    if (!referer || !referer.startsWith(process.env.APP_URL)) {
      return res.status(403).json({ error: 'Invalid referer' });
    }
  }

  next();
});

3. Use SameSite Cookie Attributes

Configure session cookies with strict SameSite policies:

res.cookie('session', sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict', // or 'lax' for better compatibility
  maxAge: 3600000
});

4. Validate Critical Actions

For sensitive operations, require additional verification:

export async function deleteAccount(formData) {
  // CSRF token validated by framework

  // Additional password confirmation
  const password = formData.get('password');
  if (!await verifyPassword(password)) {
    throw new Error('Password confirmation required');
  }

  // Proceed with deletion
  await database.deleteUser(getCurrentUser());
}

5. Security Testing

Include CSRF testing in your security test suite:

// Example test for CSRF protection
test('rejects requests with invalid CSRF tokens', async () => {
  const response = await fetch('/api/update-settings', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Cookie': 'session=valid-session'
    },
    body: JSON.stringify({
      theme: 'dark',
      _csrf: 'invalid-token'
    })
  });

  expect(response.status).toBe(400);
  // Verify action did NOT execute
  const settings = await database.getUserSettings(userId);
  expect(settings.theme).not.toBe('dark');
});

6. Follow OWASP Guidelines

Implement OWASP's CSRF prevention cheat sheet recommendations:
- Use anti-CSRF tokens for state-changing operations
- Validate the Origin and Referer headers
- Implement custom request headers for AJAX requests
- Use SameSite cookie attributes
- Consider double-submit cookie patterns for stateless applications

7. Static Analysis

Use tools to detect CSRF vulnerabilities:

  • Semgrep: Write rules to detect missing CSRF protection
  • ESLint security plugins: Catch common security mistakes
  • SAST tools: Integrate into CI/CD pipelines

Example Semgrep rule:

rules:
  - id: missing-csrf-protection
    pattern: |
      export async function $ACTION(...) {
        ...
        await database.$METHOD(...)
        ...
      }
    message: "Server action may lack CSRF protection"
    severity: WARNING

Key Takeaways

  • React Router 7.18.1's RSC mode allowed action execution before CSRF validation completed, creating a race condition that attackers could exploit to bypass CSRF protections
  • The fix in version 7.18.2 reorders the execution flow so CSRF validation acts as a mandatory gate before any server action code runs
  • The cookie dependency upgrade from 0.7.2 to 1.1.1 strengthens cookie parsing and reduces additional attack vectors
  • This vulnerability demonstrates that timing bugs in security controls are just as dangerous as missing controls—even if validation eventually fails, damage can occur if the check happens too late
  • Applications using React Router in RSC mode should immediately upgrade to 7.18.2 or 8.3.0 and verify that CSRF tokens are properly configured for all state-changing server actions

How Orbis AppSec Detected This

  • Source: HTTP request to React Router RSC endpoints in core/http/react-ui
  • Sink: Server action execution in React Router 7.18.1 before CSRF validation completes
  • Missing control: Proper ordering of CSRF token validation before action execution in RSC mode
  • CWE: CWE-352 (Cross-Site Request Forgery)
  • Fix: Upgraded react-router and react-router-dom to version 7.18.2, which enforces CSRF validation before action execution, and updated cookie parser to 1.1.1

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

The React Router RSC Mode CSRF bypass (GHSA-qwww-vcr4-c8h2) demonstrates how timing vulnerabilities in security controls can be just as exploitable as missing controls entirely. By allowing server actions to execute before CSRF validation completed, React Router 7.18.1 created a window for attackers to bypass protections designed to prevent cross-site request forgery.

The fix in version 7.18.2 is straightforward but critical: it reorders the execution flow to ensure CSRF validation acts as a mandatory gate. Combined with the updated cookie parser, this upgrade eliminates the race condition and restores the intended security guarantees of RSC mode.

For developers working with React Router, this vulnerability highlights the importance of staying current with security patches and implementing defense-in-depth strategies. CSRF protection should never be your only line of defense—use SameSite cookies, validate request origins, and require additional verification for sensitive operations.

Most importantly, this case shows why proactive security scanning matters. The vulnerability existed in a dependency lock file, easily overlooked in manual code reviews but immediately detectable by automated tools. Regular dependency audits and automated security fixes can prevent these issues from reaching production.

References

Frequently Asked Questions

What is React Router RSC Mode CSRF Bypass?

It's a vulnerability in React Router versions before 7.18.2 where RSC (React Server Components) mode allows server actions to execute before CSRF validation completes and returns a 400 error, enabling cross-site request forgery attacks to succeed despite token validation failures.

How do you prevent CSRF bypass in React Router applications?

Upgrade react-router and react-router-dom to version 7.18.2 or 8.3.0 or later. These versions fix the timing issue by ensuring CSRF validation completes and rejects invalid requests before any action execution occurs. Additionally, implement SameSite cookie attributes and validate origin headers.

What CWE is CSRF bypass?

CSRF bypass is classified as CWE-352 (Cross-Site Request Forgery). This occurs when a web application doesn't properly verify that requests originate from legitimate users, allowing attackers to trick victims into executing unwanted actions on authenticated sessions.

Is using HTTPS enough to prevent CSRF in React Router?

No, HTTPS alone does not prevent CSRF attacks. While HTTPS encrypts traffic and prevents man-in-the-middle attacks, CSRF exploits the browser's automatic inclusion of cookies in requests. You must upgrade to React Router 7.18.2+ which properly validates CSRF tokens before action execution, and implement additional defenses like SameSite cookies.

Can static analysis detect React Router CSRF bypass vulnerabilities?

Yes, dependency scanning tools like Trivy, Snyk, and GitHub Dependabot can detect vulnerable React Router versions by checking package.json and lock files against known vulnerability databases (GHSA-qwww-vcr4-c8h2). However, they cannot determine if your application actually uses RSC mode—runtime analysis and manual code review are needed for full context.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #11644

Related Articles

high

How Missing CSRF Middleware happens in Express.js and how to fix it

A high-severity CSRF vulnerability was discovered in `libProxy.js` of an Express.js application — the app had no CSRF middleware protecting its state-changing routes, leaving them open to cross-site request forgery attacks. The fix introduces a `csrf` token library, a `/csrf-token` endpoint to issue tokens, and a middleware that validates `x-csrf-token` headers or `_csrf` body fields on all non-safe HTTP methods. This proactive hardening removes an exploit primitive that could be chained with ot

high

How CSRF vulnerability happens in JavaScript fetch() calls and how to fix it

A high-severity CSRF vulnerability was discovered in Moodle's VvvebJs page builder where POST requests to `saveReusableUrl` and `saveUrl` endpoints lacked CSRF token validation. Without proper sesskey inclusion, attackers could trick authenticated users into executing unauthorized page modifications. The fix adds Moodle's sesskey token to both client-side fetch requests and enforces server-side validation with `require_sesskey()`.

high

How CSRF protection gaps happen in Express.js applications and how to fix it

A high-severity security vulnerability was discovered in a React-Express booking application where the Express backend lacked CSRF middleware protection, while the frontend's coupon code input field in `Listing.jsx` allowed unrestricted user input. The fix implemented strict input validation using a regex pattern that whitelists only alphanumeric characters, hyphens, and underscores, preventing malicious payloads from reaching backend database operations.

high

How Regular Expression Denial of Service happens in JavaScript and how to fix it

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability in the picomatch glob-matching library, triggered by specially crafted extglob patterns that cause catastrophic regex backtracking. The fix upgrades picomatch to version 4.0.4 (with overrides pinning all transitive copies) in the client's dependency tree, eliminating the vulnerable regex evaluation path. Left unpatched, any code path that passes user-influenced glob patterns to picomatch could be weaponized to stall a

high

How insecure string copy functions happen in C and how to fix them

A high-severity buffer overflow risk was discovered in `login/main.c` where `strcpy()` was used to copy the `HOME` environment variable into a fixed-size 512-byte buffer without any bounds checking. An attacker controlling the `HOME` environment variable could overflow `pwd_file_name`, potentially corrupting memory or hijacking execution. The fix replaces the two-step `strcpy`/`strcat` pattern with a single, bounds-safe `snprintf` call.

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.