I inherited a Next.js 13 codebase in early 2024 that had not been touched in eight months. The lockfile showed 1,847 transitive dependencies. When I ran a fresh npm audit, twenty-three advisories lit up, including the infamous next CVE-2024-34351 SSRF in server actions that had been disclosed in May. The engineering manager shrugged and said the app "worked fine." That is the Next.js security story in miniature — a framework so productive that teams forget how much code they are actually shipping, and so opinionated that a single upstream flaw reaches every route.
This piece walks through how I hardened that codebase and three others over the following quarter. It is not a rehash of the Next.js security docs; it is what actually moved the needle when auditors and customer procurement teams started asking pointed questions.
The dependency iceberg nobody wants to map
A minimal create-next-app scaffold in mid-2024 produced a node_modules directory with roughly 370 packages at install time. Add next-auth, a UI library like shadcn/ui, a form library, an ORM, and telemetry, and you cross 1,200 packages fast. Most teams I worked with had never generated an SBOM, and when I produced one using cyclonedx-npm, the CVSS 7.0+ count surprised the tech leads every single time.
The uncomfortable truth is that Next.js the framework is a small sliver of the attack surface. The framework team has gotten faster at patching — the middleware authorization bypass CVE-2025-29927 disclosed in March 2025 shipped a patched 14.2.25 and 15.2.3 within days — but those fixes do nothing for the twenty webpack loaders, PostCSS plugins, and React utility libraries pulled in by your app. Treat the Next.js version as necessary but wildly insufficient.
What the March 2025 middleware CVE should teach you
CVE-2025-29927 let attackers bypass middleware checks entirely by sending an x-middleware-subrequest header. Applications using Next.js middleware for auth guards — which, based on the telemetry Vercel later shared, was a majority of deployed apps — had an authorization bypass that was trivial to exploit. The fix was a one-line patch, but the lesson was architectural: never rely on middleware as your sole authorization boundary. Defense in depth means the route handler itself re-validates the session, because middleware is easy to misconfigure and, as this CVE proved, occasionally just broken at the framework level.
I now audit every Next.js codebase for what I call "middleware-only routes" — endpoints where the only check on the user's identity happens in middleware.ts. Those get refactored first.
Server actions deserve their own threat model
Server actions landed as stable in Next.js 14 and immediately became the vector for a string of CVEs in 2024, including the SSRF in CVE-2024-34351 and the cache poisoning issue in CVE-2024-46982 disclosed in September. The root cause is that server actions blur the RPC boundary; a function call in a React component turns into an HTTP POST with serialized arguments, and if you are not explicit about what the server trusts, you inherit every input validation mistake the framework chose for you. I write a Zod schema at the top of every server action now, full stop, and I log unexpected payload shapes to detect probing.
Image optimization: the forgotten SSRF vector
The next/image component is wonderful until someone points it at an attacker-controlled URL. Next.js has shipped patches for image-related CVEs twice in the last two years, including CVE-2023-29827 back when it was still called an "image sizing" issue. Your remotePatterns config is a security control, and it should list exact hostnames with protocol pinning. Wildcards like hostname: '**' are a crash reporter waiting to happen when someone inevitably writes a CMS integration that echoes user-provided URLs into src.
The build-time supply chain is half the attack surface
Next.js builds in CI, and the build process pulls in different packages than runtime does — webpack plugins, SWC binaries, PostCSS transformers, Tailwind's JIT compiler. Any of these running with network access during next build can exfiltrate environment variables. The xz-utils incident in March 2024 reminded everyone that build-time tooling is a perfectly good place to hide a backdoor. I now recommend three changes to every team:
- Run
next buildin a network-restricted container that can pull registries but cannot reach arbitrary internet hosts - Generate an SBOM at build time and ship it as a build artifact, not an afterthought
- Pin the
@next/swc-*native binary versions inoverrides, because those are effectively untyped blobs your CI trusts
Lockfile hygiene and the overrides escape hatch
A Next.js team I audited in August 2024 had next pinned to 14.2.0 but was transitively pulling styled-jsx@5.1.1, which had an XSS advisory from early 2024. Their lockfile was clean on a direct audit because the direct dependency was fine; the problem lived two levels deep. This is where overrides in package.json earns its keep — you can force a transitive dependency to a patched version without waiting for every intermediate package to release. The tradeoff is that you now own that override forever, so document it in a SECURITY.md and review quarterly.
CSP, nonces, and the App Router reality
Next.js 14's App Router made strict CSP with nonces practical for the first time — you can generate a nonce in middleware and inject it into inline scripts via the nonce prop. In practice, maybe one team in ten I have reviewed actually ships a restrictive CSP. Start with script-src 'self' 'nonce-{generated}' and remove unsafe-inline from every directive you can. Report-only mode for two weeks, then enforce. The performance cost is negligible and the defense-in-depth win is real, especially against supply chain injections that might sneak through a compromised npm package.
Environment variable leakage is still rampant
I have lost count of the Next.js projects that accidentally shipped secrets to the client because someone prefixed a server-only secret with NEXT_PUBLIC_. The framework inlines those at build time into the JavaScript bundle. A 2024 sweep using trufflehog against public Vercel deployments found hundreds of exposed Stripe keys, Sentry DSNs, and internal API tokens. Your build should fail if any environment variable matching a known secret pattern gets the NEXT_PUBLIC_ prefix — that is a one-afternoon pre-build hook that has stopped three production incidents on teams I have worked with.
How Safeguard Helps
Safeguard generates a CycloneDX SBOM on every Next.js build and performs reachability analysis to tell you which of the 1,800 transitive dependencies actually execute in your production bundle, not just which ones are installed. Griffin AI summarizes middleware and server-action CVEs in plain English and flags which of your specific routes are affected, turning a framework advisory into a concrete ticket. Policy gates block builds that introduce new critical CVEs or that add NEXT_PUBLIC_ variables matching secret patterns, catching the class of mistakes that plagues Next.js teams most often. Continuous monitoring tracks the upstream Next.js release feed and raises a finding the hour a relevant CVE drops, so you never again learn about a middleware bypass from a customer's pentest report.