Skip to content
Back to field notes
BrowserJune 6, 20266 min read

CORS misconfiguration in AI-built APIs: what actually breaks before launch

An AI-generated API that answers with a wildcard or reflected CORS header lets any website read your authenticated responses; this note shows how to check your own routes before you share the URL.

CORSAPIBrowser

A wildcard or reflected CORS header opens your API to any origin

This is the concrete risk: an AI-built app ships an API that answers cross-origin requests with Access-Control-Allow-Origin set to a wildcard, or to whatever Origin the request carried, echoed straight back. Both pass the browser's CORS check. Both mean any website your user visits can make requests to your API in their browser and read the responses.

The part that is directly observable is the response header. Open the Network tab and you can see Access-Control-Allow-Origin: * coming back, or you can see the value match exactly whatever Origin you sent. That is reproducible on your own app in a minute.

The part that is only possible, not proven, is impact. A wildcard on a public, unauthenticated, read-only endpoint that serves data you would publish anyway is usually harmless. The damage case is narrower and specific: an endpoint that returns per-user or account data, combined with a CORS policy that both allows the calling origin and allows credentials. So separate the two questions. What does the header say, and what does the endpoint return. The header is the signal; the endpoint behind it is the severity.

  • Access-Control-Allow-Origin: * means any origin may read the response, but browsers will not send cookies on a wildcard, so a pure cookie-session API is partly shielded.
  • Reflecting the Origin header back, combined with Access-Control-Allow-Credentials: true, is the dangerous pattern: it tells the browser to both accept the foreign origin and send the user's cookies.
  • A token sent in a custom header or Authorization header is governed by the same allow-origin decision, so a reflected origin exposes token-authenticated endpoints too.

Why AI-built apps land on the permissive setting

CORS is the kind of thing that produces a confusing browser error during development and a clean console once it is silenced. That shape is exactly what fast, prompt-driven building optimizes away. The error says the request was blocked by CORS policy. The quickest prompt-sized fix that makes the error disappear is to allow everything.

So the generated handler tends toward one of two defaults. It sets Access-Control-Allow-Origin to * because that is the broadest answer that stops the error. Or it reads the incoming Origin header and writes it straight back into the allow-origin response, which also stops the error and feels more precise because it is not a literal star. The second one is more dangerous, because reflecting the origin is functionally equivalent to allowing all origins while still being eligible to allow credentials.

  • The wildcard is the simplest thing that makes the dev error go away, so it survives into production.
  • Origin reflection looks like an allowlist but is not one; it allows whatever asks, including a malicious site.
  • The app works in the browser either way, so nothing in normal testing surfaces the problem. CORS does not break your own first-party calls when it is too open. It only breaks them when it is too strict.
  • Dev and prod often differ here. A localhost-friendly config that allowed everything in development is the thing that ships, because tightening it was never forced by a failing test.

Check your own API's CORS response

You can confirm all of this on your own app without attacking anything. Two reliable paths, both reproducible.

From DevTools: open the Network tab, exercise a real API call in your app, and read the response headers on the request. Look at Access-Control-Allow-Origin and Access-Control-Allow-Credentials. For requests that trigger a CORS preflight in production (anything beyond a simple GET, or anything with a custom header), you will also see an OPTIONS request first; check the headers on that OPTIONS response too, because the preflight is where the browser decides whether the real request is allowed.

From the command line, against your own endpoint only: send a request with a made-up foreign Origin header, for example a value like https://not-your-app.example, and read what comes back in Access-Control-Allow-Origin.

  • Safe result: the header is absent on endpoints that only your own frontend calls, or it names your exact production origin and nothing else. An endpoint that returns no allow-origin header to a foreign origin is refusing cross-origin reads, which is the correct default for a private API.
  • Suspicious result: the response comes back with Access-Control-Allow-Origin: *, or it echoes your fake https://not-your-app.example value verbatim. Either one means the browser would let an arbitrary site read this response.
  • The combination to treat as a launch blocker: a reflected or wildcard origin together with Access-Control-Allow-Credentials: true on any route that returns user or account data. Note that browsers reject the literal pairing of a wildcard origin with credentials, which is exactly why the reflection pattern is the one that actually achieves credentialed cross-origin access.
  • Repeat per route. CORS is set per response, and an AI-built app often configures it inconsistently across handlers, so a clean check on one route says nothing about the next.

Set an explicit allowlist, and know what it does not cover

The fix is to stop answering every origin and start answering a known list.

Replace the wildcard and the reflection with an explicit allowlist of the origins you actually serve, usually just your production frontend origin. When a request arrives from an origin on the list, return that exact origin in Access-Control-Allow-Origin; when it is not on the list, return no allow-origin header at all rather than reflecting it. Only set Access-Control-Allow-Credentials: true on routes that genuinely need to send cookies cross-origin, and never pair it with a wildcard. If your frontend and API live on different domains, your session cookies also need SameSite and Secure set correctly, which is a separate cookie concern covered in the related notes on cookie flags and SameSite=None cross-origin cookies.

What this does not cover is the more important point. CORS is a browser-enforced control on what other websites can read in a user's browser. It is not server-side access control. It does nothing against a direct request from a script, a server, or a tool like curl, because those are not browsers and do not honor CORS at all.

  • Tightening CORS to an allowlist stops a malicious website from reading your responses in a victim's browser. That is its entire job.
  • It does not authenticate or authorize anyone. A route that returns sensitive data to any caller who reaches it is still exposed to non-browser clients no matter how strict the CORS header is.
  • Every endpoint that returns private data still needs real authentication on the request and an authorization check on what that caller is allowed to see. CORS sits on top of that; it does not replace it.

A correct CORS allowlist closes the cross-origin browser read path. It does not make an endpoint private. If an API route returns account data to an unauthenticated request, that is a server-side authorization gap, and no CORS header will fix it. Check the route's auth separately from its CORS header.

Where VibeCodeGuard fits, and what it does not prove

VibeCodeGuard's Launch Check inspects the CORS headers on your public API routes from the outside. It flags the patterns that are visible from the response: a wildcard Access-Control-Allow-Origin, and origin-reflection where the header simply echoes whatever origin was sent. Those are the two failure modes that this note is about, and they are exactly what an outside scan of the public surface can see. The scan reports the finding with severity and fix direction.

What it cannot do is verify that your allowlist is correct. Deciding which origins should be allowed, and confirming that your handler returns the right origin for the right request and refuses the rest, is logic that lives in your code. A scan sees the header that comes back for the origins it tries; it does not read your allowlist or prove that every legitimate origin works and every other one is denied. That correctness check needs manual verification.

Next step: open your Network tab, send one request with a foreign Origin to a route that returns user data, and confirm the response does not echo it back. Then run a focused Launch Check before sharing the URL publicly, so a wildcard or reflected origin shows up in your scan and not in someone else's.

A clean CORS result on the public surface is one signal, not a guarantee. The scan checks the header pattern, not your authorization logic and not the contents of your allowlist. Treat a flagged wildcard or reflected origin as a launch blocker, and treat a clean result as a reason to move to the next check, not a sign the route is private.

> launch check

Scan the public surface before launch.

Get severity, evidence, and practical fix guidance for the checks VibeCodeGuard can run from the outside.