← All news
security6 min read

Your AI-generated API defaults to Access-Control-Allow-Origin: *

By Seaworthy · September 13, 2026

A red padlock resting on a black computer keyboard

Photo by FlyD on Unsplash

Ask any assistant to fix a CORS error and watch what happens. The browser console says "blocked by CORS policy," you paste it in, and thirty seconds later there's a new line in your Express or Fastify config:

app.use(cors({ origin: '*' }))

The error goes away. You move on. This is how almost every side project, hackathon build, and "let me just ship this" internal tool ends up with a wildcard origin on every route, including the ones that read a session cookie or check a bearer token.

Here's the problem: Access-Control-Allow-Origin: * tells the browser that any website on the internet is allowed to read the response to a cross-origin request. If that endpoint only returns public data, fine. If it returns anything scoped to a logged-in user, you've just built a way for evil.example.com to fetch your users' data from their own browser session and read the result.

Why the wildcard "just works" until it doesn't

Browsers actually stop you from combining the wildcard with credentials. If a request is sent with credentials: 'include' (cookies, HTTP auth) or the server responds with Access-Control-Allow-Credentials: true, the spec forbids pairing that with * for the origin. The browser will reject the response.

So a wildcard-with-cookies setup usually breaks visibly in development, which means someone "fixes" it again. The typical next step, again assistant-suggested, is:

app.use(cors({
  origin: (origin, callback) => callback(null, true),
  credentials: true,
}))

That's not a wildcard string, so the credentials check passes. But it echoes back whatever origin the browser sent, for every origin, with credentials enabled. This is worse than the literal *. It's the exact CORS misconfiguration security scanners flag first, because it grants every website on the internet permission to make authenticated requests to your API as your logged-in users.

I've seen this pattern in generated code more than once, because it's a genuinely reasonable-looking fix. It removes the specific error the developer was staring at. It just also removes the entire point of same-origin restrictions.

What the assistant doesn't know

An LLM completing your CORS config has no idea which routes are public and which aren't. It's not reading your auth middleware or your session logic. It's pattern-matching "CORS error" to "wildcard origin," because that's the fix that appears most often in Stack Overflow answers and tutorial repos, most of which are for demos where it doesn't matter.

Your API is not a demo. If /api/user/profile or /api/invoices sits behind the same CORS config as your public /api/status endpoint, the assistant has no way to tell you that's a problem, because it was never shown the difference.

The actual fix

CORS should be an allowlist, not a wildcard, the moment any route touches a cookie, a session, or an auth header:

const allowedOrigins = ['https://app.example.com', 'https://staging.example.com']

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  },
  credentials: true,
}))

A few things that matter here and get skipped in the rush to silence the console error:

  • Public, unauthenticated endpoints can genuinely use *. Don't set Access-Control-Allow-Credentials on them, and don't put them behind the same CORS middleware as authenticated routes if you can help it.
  • origin: true and reflecting the request origin back is not an allowlist. It looks like one because it's dynamic, but it accepts everything.
  • If you're behind a proxy or CDN that also sets CORS headers, check you don't end up with two Access-Control-Allow-Origin values on the same response. Browsers reject that outright.

The habit worth building

When an assistant proposes a CORS fix, ask it a direct follow-up: does this route read a cookie or an authorization header? If yes, the fix needs an allowlist, not a wildcard, and no amount of "the error is gone now" changes that. Treat the wildcard as a fix for public data only, and treat every other CORS error as a five-minute job to write down which origins actually need access.

Seaworthy flags wildcard CORS paired with credentialed routes during a scan, precisely because this is one of the fixes that reads as correct right up until someone tests it from the wrong origin.

This article was generated by AI and summarises publicly available sources.

From the makers of Seaworthy

Seaworthy scans your repo for the issues covered in articles like this one.

Security gaps, exposed secrets, and misconfigurations — caught before you deploy. Free to run, no account needed.

$ npx seaworthycode