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:
- The form submits to the victim's authenticated session
- React Router 7.18.1 begins executing
updateUserSettings() - The database update completes
- CSRF validation finally runs and fails
- 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.