---
title: "CORS wide open"
slug: cors-wide-open
severity: high
category: Configuration
summary: "Reflecting any website's Origin and allowing credentials means that website can call your API as the logged-in user."
platforms:
  - Next.js
  - Supabase
  - Firebase
  - Vercel
updated: "2026-10-13"
related:
  - auth-checked-only-in-the-ui
  - missing-security-headers
---

CORS is the browser's rule for whether JavaScript on site A may read a response from site B. It is not a firewall. A non-browser client ignores it. Your server still needs authentication.

The dangerous configuration is specific: your API sets `Access-Control-Allow-Origin` to whatever `Origin` header just arrived, and also sets `Access-Control-Allow-Credentials: true`. Then `https://evil.example` can run `fetch("https://your-app.example/api/notes", { credentials: "include" })` and read the JSON using the victim's cookies, if the victim is logged in and visits the attacker's page.

`Access-Control-Allow-Origin: *` cannot be combined with credentials. Browsers reject that pair. The bug people actually ship is the reflected origin, often copied from a snippet that "fixes CORS" by echoing the request.

Supabase's Data API is called from browsers on purpose. The publishable key is public, and row level security is the control. Locking down Supabase's CORS is not how you protect tables. Protect your own cookie-authenticated routes.

## How to check in 5 minutes

1. Search for `Access-Control-Allow-Origin` and `cors`. Note any `*` and any code that copies `request.headers.get("origin")` into the response.
2. If you also see `Allow-Credentials: true` or `credentials: true` in the CORS options, the allowlist must be your real origins only.
3. With a throwaway page on another origin, or with curl, send `Origin: https://example.com` to your API. If the response reflects `https://example.com` and allows credentials, the check failed.
4. Confirm production and preview URLs you actually use are the ones on the list, including the Vercel preview host only if previews should be allowed to call production. Usually they should not.

## How to fix

### Next.js

```ts
const allowed = new Set([
  "https://your-app.example",
]);

export function cors(request: Request): Headers {
  const headers = new Headers();
  const origin = request.headers.get("origin");
  if (origin && allowed.has(origin)) {
    headers.set("Access-Control-Allow-Origin", origin);
    headers.set("Vary", "Origin");
    headers.set("Access-Control-Allow-Credentials", "true");
  }
  return headers;
}
```

If the route does not use cookies, you often do not need CORS credentials at all. Same-origin fetches from your own Next.js pages do not need a CORS middleware. Delete the "allow all" helper if nothing cross-origin is supposed to call you.

### Supabase

Keep using RLS. Do not put the secret key in the browser to "avoid CORS." If you added custom headers on a Next.js proxy in front of Supabase, apply the allowlist to that proxy, not a wildcard.

### Firebase

The Firebase client SDK is meant to run in the browser. Rules decide access. A wildcard on your own Cloud Function or Route Handler is a separate choice. Allowlist origins there the same way.

### Vercel

You can set headers in `vercel.json` or `next.config.ts`. A static `*` header on `/api/:path*` is the bug if those routes use cookies. Prefer the allowlist in code so `Vary: Origin` is correct.

## Agent prompt

```text
You are closing an open CORS policy. Do not add features.

Find every Access-Control-Allow-Origin header and CORS middleware.

1. If a route uses cookies or Authorization from the browser, allow only origins I list. Do not reflect the request Origin by default.
2. Do not send Allow-Credentials together with *.
3. Add Vary: Origin when the allow-origin value depends on the request.
4. Remove CORS headers from routes that are only called same-origin, instead of opening them.
5. Do not change Supabase RLS. CORS is not the database control.

If you do not know the production origin, use a clearly named allowlist constant and tell me to fill it in. Do not default it to *.

Show the before and after.
```

## FAQ

### Does CORS protect my API from curl?

No. CORS is enforced by browsers. Authentication and RLS protect the data. CORS stops other websites from riding a logged-in browser session.

### Our mobile app has no origin. Did we break it?

Native apps are not subject to browser CORS. An allowlist for websites does not block a mobile client that sends no `Origin`, unless your server rejects missing origins on purpose. Do not open the allowlist to `*` to make the mobile app work.

## Sources

- [MDN: CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)
- [Supabase API keys](https://supabase.com/docs/guides/api/api-keys)
