DEV Community

Cover image for 3 CORS Misconfigs Cursor Keeps Writing Into Vibe-Coded APIs
Charles Kern
Charles Kern

Posted on

3 CORS Misconfigs Cursor Keeps Writing Into Vibe-Coded APIs

TL;DR

  • Cursor and Copilot default to origin: '*' when generating Express CORS config, letting any website call your API from a logged-in user's browser
  • Pairing origin: '*' with credentials: true gets blocked by browsers, so developers remove credentials to make it work — breaking auth while leaving the wildcard intact
  • Fix: pass an origin allowlist function, not a string, and keep credentials: true alongside it

I was reviewing a side project last month. React frontend, Node.js API, the whole thing built in a weekend with Cursor. The code was clean. The architecture was sensible. Then I checked the CORS config.

app.use(cors()); // fix before prod
Enter fullscreen mode Exit fullscreen mode

The comment was still there. The app had been live for three weeks.

This is not a freak occurrence. I've seen the same pattern across probably twenty AI-assisted backends in the past year. There's a structural reason it keeps happening.

The vulnerable config (CWE-942)

The bare cors() call defaults origin to '*'. That's the most common form. The explicit version shows up just as often:

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

This one causes extra damage. Browsers block origin: '*' combined with credentials: true as a security measure. The CORS request fails. The developer searches "cors credentials not working express", finds a Stack Overflow answer, removes credentials: true, and the API "works" again. Now cookie-based auth is broken and the wildcard is still open. Two bugs for the price of one.

The third form I see regularly is hand-rolled middleware lifted from a 2017 tutorial:

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', '*');
  next();
});
Enter fullscreen mode Exit fullscreen mode

Different syntax, identical risk.

Why AI editors keep producing this

The training data is the issue. Express tutorials from 2014 to 2022 — the era when most of the internet's how-to content got written and indexed — almost universally used cors() with no arguments, or explicitly origin: '*', followed by a note about locking it down before production. The model learned to generate code that makes the demo work. The demo doesn't have production origins.

When you type "set up CORS for my Express API" into Cursor or Copilot, there's no origin context available. The model completes toward the pattern it saw most often. That pattern is the wildcard.

The fix

Origin allowlisting done properly:

const allowedOrigins = [
  'https://yourdomain.com',
  'https://app.yourdomain.com',
  process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null
].filter(Boolean);

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

The !origin check matters. Server-to-server requests and tools like Postman don't send an Origin header. Without it, your own backend-to-backend calls fail the CORS check. The filter(Boolean) strips the null in production so localhost never ends up in the allowlist by accident. credentials: true now works correctly because you're passing a function rather than a wildcard string.

While you're there, audit methods and allowedHeaders. Cursor often generates methods: '*' or allowedHeaders: '*' in the same breath as the origin wildcard. Lock those down too:

app.use(cors({
  origin: allowedOrigins,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));
Enter fullscreen mode Exit fullscreen mode

No surprises in production. No OPTIONS preflight letting through anything the server can handle.

I've been running SafeWeave for this. It hooks into Cursor and Claude Code as an MCP server and flags these CORS patterns before I move on. That said, even a basic pre-commit hook with semgrep and a custom CORS rule will catch the bare cors() call. The important thing is catching it early, whatever tool you use.

Top comments (0)