#Next.js#Security#Web Development#Middleware#Cache

Next.js Security Patches: What You Need to Fix Now

webhani·

The Vulnerabilities

Next.js released a set of security fixes covering high, moderate, and low severity issues. Both App Router and Pages Router are affected, which means this touches most production Next.js deployments. The vulnerability types addressed:

  • Denial of Service — specific request patterns can overload the server
  • Middleware bypass — authentication and authorization logic in middleware can be circumvented
  • Cross-Site Scripting (XSS)
  • Server-Side Request Forgery (SSRF)
  • Cache Poisoning

If you're running Next.js in production, check your current version and update.

Middleware Bypass: Why This One Matters Most

Middleware is commonly used in Next.js to handle authentication — checking tokens, session cookies, or JWT claims before a request reaches a route. If that middleware can be bypassed, authenticated routes become publicly accessible.

The problematic pattern:

// Relying solely on middleware for auth — vulnerable pattern
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
 
export function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token");
  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next();
}
 
export const config = {
  matcher: ["/dashboard/:path*", "/api/protected/:path*"],
};

If middleware is bypassed, /api/protected/* routes have no protection at all.

The fix is defense in depth — verify authentication at each layer independently:

// app/api/protected/data/route.ts
import { NextRequest, NextResponse } from "next/server";
import { verifySession } from "@/lib/auth";
 
export async function GET(request: NextRequest) {
  const token = request.cookies.get("auth-token")?.value;
 
  // Independent auth check — does not rely on middleware running
  if (!token) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
 
  const session = await verifySession(token);
  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
 
  return NextResponse.json({ userId: session.userId });
}

Middleware should be the first filter, not the only one.

Cache Poisoning in the App Router

Cache poisoning allows an attacker to store malicious content in the cache, which then gets served to subsequent users. Next.js App Router uses aggressive caching by default, which can amplify the impact if an attacker can inject into the cache.

Explicit cache control reduces the attack surface significantly:

// Force dynamic rendering for routes with sensitive data
export const dynamic = "force-dynamic";
 
// Be explicit about fetch caching behavior
async function loadPageData(userId: string) {
  // User-specific data — never cache
  const userData = await fetch(`https://api.example.com/users/${userId}`, {
    cache: "no-store",
    headers: { Authorization: `Bearer ${await getToken()}` },
  });
 
  // Public data — controlled freshness
  const announcements = await fetch("https://api.example.com/announcements", {
    next: { revalidate: 300 },
  });
 
  return { userData: await userData.json(), announcements: await announcements.json() };
}

Don't rely on Next.js inferring what to cache. For anything user-specific or sensitive, use cache: 'no-store' explicitly.

SSRF: Validate URLs Before Fetching

Server Actions and API Routes can make server-side HTTP requests. Without URL validation, an attacker can trigger requests to internal services, cloud metadata endpoints, or other restricted resources.

// app/actions/fetch-content.ts
"use server";
 
const ALLOWED_HOSTS = new Set(["api.example.com", "images.example.com"]);
 
function validateUrl(rawUrl: string): URL {
  let parsed: URL;
  try {
    parsed = new URL(rawUrl);
  } catch {
    throw new Error("Invalid URL format");
  }
 
  if (!["http:", "https:"].includes(parsed.protocol)) {
    throw new Error("Only HTTP/HTTPS allowed");
  }
 
  if (!ALLOWED_HOSTS.has(parsed.hostname)) {
    throw new Error(`Host ${parsed.hostname} is not allowed`);
  }
 
  return parsed;
}
 
export async function fetchExternalContent(url: string) {
  const validated = validateUrl(url); // throws on invalid input
  const response = await fetch(validated.toString());
  return response.json();
}

An allowlist is more reliable than a blocklist. Internal IP ranges (169.254.x.x, 10.x.x.x, 192.168.x.x) are easy to miss in a blocklist and can expose cloud provider metadata endpoints.

CSP Headers as an XSS Defense Layer

A Content Security Policy header limits what can execute in a user's browser, reducing the impact of XSS even when a vulnerability exists:

// next.config.mjs
const nextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          {
            key: "Content-Security-Policy",
            value: [
              "default-src 'self'",
              "script-src 'self'",
              "style-src 'self' 'unsafe-inline'",
              "img-src 'self' data: https:",
              "connect-src 'self' https://api.example.com",
              "frame-ancestors 'none'",
            ].join("; "),
          },
          {
            key: "X-Frame-Options",
            value: "DENY",
          },
        ],
      },
    ];
  },
};
 
export default nextConfig;

Start strict and loosen specific directives as legitimate requirements emerge. CSP reporting (report-uri or report-to) is worth adding to catch policy violations in the wild.

Immediate Action Items

  1. Update Next.js — run npm install next@latest and check the release notes for the exact affected version range
  2. Audit middleware usage — identify every route protected only by middleware and add independent authentication in the route handler
  3. Review cache settings — audit fetch() calls and route-level cache config; make dynamic and authenticated routes explicit
  4. Add URL validation — review Server Actions and API Routes that make outbound requests and apply an allowlist
  5. Set up automated dependency updates — Dependabot or Renovate configured to auto-merge patch releases keeps future exposure windows short

Takeaways

  • Next.js patched DoS, middleware bypass, XSS, SSRF, and cache poisoning — all categories with real production impact
  • Middleware-only authentication is a structural risk regardless of this specific patch
  • Explicit cache control prevents accidental exposure of sensitive data and is good practice independent of security patches
  • URL allowlists and CSP headers provide meaningful defense layers beyond keeping dependencies current