{"slug": "cursor-s-cors-fix-is-a-critical-vulnerability", "title": "Cursor's CORS Fix Is a Critical Vulnerability", "summary": "Cursor, Bolt, Lovable, and GitHub Copilot AI coding assistants frequently generate CORS middleware that reflects the incoming Origin header with credentials enabled, creating a critical vulnerability (CWE-942) that allows any website to read authenticated API responses on a logged-in user's behalf. Veracode's 2025 GenAI Code Security Report found that when tasks offered both secure and insecure implementations, models chose the insecure path 45% of the time, with CORS reflection being a textbook example. Security researcher Ji-ho Choi warns that the fix must be external to the generation step, as prompting alone cannot reliably produce secure code.", "body_md": "[Security](https://sourcefeed.dev/c/security)Article\n\n# Cursor's CORS Fix Is a Critical Vulnerability\n\nAI assistants keep reflecting the Origin header with credentials on — the one pattern that turns an authenticated API into an open door.\n\n[Ji-ho Choi](https://sourcefeed.dev/u/jiho_choi)\n\nAsk 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 `*`\n\n— that one's mostly harmless because browsers refuse to pair it with credentials. The dangerous pattern is subtler: middleware that reads the incoming `Origin`\n\nheader and echoes it straight back into `Access-Control-Allow-Origin`\n\n, alongside `Access-Control-Allow-Credentials: true`\n\n. 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.\n\n## Why the model reaches for reflection\n\nCORS 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`\n\n, the API on `localhost:8000`\n\n, 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.\n\n```\nres.header('Access-Control-Allow-Origin', req.headers.origin);\nres.header('Access-Control-Allow-Credentials', 'true');\n```\n\nFrom 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.\n\nThe tell is that `req.headers.origin`\n\nis attacker-controlled. A malicious page sets `Origin: https://your-api.example`\n\n— no, it sets whatever it likes, `Origin: https://evil.test`\n\n, and the server dutifully reflects `evil.test`\n\nback. 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.\n\n## This isn't a Cursor bug\n\nIt'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.\n\nThat 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.\n\n## What the correct version actually costs\n\nThe secure pattern is not exotic. It's an allowlist and a callback:\n\n``` js\nconst allowedOrigins = new Set([\n  'https://app.example.com',\n  'https://staging.example.com',\n]);\n\napp.use(cors({\n  origin(origin, cb) {\n    // no Origin header = same-origin or a non-browser client (curl, server-to-server)\n    if (!origin || allowedOrigins.has(origin)) return cb(null, true);\n    return cb(new Error('Not allowed by CORS'));\n  },\n  credentials: true,\n}));\n```\n\nThe [ cors middleware](https://www.npmjs.com/package/cors) does the right thing here: it only emits\n\n`Access-Control-Allow-Origin`\n\nwhen the origin is on the list, and it never falls back to a wildcard. Flask's `flask-cors`\n\nand 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`\n\nor `Strict`\n\ncookies 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.\n\n## The workflow change this demands\n\nIf you're shipping AI-generated backends, treat CORS config as a review chokepoint, not a line you skim. Concretely:\n\n- Grep your codebase for\n`Access-Control-Allow-Origin`\n\nset from a request header, and for`origin: true`\n\nin the`cors`\n\nmiddleware — 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\n`req.headers.origin`\n\nor pairs a reflected/wildcard origin with`credentials: true`\n\n. 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\n`Origin: https://not-your-app.test`\n\nand assert the response does*not*echo it back. Your normal test suite won't catch this because your tests come from allowed origins.\n\nThe 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.\n\n## Sources & further reading\n\n-\n[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 -\n[2025 GenAI Code Security Report](https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/)— veracode.com -\n[CORS Misconfiguration in Vibe-Coded Apps: Wildcard Origins Explained](https://vibedoctor.io/blog/sec-004-cors-misconfiguration-vibe-coded-apps)— vibedoctor.io -\n[CWE-942: Permissive Cross-domain Policy with Untrusted Domain](https://cwe.mitre.org/data/definitions/942.html)— cwe.mitre.org\n\n[Ji-ho Choi](https://sourcefeed.dev/u/jiho_choi)· Security & Cloud Editor\n\nJi-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.\n\n## Discussion 0\n\nNo comments yet\n\nBe the first to weigh in.", "url": "https://wpnews.pro/news/cursor-s-cors-fix-is-a-critical-vulnerability", "canonical_source": "https://sourcefeed.dev/a/cursors-cors-fix-is-a-critical-vulnerability", "published_at": "2026-07-26 00:08:46+00:00", "updated_at": "2026-07-26 00:25:20.955012+00:00", "lang": "en", "topics": ["ai-safety", "ai-tools", "developer-tools"], "entities": ["Cursor", "Bolt", "Lovable", "GitHub Copilot", "Veracode", "Ji-ho Choi", "CWE-942"], "alternates": {"html": "https://wpnews.pro/news/cursor-s-cors-fix-is-a-critical-vulnerability", "markdown": "https://wpnews.pro/news/cursor-s-cors-fix-is-a-critical-vulnerability.md", "text": "https://wpnews.pro/news/cursor-s-cors-fix-is-a-critical-vulnerability.txt", "jsonld": "https://wpnews.pro/news/cursor-s-cors-fix-is-a-critical-vulnerability.jsonld"}}