Skip to main content

CORS is not wildcard in production

ab-002576 · project-snapshot.injection.cors-not-wildcard
Severity: mediumactive

Why it matters

Access-Control-Allow-Origin: * tells every browser that any website can make cross-origin requests to this API and read the response. For public, unauthenticated endpoints that's fine; for anything that reads cookies, session tokens, or authenticated user data, wildcard CORS becomes the mechanism that lets a malicious site call your API while the user is logged in and scrape the response. AI coding tools reach for * as the "just make it work" default the moment a CORS error appears in console — it silences the error without solving the problem, and the comment // TODO: restrict in prod almost never comes back. Browser CORS is also not a complete defense (it doesn't block the request itself, only the reading of the response), but removing the wildcard is the table-stakes control that everything else assumes.

Severity rationale

Medium because wildcard CORS only becomes exploitable when combined with cookie-based auth or sensitive response data, and browsers still block credentialed `*` requests — the risk is contingent, not automatic.

Remediation

List specific origins:

const ALLOWED = ['https://app.example.com', 'https://admin.example.com']
const origin = request.headers.get('origin')
if (ALLOWED.includes(origin)) headers.set('Access-Control-Allow-Origin', origin)

Deeper remediation guidance and cross-reference coverage for this check lives in the security-hardening Pro audit — run that after applying this fix for a more exhaustive pass on the same topic.

Detection

  • ID: project-snapshot.injection.cors-not-wildcard
  • Severity: medium
  • What to look for: Enumerate every CORS configuration: cors() middleware calls, manual Access-Control-Allow-Origin: * headers in API routes, framework config (next.config.js headers section), vercel.json/netlify.toml CORS rules. Count * origins vs. specific origins.
  • Pass criteria: No Access-Control-Allow-Origin: * in production code paths. Either CORS is not configured (fine for same-origin apps), or it is configured with a specific origin or a function that validates against an allowlist.
  • Fail criteria: At least one wildcard CORS configuration that ships to production.
  • Skip (N/A) when: No API routes exist (purely static or SPA-only). Quote: "No API routes detected; no CORS surface."
  • Do NOT pass when: CORS uses origin: '*' only in development — it must be guarded by NODE_ENV !== 'production' to skip.
  • Report even on pass: "Scanned CORS config sites: N; wildcard origins: 0."
  • Detail on fail: "app/api/data/route.ts sets Access-Control-Allow-Origin: * unconditionally".
  • Remediation: List specific origins:
    const ALLOWED = ['https://app.example.com', 'https://admin.example.com']
    const origin = request.headers.get('origin')
    if (ALLOWED.includes(origin)) headers.set('Access-Control-Allow-Origin', origin)
    

Taxons

History