# Risk map — vercel/next.js

> Canonical page: https://codevetter.com/unpack/nextjs/risk-map

# Risk map — vercel/next.js

Scanned commit 2861a364fcbe75fe96b0b9127f9b335edfcf288c (2026-09-22). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

The biggest blast-radius surfaces are the header-filtering allowlist, the dev-server origin guard, the Server Actions CSRF check, the image optimizer's SSRF path, and the PR-adoption script that grants contributor code access to CI secrets. Structurally, `app-render.tsx` (10,768 lines), `build/index.ts` (4,815) and `base-server.ts` (3,459) are single files that concentrate enormous behaviour and are the natural home of regressions. There is also a large amount of implicit coupling via global symbols, `process.env.__NEXT_*` compile-time constants, and an env-var-driven test matrix, all of which make 'works locally' a weak signal unless the exact CI env is mirrored.
- `INTERNAL_HEADERS` is a hardcoded allowlist, not a prefix rule — any new internal header (e.g. a future `x-middleware-*`) that is read by server code but not added to this array is forgeable by an external client, and the repo's own AGENTS.md flags this as a mandatory PR review check. — [packages/next/src/server/lib/server-ipc/utils.ts#L38-L62](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/lib/server-ipc/utils.ts#L38-L62) [AGENTS.md](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/AGENTS.md)
- `filterInternalHeaders` is skipped entirely when `NEXT_PRIVATE_TEST_HEADERS` is set; anything that leaks that env var into a non-test process disables internal-header sanitisation for the whole server. — [packages/next/src/server/lib/router-server.ts#L310-L314](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/lib/router-server.ts#L310-L314)
- `blockCrossSiteDEV` only guards URLs matching `/_next` or `/__nextjs` and explicitly excludes `/_next/image`, `/_next/static/media`, and `/_next/static/immutable/media` from the check, so those three paths are reachable cross-site in dev by design — a deliberate CORS tradeoff worth re-checking whenever media handling changes. — [packages/next/src/server/lib/router-utils/block-cross-site-dev.ts#L93-L115](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/lib/router-utils/block-cross-site-dev.ts#L93-L115)
- The dev MCP server at `/_next/mcp` executes agent tools (`get-logs`, `get-server-action-by-id`, `compile-route`) and is protected only by virtue of sitting under the `/_next` prefix that `blockCrossSiteDEV` covers; it parses request bodies with a 1MB cap and swallows every error into a generic JSON-RPC 500. — [packages/next/src/server/mcp/get-mcp-middleware.ts#L16-L43](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/mcp/get-mcp-middleware.ts#L16-L43) [packages/next/src/server/mcp/tools/](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/mcp/tools/)
- Server Actions CSRF hinges on `x-forwarded-host` being trustworthy: `getIsPossibleServerAction`-adjacent logic prefers `x-forwarded-host` over `host` when comparing to `Origin`, so a misconfigured reverse proxy that lets clients set that header weakens the check. — [packages/next/src/server/app-render/action-handler.ts#L529-L545](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/app-render/action-handler.ts#L529-L545) [packages/next/src/server/app-render/action-handler.ts#L710-L745](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/app-render/action-handler.ts#L710-L745)
- SSRF protection in the image optimizer is DNS-resolution-based and opt-out: `fetchExternalImage` resolves the hostname and rejects private IPs via `isPrivateIp`, but `images.dangerouslyAllowLocalIP = true` disables it, and the resolve-then-fetch pattern is inherently TOCTOU against DNS rebinding. — [packages/next/src/server/image-optimizer.ts#L521-L555](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/image-optimizer.ts#L521-L555) [packages/next/src/server/is-private-ip.ts](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/is-private-ip.ts)
- `matchWildcardDomain` in `csrf-protection.ts` is a hand-rolled glob matcher written specifically because micromatch cannot run at the edge; it carries explicit special-cases (rejecting bare `*`/`**`, rejecting `**` in non-terminal position) and is the shared basis for both `serverActions.allowedOrigins` and `allowedDevOrigins`, so a bug there affects two security checks at once. — [packages/next/src/server/app-render/csrf-protection.ts#L1-L90](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/app-render/csrf-protection.ts#L1-L90) [packages/next/src/server/lib/router-utils/block-cross-site-dev.ts#L5](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/lib/router-utils/block-cross-site-dev.ts#L5)
- `pnpm pr-adopt` (`scripts/adopt-pr.js`) re-pushes contributor commits into `vercel/next.js`, which grants that code access to repository secrets during install/build/test; it defends with an interactive confirmation naming the author and head SHA, a throwaway worktree, and `core.hooksPath` redirected to an empty directory via `GIT_CONFIG_COUNT` to neutralise `.husky/*` hooks. — [scripts/adopt-pr.js](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/scripts/adopt-pr.js) [package.json#L6](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/package.json#L6) [AGENTS.md](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/AGENTS.md)
- `app-render.tsx` is 10,768 lines and holds the entire RSC render, prospective/final prerender, and resume-data-cache logic in one module — the single highest blast-radius file in the repo. — [packages/next/src/server/app-render/app-render.tsx](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/app-render/app-render.tsx) [packages/next/src/server/app-render/app-render.tsx#L1111-L1260](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/app-render/app-render.tsx#L1111-L1260)
- Cache handlers are wired through nine `Symbol.for('@next/cache-handlers*')` globals rather than imports; because they live on `globalThis`, two copies of the module share state, and a stale symbol contract between the framework and an external handler package fails silently rather than at type-check time. — [packages/next/src/server/use-cache/handlers.ts#L14-L52](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/use-cache/handlers.ts#L14-L52)
- Fragile coupling: `renderServerOpts.serverFields.routerServerHandler = requestHandlerImpl` creates a cycle where the render server calls back into the router server's handler, which is what makes the `handleIndex > 5` recursion guard necessary. — [packages/next/src/server/lib/router-server.ts#L942](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/lib/router-server.ts#L942) [packages/next/src/server/lib/router-server.ts#L470-L475](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/lib/router-server.ts#L470-L475)
- Deprecated-but-live config surface is a trap: `experimental.ppr`, `experimental.cacheComponents`, and `images.domains` are all marked deprecated yet still read at runtime, so code must handle both the old and new location of the same setting. — [packages/next/src/server/config-shared.ts#L1231-L1234](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/config-shared.ts#L1231-L1234) [packages/next/src/server/config-shared.ts#L1373-L1375](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/config-shared.ts#L1373-L1375) [packages/next/src/server/image-optimizer.ts#L173-L182](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/image-optimizer.ts#L173-L182)
- `NextServer` retains five deprecated methods that all emit the same 'Please use `app.getRequestHandler()` with an adjusted parsed URL instead.' warning — legacy API surface that still has to keep working for the programmatic-server use case. — [packages/next/src/server/next.ts#L97-L182](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/next.ts#L97-L182)
- Compile-time env constants are the main source of 'passes locally, fails in CI': `process.env.NODE_ENV !== 'production'` and `process.env.__NEXT_DEV_SERVER` mean different things (`next build --debug-prerender` produces a development bundle without a dev server), and snapshot output differs between Turbopack (`react-dom/server.edge`) and webpack (`react-dom/server.node`). — [packages/next/src/build/define-env.ts#L160](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/build/define-env.ts#L160) [AGENTS.md](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/AGENTS.md) [.agents/skills/dce-edge/SKILL.md](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/.agents/skills/dce-edge/SKILL.md)
- Untested-by-construction flow: the dev memory-threshold restart path exits the process from inside a request's `finally` block, so any in-flight streaming responses are dropped; it is triggered by real heap pressure and therefore hard to cover in the integration suite. (inferred) — [packages/next/src/server/lib/start-server.ts#L253-L270](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/packages/next/src/server/lib/start-server.ts#L253-L270)
- The `patches/` directory carries 11 pnpm patches against third-party packages (`@rspack/core@1.6.7`, `playwright-core@1.61.0`, `@types/node@20.17.7`, `@modelcontextprotocol/sdk`, `webpack-sources`, `taskr`, …), each of which must be re-verified on every upstream bump. — [package.json#L1](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/package.json#L1) [patches/](https://github.com/vercel/next.js/blob/2861a364fcbe75fe96b0b9127f9b335edfcf288c/patches/)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
