Vercel Edge and Middleware audit
TL;DR: Vercel splits compute into Node serverless functions, Edge Functions (V8 isolates), and Middleware (runs before every request to a Next.js / framework deployment). Audit surface: middleware that fails-open on errors, header trust (
x-forwarded-*), the CVE-2025-29927 middleware-bypass shape (see nextjs-middleware-cve-2025-29927), env var exposure via build leak, ISR cache poisoning, and the per-region cold-start race.
What it is
Vercel deployments include multiple compute layers:
- Static — pre-rendered HTML.
- ISR — periodically regenerated static.
- Edge — V8 isolate, web platform APIs.
- Serverless — Node.js Lambda-style, full Node APIs.
- Middleware — Edge runtime function intercepting all requests before they reach a handler.
Each layer has different trust assumptions. Middleware in particular runs first — bugs there bypass everything else.
Bug patterns
1. Middleware bypass (CVE-2025-29927 family)
- Header
x-middleware-subrequestwas honoured by Next.js to skip middleware on internal subrequests. Attacker sent the header on external requests → middleware (often the only authz check) skipped → unauthenticated access to gated pages. - See nextjs-middleware-cve-2025-29927 for the canonical exploit; this is a pattern worth checking across other “internal subrequest” header conventions.
2. Middleware fails open on exception
1
2
3
4
5
6
7
8
9
export async function middleware(req) {
try {
const user = await verifyJWT(req.cookies.get('token'));
if (!user) return NextResponse.redirect('/login');
} catch (e) {
// log and let through
}
return NextResponse.next();
}
JWT verification throws → catch swallows → request proceeds unauthenticated.
- Fix: fail-closed; any auth check exception is a denial.
3. Header trust (x-forwarded-*)
req.headers.get('x-forwarded-for')is set by Vercel proxy from the connecting IP.- BUT if your middleware also runs in
wrangler dev/ local Next dev, the header is attacker-controlled. - And attacker can include multiple comma-separated IPs; the “client IP” is the first one — but Vercel guarantees only that the last is the connecting IP. Picking the wrong index spoofs origin.
4. ISR / Data cache poisoning
revalidateTag('user-' + userId)called with attacker-controlled tag can invalidate caches for any user.next/cachedata cache keyed by URL + headers; if the cache key doesn’t include auth context, one user’s cached data serves to another.- See cache-poisoning.
5. Environment variable exposure
- Build-time env vars prefixed
NEXT_PUBLIC_*are inlined into client bundles. Devs accidentally mark sensitive vars public. process.env.API_KEYin a client component (Next.js will warn but not block in all configs) inlines the secret.- Fix: never
NEXT_PUBLIC_*for secrets;next buildoutput audit.
6. Edge runtime API mismatches
- Edge runtime is V8 only — no
fs, no most Node APIs. Devs sometimes useBuffer,crypto.createHash,node:fsthinking they work; Vercel polyfills some, fails on others. - Polyfill bugs occasionally introduce subtle differences (timing, randomness quality).
7. Geographic / IP rate limit at edge
- Vercel built-in
geolocation()returns claimed country from connecting IP. Trustworthy. - But edge functions in different regions may see different DNS for the same upstream → SSRF target varies by deploy region.
8. ISR on-demand revalidate
app/api/revalidate/route.tstypically requires a secret. If the secret is leaked (env in client bundle) or weak (no entropy), attacker forces revalidation → DoS or cache poisoning if they can also trigger a render with attacker input.
9. Middleware matcher misconfig
export const config = { matcher: ['/dashboard/:path*'] }— typo or pattern bug excludes a gated path. Audit everymatcheragainst your route list.- Negative patterns (
!/api/...) had historical bugs around regex anchoring.
10. Preview deploys exposed
- Every PR gets a preview URL on
*.vercel.app. Indexed by Google sometimes. - Preview deploys often have looser auth (testing creds, debug flags) → auth bypass against the same code that’s locked down in prod.
- Fix: Vercel Password Protection or SSO on previews; remove debug flags from
process.envdefaults.
11. Image Optimization SSRF
<Image src="https://victim/...">withnext.config.jsimages.domains = ['*']lets the Vercel image optimizer fetch any URL → SSRF from Vercel’s image worker to internal services.- Fix: restrict
images.domains/images.remotePatternsstrictly.
12. Server Actions specifics
- See nextjs-server-actions-audit — also relevant on Vercel.
Audit checklist
1
2
3
4
5
6
7
8
9
10
# Middleware
rg -n 'export (async )?function middleware' app/ src/
# Matcher patterns
rg -n 'matcher:\s*\[' app/ src/ next.config.*
# Public env prefix
grep -nE 'NEXT_PUBLIC_' .env* next.config.*
# Revalidate routes
rg -n 'revalidateTag|revalidatePath|res\.revalidate' app/ pages/
# Image domain wildcards
grep -nE 'domains|remotePatterns' next.config.*
Hardening
- Fail-closed in middleware; no error swallow.
- Restrict
NEXT_PUBLIC_*to truly public values; lint in CI. - Preview deploys behind auth (Vercel Password Protection or SSO).
- Pin Next.js to a version past the middleware-bypass fix.
images.remotePatternswith explicitprotocol/hostname/pathname.