By Shyam Verma

How to Fix & Prevent React2Shell Attack on Next.js

How to Fix & Prevent React2Shell Attack on Next.js

On December 3, 2025, React2Shell dropped - a CVSS 10.0 vulnerability letting attackers run any code on your Next.js server. No login needed. Within hours, state-sponsored groups were exploiting it.

This guide shows exactly how I secured a production app, with commands you can copy-paste.

TL;DR - Quick Fix

If you're in a hurry, run this now:

# Check if you're vulnerable
npm list next

# Fix it
npx fix-react2shell-next

# Verify
npx fix-react2shell-next
# Should say: "Your Next.js installation is not vulnerable"

Then read the rest of this guide because patching alone isn't enough.


Am I Affected?

Quick check:

  1. Using App Router? (files in /app directory) → Potentially vulnerable
  2. Using Pages Router only? (files in /pages directory) → Safe
  3. Static export? (no server) → Safe
  4. Next.js 13.x or 14.x stable? → Safe

Vulnerable versions:

Version Patch to
15.0.x 15.0.5+
15.1.x 15.1.9+
15.2.x 15.2.6+
15.3.x 15.3.6+
15.4.x 15.4.8+
15.5.x 15.5.7+
16.0.x 16.0.7+

Full details: Next.js Security Advisory


What's the Actual Risk?

React2Shell exploits a flaw in how React Server Components handle incoming data. An attacker sends a malformed request, and your server executes their code.

What attackers can do:

  • Read your environment variables (API keys, database passwords)
  • Access your database
  • Pivot to other systems on your network
  • Install crypto miners or backdoors

For the technical deep-dive: Datadog's analysis or Wiz's breakdown.


Step 1: Patch Now

The critical fix. Do this first.

# Option A: Use the official tool
npx fix-react2shell-next

# Option B: Manual upgrade
npm install next@15.5.7  # or latest in your branch

Verify it worked:

npx fix-react2shell-next
# Expected: "Your Next.js installation is not vulnerable to CVE-2025-55182"

Step 2: Scan for Compromise

Before assuming you're safe, check if you were already exploited.

Assetnote's scanner can detect vulnerable instances:

# Clone and install
git clone https://github.com/assetnote/react2shell-scanner
cd react2shell-scanner
pip install -r requirements.txt

# Safe scan (no code execution on target)
python3 scanner.py -u https://your-app.com --safe-check

Check your logs for:

  • Unusual POST requests to /_next/ paths
  • Multipart requests to non-upload endpoints
  • Spikes in 500 errors

Step 3: Hide Your Tech Stack

Attackers scan for Next.js servers by checking response headers. Remove the fingerprints.

In next.config.ts:

const nextConfig: NextConfig = {
  headers: async () => [
    {
      source: '/(.*)',
      headers: [
        { key: 'X-Powered-By', value: '' },
        { key: 'Server', value: '' }
      ],
    },
  ],
};

If using a reverse proxy (Nginx/Caddy):

# Nginx
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
# Caddy
servers {
    headers {
        -Server
        -X-Powered-By
    }
}

Verify:

curl -I https://your-app.com 2>/dev/null | grep -E "Server:|X-Powered-By:"
# Should return nothing

Step 4: Rotate All Secrets

This is the step most people skip. Don't.

Even if you patched quickly, your secrets may have been stolen during the vulnerability window. Assume breach.

What to rotate:

Category Examples
Database Connection strings, passwords
APIs Stripe, OpenAI, Mailjet, Vimeo
Auth JWT secrets, session keys
CMS Admin tokens, API keys
Webhooks Signing secrets

Rotation process:

  1. Generate new credentials in service dashboard
  2. Update .env.production
  3. Deploy with new credentials
  4. Verify the app works
  5. Then revoke old credentials

Quick validation script:

// test-credentials.ts
async function test(name: string, fn: () => Promise<boolean>) {
  try {
    const ok = await fn();
    console.log(ok ? `✓ ${name}` : `⚠ ${name}: Check permissions`);
  } catch (e) {
    console.log(`✗ ${name}: ${e.message}`);
    process.exit(1);
  }
}

// Add tests for each service
await test('Database', async () => {
  // Your DB connection test
  return true;
});

await test('Stripe', async () => {
  const res = await fetch('https://api.stripe.com/v1/balance', {
    headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}` }
  });
  return res.ok;
});

Run after each deployment: npx tsx test-credentials.ts


Step 5: Lock Down API Access

Where possible, restrict your API keys:

  1. Use restricted keys - Stripe, for example, lets you create keys with specific permissions only
  2. IP allowlisting - Some services let you restrict API access to your server IPs
  3. VPN for admin panels - Put sensitive dashboards behind a VPN

Step 6: Fix Container Permissions

If you're running Docker, check your file permissions. Common issue: upload directories owned by wrong user.

# Set correct ownership (adjust path and UID for your setup)
sudo chown -R 1000:1000 /var/data/uploads
sudo find /var/data/uploads -type d -exec chmod 755 {} \;
sudo find /var/data/uploads -type f -exec chmod 644 {} \;

Add this to your deployment script so it runs automatically.


Verification Checklist

Run through this after remediation:

# 1. Version check
npm list next
# Should show patched version

# 2. Official verification
npx fix-react2shell-next
# Should say "not vulnerable"

# 3. Header check
curl -I https://your-app.com | grep -E "Server:|X-Powered-By:"
# Should return nothing

# 4. Credential test
npx tsx test-credentials.ts
# All should pass

Copy-Paste Checklist

Save this for your incident response:

Immediate (Do Now)

  • npx fix-react2shell-next
  • Verify: npm list next shows patched version
  • Check logs for exploitation attempts

Today

  • Hide server headers (Next.js config + reverse proxy)
  • Scan with react2shell-scanner
  • Start secret rotation

This Week

  • Complete all secret rotations
  • Revoke old credentials (after verifying new ones work)
  • Review container permissions
  • Set up log monitoring for suspicious patterns
  • Restrict API keys where possible

What I learned from this

Patching is step one, not the finish line. The fix closes the hole but doesn't undo whatever happened during the window you were exposed.

I'm treating this as a breach until proven otherwise. When something scores 10.0 and gets weaponized the same day, you have to assume someone tried it on your server. That's why I rotated everything.

The defense-in-depth stuff matters more than I used to think. Hiding headers feels like security theater until you realize attackers are scanning for Next.js servers specifically. Same with IP restrictions on API keys - minor friction, real protection.

I also wrote that credential test script for myself, not just this guide. It runs in CI now. Catches problems before they hit production.

If you're not already following Next.js security updates and GitHub advisories, set that up. I found out about this one through Twitter, which is not ideal.


Questions? Reach out on Twitter @ssv445 or LinkedIn.

Interested in Collaborating?

Whether it's a startup idea, a technical challenge, or a potential partnership—let's have a conversation.

20+
Years Experience
150K+
Websites Powered
2
Successful Exits
7x
Faster with AI