TL;DR
- Ask Cursor to "fix the CORS error" and you usually get a wildcard or a reflected origin
- Reflected origin plus credentials means any website can act as your logged-in users
- An explicit allowlist is five lines. There is no good excuse to skip it.
Last week I reviewed an Express API a friend built almost entirely with Cursor. Clean structure, decent tests, tidy commit messages. Then I opened the middleware file and found the one line that undid all of it.
The story is always the same. You wire the frontend to the backend, the browser console throws the famous red error: "blocked by CORS policy". You paste it into the AI chat. The error disappears in ten seconds. What you shipped instead is CWE-942, a permissive cross-domain policy.
The code Cursor writes
The first attempt usually looks harmless:
app.use(cors({ origin: '*' }));
Then cookies stop working, because browsers refuse to send credentials to a wildcard origin. So you tell the AI "auth requests are failing now" and it produces the real problem:
app.use(cors({
origin: (origin, callback) => callback(null, true),
credentials: true
}));
That callback reflects every origin back to the browser. Combined with credentials: true, any website on the internet can make authenticated requests to your API from a visitor's browser and read the responses. Your users' session cookies do the authentication for the attacker. A malicious site just needs one of your users to have a live session.
I have also seen the manual version:
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
Same hole, no library needed.
Why this keeps happening
Three reasons, and none of them are the AI being stupid.
Training data is the first. Thousands of StackOverflow answers to CORS questions boil down to "allow everything, it works". Those answers were written for localhost tutorials where the wildcard genuinely does not matter. The model learned the pattern without the context.
The second reason is feedback asymmetry. A CORS error is loud. It is red, it is in your console, it blocks your demo. The vulnerability it hides is silent. Nothing in your test suite fails when your API trusts every origin. The AI is optimizing for the visible signal, and so are you.
Third: CORS is confusing enough that most developers cannot review the fix. If you do not know that credentials plus wildcard is blocked by browsers but credentials plus reflected origin is not, the dangerous version looks like the clever version.
The fix
Explicit allowlist. Five lines that matter:
const ALLOWED_ORIGINS = [
'https://app.example.com',
'https://staging.example.com'
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || ALLOWED_ORIGINS.includes(origin)) {
return callback(null, origin);
}
callback(new Error('Not allowed by CORS'));
},
credentials: true
}));
Rules I follow now on every vibe-coded backend. Never reflect req.headers.origin without checking it against a list. Treat credentials: true as the dangerous flag that triggers a manual review. Keep the allowlist in config, not scattered across route files. And grep for Access-Control-Allow-Origin before every deploy. It takes four seconds.
I have been running SafeWeave for this. It hooks into Cursor and Claude Code as an MCP server and flags permissive CORS and reflected origins before I move on to the next prompt. That said, even a basic pre-commit hook with semgrep will catch most of what is in this post. The important thing is catching it early, whatever tool you use.
Top comments (0)