# Cursor's CORS Fix Is a Critical Vulnerability

> Source: <https://sourcefeed.dev/a/cursors-cors-fix-is-a-critical-vulnerability>
> Published: 2026-07-26 00:08:46+00:00

[Security](https://sourcefeed.dev/c/security)Article

# Cursor's CORS Fix Is a Critical Vulnerability

AI assistants keep reflecting the Origin header with credentials on — the one pattern that turns an authenticated API into an open door.

[Ji-ho Choi](https://sourcefeed.dev/u/jiho_choi)

Ask Cursor to fix a CORS error and there's a good chance it hands you a critical vulnerability with a green checkmark. Not a wildcard `*`

— that one's mostly harmless because browsers refuse to pair it with credentials. The dangerous pattern is subtler: middleware that reads the incoming `Origin`

header and echoes it straight back into `Access-Control-Allow-Origin`

, alongside `Access-Control-Allow-Credentials: true`

. It works flawlessly in local dev. It ships. And it turns your authenticated API into something any website on the internet can read on a logged-in user's behalf.

## Why the model reaches for reflection

CORS errors are among the most common friction points in web development, and they share a frustrating property: the browser tells you *what* it wants but the fix is never one obvious line. You're running the frontend on `localhost:3000`

, the API on `localhost:8000`

, a preview build on some Vercel URL, maybe Postman or a mobile simulator hitting the same backend. Every one of those is a distinct origin. An allowlist has to enumerate all of them and stay current. Reflection doesn't — it says yes to whoever asks.

```
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');
```

From a language model's perspective this is the global maximum. It resolves the error for every dev port, every tool, every teammate, on the first try, with no follow-up questions. The training corpus is saturated with Stack Overflow answers that do exactly this because they, too, were optimizing for "make the red text go away." As one writeup on the pattern put it, the fastest way to make a CORS error disappear is to stop checking who's asking. The model learned that lesson well.

The tell is that `req.headers.origin`

is attacker-controlled. A malicious page sets `Origin: https://your-api.example`

— no, it sets whatever it likes, `Origin: https://evil.test`

, and the server dutifully reflects `evil.test`

back. The browser sees the response origin match the request origin, sees credentials allowed, and releases the victim's session cookie along with the response body. Same-origin policy, the thing that's supposed to stop cross-site reads, has been explicitly waived by your own server. This is [CWE-942](https://cwe.mitre.org/data/definitions/942.html), permissive cross-domain policy, and it reads as critical on any scanner because it is one.

## This isn't a Cursor bug

It's tempting to frame this as a [Cursor](https://cursor.com) problem, but that's too narrow. The behavior shows up across [Bolt](https://bolt.new), [Lovable](https://lovable.dev), and plain [Copilot](https://github.com/features/copilot) completions, because they draw on the same corpus and optimize for the same signal: does the code run. [Veracode's 2025 GenAI Code Security Report](https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/), which ran 80 curated tasks across more than 100 models, found that when a task offered both a secure and an insecure way to write something, the models picked the insecure path 45% of the time. CORS is a textbook instance of that split: the secure version costs an extra function and a config array, the insecure version is shorter and passes the same functional test.

That framing matters because it tells you where the fix has to live. You cannot prompt your way to safety reliably — "write secure CORS" nudges the odds but doesn't close the gap, because the model's notion of "works" and your notion of "safe" diverge precisely here. The check has to be external to the generation step.

## What the correct version actually costs

The secure pattern is not exotic. It's an allowlist and a callback:

``` js
const allowedOrigins = new Set([
  'https://app.example.com',
  'https://staging.example.com',
]);

app.use(cors({
  origin(origin, cb) {
    // no Origin header = same-origin or a non-browser client (curl, server-to-server)
    if (!origin || allowedOrigins.has(origin)) return cb(null, true);
    return cb(new Error('Not allowed by CORS'));
  },
  credentials: true,
}));
```

The [ cors middleware](https://www.npmjs.com/package/cors) does the right thing here: it only emits

`Access-Control-Allow-Origin`

when the origin is on the list, and it never falls back to a wildcard. Flask's `flask-cors`

and most framework equivalents take the same explicit-list shape. The cost is real but small — you maintain a list, and you plumb your preview and staging URLs into it, usually from an environment variable so the list differs per deployment.One caveat worth stating plainly, because a lot of the "just use an allowlist" advice glosses it: locking down CORS is not CSRF protection. CORS governs whether a cross-origin script can *read* your response. It does nothing to stop a cross-site request from being *sent* and having side effects. If your API mutates state on cookie auth, you still want `SameSite=Lax`

or `Strict`

cookies and, for anything sensitive, CSRF tokens. Reflection-plus-credentials is worse than a missing allowlist because it defeats both layers at once, but fixing the read side doesn't absolve you of the write side.

## The workflow change this demands

If you're shipping AI-generated backends, treat CORS config as a review chokepoint, not a line you skim. Concretely:

- Grep your codebase for
`Access-Control-Allow-Origin`

set from a request header, and for`origin: true`

in the`cors`

middleware — that's the reflect-everything switch in disguise. - Add a CI check. It's a one-line static rule: flag any response that sets ACAO from
`req.headers.origin`

or pairs a reflected/wildcard origin with`credentials: true`

. This catches the regression the model reintroduces every time someone asks it to fix a new preview URL. - Test it adversarially, not functionally. Fire a request with
`Origin: https://not-your-app.test`

and assert the response does*not*echo it back. Your normal test suite won't catch this because your tests come from allowed origins.

The broader lesson is that AI assistants have quietly shifted where security bugs enter. They don't write novel, exotic flaws — they industrialize the well-known ones, reproducing the most common insecure Stack Overflow answer at the speed of autocomplete and with the confidence of a passing local test. CORS reflection is the current poster child because it's invisible until a scanner or a pentester flags it, and by then it's in production behind a login. The defense isn't better prompts. It's assuming the generated config is wrong about trust boundaries until a check you control proves otherwise.

## Sources & further reading

-
[Why Cursor Keeps Generating Wildcard CORS Headers in Your API](https://dev.to/c_k_fb750e731394/why-cursor-keeps-generating-wildcard-cors-headers-in-your-api-33ii)— dev.to -
[2025 GenAI Code Security Report](https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/)— veracode.com -
[CORS Misconfiguration in Vibe-Coded Apps: Wildcard Origins Explained](https://vibedoctor.io/blog/sec-004-cors-misconfiguration-vibe-coded-apps)— vibedoctor.io -
[CWE-942: Permissive Cross-domain Policy with Untrusted Domain](https://cwe.mitre.org/data/definitions/942.html)— cwe.mitre.org

[Ji-ho Choi](https://sourcefeed.dev/u/jiho_choi)· Security & Cloud Editor

Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.

## Discussion 0

No comments yet

Be the first to weigh in.
