Vercel
CORS Misconfiguration

CORS Misconfiguration on Vercel

Next.js API routes and Vercel serverless functions frequently set Access-Control-Allow-Origin: * to "make CORS errors go away." This allows any website to make requests to your API, potentially accessing data with your users' credentials.

Scan Your Vercel App

How It Happens

CORS errors are one of the most common frustrations in web development. When a developer builds a Next.js app on Vercel and the frontend cannot reach an API route due to CORS, the immediate fix is adding Access-Control-Allow-Origin: * to the response. This eliminates the error but opens the API to requests from any domain. The combination of Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true is the most dangerous misconfiguration. It tells the browser to send cookies with cross-origin requests and accept responses from any origin. However, browsers actually block this specific combination, so developers often switch to reflecting the request's Origin header in the response, which achieves the same dangerous effect without the browser blocking it. Vercel serverless functions used as standalone APIs (separate from a Next.js frontend) are particularly prone to this. Developers set permissive CORS to allow their frontend to call the API, but the permissive policy also allows any malicious site to make the same calls.

Impact

Permissive CORS with credentials allows a malicious website to make authenticated requests to your Vercel API as the visiting user. If user A visits evil.com while logged into your app, evil.com can call your API routes with user A's cookies and read the responses. This enables data theft without any XSS vulnerability in your application. The attacker hosts a page on their own domain that makes fetch requests to your API. The browser includes the user's cookies, and the permissive CORS headers allow the attacker's JavaScript to read the response. For APIs that handle sensitive data (user profiles, financial records, private messages), CORS misconfiguration is equivalent to exposing the data to any website the user visits. The attack requires no interaction beyond visiting the malicious site.

How to Detect

Send a request to your Vercel API with a custom Origin header: curl -H "Origin: https://evil.com" -I https://your-app.vercel.app/api/endpoint. If the response includes Access-Control-Allow-Origin: https://evil.com or Access-Control-Allow-Origin: *, the CORS configuration is too permissive. Check the Access-Control-Allow-Credentials header. If it is true alongside a permissive Allow-Origin, authenticated cross-origin requests are possible from any domain. Vibe App Scanner tests CORS configuration on every API endpoint by sending requests with various Origin headers and analyzing which origins are accepted, flagging configurations that allow arbitrary cross-origin access with credentials.

How to Fix

Set Access-Control-Allow-Origin to your specific frontend domain, not *. For a Next.js app on Vercel, this is typically your production domain: Access-Control-Allow-Origin: https://your-app.com. If your API serves multiple known frontends, validate the Origin header against an allowlist and reflect only allowed origins. Never blindly reflect the Origin header from the request. For Next.js API routes, use a CORS middleware or the cors package configured with a specific origin. For Vercel serverless functions, set headers in the response object with explicit allowed origins. If your API does not need to accept cross-origin requests at all (the frontend is on the same domain), do not set any CORS headers. The browser's same-origin policy will protect the API by default.

Code Examples

CORS configuration in Next.js API route

Vulnerable
// Allows any website to call this API with credentials
export async function GET(req: Request) {
  const origin = req.headers.get('origin')
  return Response.json({ data: sensitiveData }, {
    headers: {
      'Access-Control-Allow-Origin': origin || '*',
      'Access-Control-Allow-Credentials': 'true',
    },
  })
}
Secure
const ALLOWED_ORIGINS = [
  'https://your-app.com',
  'https://staging.your-app.com',
]

export async function GET(req: Request) {
  const origin = req.headers.get('origin') || ''
  const corsOrigin = ALLOWED_ORIGINS.includes(origin)
    ? origin : ALLOWED_ORIGINS[0]
  return Response.json({ data: sensitiveData }, {
    headers: {
      'Access-Control-Allow-Origin': corsOrigin,
      'Access-Control-Allow-Credentials': 'true',
    },
  })
}

Frequently Asked Questions

Is Access-Control-Allow-Origin: * always dangerous?

Not always. For public APIs that serve non-sensitive data and do not use cookies or Authorization headers, wildcard CORS is acceptable. It becomes dangerous when combined with credentials (cookies or tokens) or when the API returns user-specific data.

Why do browsers block Access-Control-Allow-Origin: * with credentials?

The CORS specification explicitly forbids combining Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. Browsers enforce this to prevent the most obvious misconfiguration. However, developers work around it by reflecting the request Origin, which is equally dangerous.

Does Next.js have built-in CORS handling?

Next.js does not include built-in CORS middleware. API routes have no CORS restrictions by default (they respond to same-origin requests). To handle cross-origin requests, you must set CORS headers manually or use a package like cors or @vercel/edge-cors.

Is Your App Vulnerable?

VAS automatically scans for cors misconfiguration and other security issues in Vercel apps. Get actionable results with step-by-step fixes.

Scans from $5, results in minutes.