DEV Community

Charles Kern
Charles Kern

Posted on

Why Cursor Keeps Setting CORS to * (And How to Fix It)

TL;DR

  • Wildcard CORS (Access-Control-Allow-Origin: *) shows up in the majority of AI-generated Express backends
  • Cursor defaults to it because most training-data CORS examples skip origin whitelisting entirely
  • Fix: replace origin: '*' with a runtime allowlist function -- five minutes, zero extra dependencies

I was reviewing a side project for a friend last week. Express backend, generated almost entirely with Cursor. Clean TypeScript, solid test coverage, auth working correctly. One thing stood out immediately.

Every route was open to every domain on the internet.

Not because auth was missing. The JWT middleware was fine. But at the top of server.ts:

app.use(cors({ origin: '*' }));
Enter fullscreen mode Exit fullscreen mode

This line shows up in probably eight out of ten AI-generated Express backends I've looked at. It's not a Cursor failure specifically. It's a training data problem. The StackOverflow answers that taught CORS to a generation of Node developers all used origin: '*' in their examples. They were written to solve "make the browser request work" -- not "make it work securely." LLMs absorbed that pattern.

The Vulnerable Code (CWE-346)

Here's what you get when you ask Cursor to "add CORS to my Express API":

import cors from 'cors';

// CWE-346: Origin Validation Error
app.use(cors({
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));
Enter fullscreen mode Exit fullscreen mode

That last line is where it gets interesting. origin: '*' combined with credentials: true is rejected by most browsers -- so the developer hits a CORS error and asks their AI editor to fix it. The fix often involves setting origin to a specific string... which the AI sometimes still writes as '*', or suggests allowedHeaders: '*' as a workaround.

Even without credentials, wildcard CORS on an authenticated API means any website can make requests to your API from inside a victim's browser and read the responses. If the user is logged in, their session token flows with the request. It's not a full auth bypass, but it's a real attack surface that costs nothing to close.

Why This Pattern Persists

Three things keep it alive.

CORS errors are painful. Developers want them gone fast. "Add CORS to my Express server" is one of the most common backend setup prompts. Wildcard CORS works -- the request succeeds, no error, no warning. The developer moves on. And the attack isn't visible. No stack trace, no monitoring alert, no test failure. It just sits there until someone looks for it.

The Fix

Replace the static string with a validator function:

const ALLOWED_ORIGINS = [
  'https://yourapp.com',
  'https://www.yourapp.com',
  process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean) as string[];

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin) return callback(null, true);
      if (ALLOWED_ORIGINS.includes(origin)) return callback(null, true);
      callback(new Error(`CORS blocked: ${origin}`));
    },
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
  })
);
Enter fullscreen mode Exit fullscreen mode

Two things worth noting: the !origin case passes through intentionally -- server-to-server requests and CLI tools don't send an Origin header, and that's fine. Only browser cross-origin requests need the check. And credentials: true is safe here because you've replaced '*' with an actual validator.

Add one integration test:

it('blocks requests from unauthorized origins', async () => {
  const res = await request(app)
    .get('/api/user/me')
    .set('Origin', 'https://malicious.example.com');
  expect(res.headers['access-control-allow-origin']).toBeUndefined();
});
Enter fullscreen mode Exit fullscreen mode

Put that in CI. It'll catch the next time someone asks an AI editor to "simplify the CORS config."

The same applies outside Express. Next.js (cors in next.config.js), FastAPI (CORSMiddleware with allow_origins), Django (CORS_ALLOWED_ORIGINS), Rails (rack-cors with origins). Every framework has this setting. Every AI editor defaults to permissive.

I've been running SafeWeave for this. It hooks into Cursor and Claude Code as an MCP server and flags these patterns before I move on. That said, even a basic pre-commit hook with semgrep and gitleaks will catch most of what's in this post. The important thing is catching it early, whatever tool you use.

Top comments (0)