claude-code - 💡(How to fix) Fix [BUG] Security remediation generates vercel.json with mutually-exclusive routes + headers — security headers silently not applied [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#53056Fetched 2026-04-25 06:13:32
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
labeled ×3

Error Message

  • Silent security failure — no error in build logs, no warning in Vercel dashboard
  1. Detect if routes is present alongside headers and warn/refuse to generate

Root Cause

Vercel has two routing systems:

  • Legacy (routes) — overrides ALL other config processing when present
  • Modern (redirects, rewrites, headers, cleanUrls) — composable, coexist fine

When routes is present, Vercel silently ignores the headers block. This is documented in Vercel's config reference but not prominently surfaced. Claude Code correctly knows both systems exist but generates them together, producing a configuration that looks correct but silently fails.

Fix Action

Workaround

Replace routes with middleware.js for path blocking. Keep headers in vercel.json as-is — it applies correctly once routes is removed.

Confirmed working in production after migration (nuvaus.com, Vercel Pro, static HTML+Tailwind+Alpine.js).

Code Example

{
  "routes": [
    { "src": "/.env",  "status": 404 },
    { "src": "/.git",  "status": 404 }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Content-Security-Policy", "value": "default-src 'self'..." },
        { "key": "X-Frame-Options",         "value": "DENY" },
        { "key": "X-Content-Type-Options",  "value": "nosniff" }
      ]
    }
  ]
}

---

// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
  const url = new URL(request.url)
  const blocked = ['/.env', '/.git', '/wp-admin']
  if (blocked.some(p => url.pathname.startsWith(p))) {
    return new Response('Not Found', { status: 404 })
  }
}
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report

What's Wrong?

When Claude Code performs a security audit and remediation on a Vercel-hosted static site, it may generate a vercel.json combining the legacy routes array with the modern headers array. These two top-level fields are mutually exclusive in Vercel: the presence of routes silently disables headers processing, so all security headers never reach the client.

Steps to Reproduce

  1. Have a static Vercel site (HTML + no build step)
  2. Ask Claude Code to run a security audit and apply fixes
  3. Claude Code generates vercel.json with routes (for path-blocking) AND headers (for security headers)
  4. Deploy succeeds with no errors
  5. Run curl -sI https://your-domain.com — security headers are absent

Generated (broken) vercel.json:

{
  "routes": [
    { "src": "/.env",  "status": 404 },
    { "src": "/.git",  "status": 404 }
  ],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Content-Security-Policy", "value": "default-src 'self'..." },
        { "key": "X-Frame-Options",         "value": "DENY" },
        { "key": "X-Content-Type-Options",  "value": "nosniff" }
      ]
    }
  ]
}

What Should Happen?

Claude Code should generate a vercel.json where security headers actually reach the browser. When path-blocking is also needed, it should use compatible alternatives:

  1. middleware.js for path blocking (compatible with headers)
  2. redirects / rewrites (modern syntax, coexists with headers)
  3. NOT routes when headers are also needed

Working approach using middleware:

// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
  const url = new URL(request.url)
  const blocked = ['/.env', '/.git', '/wp-admin']
  if (blocked.some(p => url.pathname.startsWith(p))) {
    return new Response('Not Found', { status: 404 })
  }
}

Root Cause

Vercel has two routing systems:

  • Legacy (routes) — overrides ALL other config processing when present
  • Modern (redirects, rewrites, headers, cleanUrls) — composable, coexist fine

When routes is present, Vercel silently ignores the headers block. This is documented in Vercel's config reference but not prominently surfaced. Claude Code correctly knows both systems exist but generates them together, producing a configuration that looks correct but silently fails.

Impact

  • Silent security failure — no error in build logs, no warning in Vercel dashboard
  • Invisible to users — headers appear correct in vercel.json but never reach the browser
  • Real-world incident — after remediating 20/22 security audit findings (commit e043f73), only Strict-Transport-Security (set by Vercel automatically) was present in responses; CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy, COOP, X-Content-Type-Options were all silently absent
  • Verification required — discovery only via curl -sI https://domain.com/?bust=$(date +%s%N) (random query to bypass Edge cache)

Workaround

Replace routes with middleware.js for path blocking. Keep headers in vercel.json as-is — it applies correctly once routes is removed.

Confirmed working in production after migration (nuvaus.com, Vercel Pro, static HTML+Tailwind+Alpine.js).

Suggested Fix

When generating vercel.json with security headers:

  1. Detect if routes is present alongside headers and warn/refuse to generate
  2. Auto-generate middleware.js instead of routes entries when path-blocking is needed
  3. Or add a post-generation verification step: curl -sI <deployed-url> and compare actual headers against vercel.json

Environment

  • OS: Windows 11 Pro 10.0.26200
  • Claude Code: latest (2.1.119+)
  • Vercel: Pro plan
  • Site type: static (HTML + Tailwind CDN + Alpine.js v3, no build step)

extent analysis

TL;DR

To fix the issue, replace the routes array in vercel.json with a middleware.js file for path blocking, allowing the headers array to be processed correctly.

Guidance

  • Identify if both routes and headers are present in the generated vercel.json and prioritize using middleware.js for path blocking to avoid conflicts.
  • Verify the fix by running curl -sI https://your-domain.com and checking for the presence of security headers.
  • Consider implementing a post-generation verification step to compare actual headers against those defined in vercel.json.
  • Use redirects or rewrites instead of routes when headers are also needed for a compatible configuration.

Example

// middleware.js
import { NextResponse } from 'next/server'
export function middleware(request) {
  const url = new URL(request.url)
  const blocked = ['/.env', '/.git', '/wp-admin']
  if (blocked.some(p => url.pathname.startsWith(p))) {
    return new Response('Not Found', { status: 404 })
  }
}

Notes

This solution assumes that the middleware.js approach is compatible with the existing static site configuration and Vercel Pro plan. It's essential to test and verify the changes to ensure they work as expected in the production environment.

Recommendation

Apply the workaround by replacing routes with middleware.js for path blocking, as it allows the headers array to be processed correctly and ensures security headers reach the client.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Security remediation generates vercel.json with mutually-exclusive routes + headers — security headers silently not applied [1 participants]