TL;DR
- AI editors default to
cors()with zero config in almost every Express/Fastify scaffold - Wildcard CORS disables the browser's same-origin protection, exposing your API to any website
- Fix: define an explicit origin allowlist and validate the request Origin header before reflecting it
Last month I reviewed three side projects from friends who had all used Cursor to scaffold their backends. Two of them had wildcard CORS enabled in production. Not just in dev. In production, serving real user data.
The code looked perfectly reasonable. Cursor had generated a clean Express setup. Routes were organized, error handling was solid. But at the top of every app.js was the same line:
// CWE-942: Permissive Cross-domain Policy
app.use(cors()); // wildcard - every origin allowed
That one line, with zero configuration, tells the browser: any website in the world can make authenticated requests to this API on behalf of your logged-in users.
What wildcard CORS actually means
Browsers enforce same-origin policy by default. A page on evil.com cannot read the response from a fetch to your-api.com unless your API explicitly permits that origin.
When you call cors() with no arguments, Express adds this header to every response:
Access-Control-Allow-Origin: *
The asterisk means: accept requests from any origin. The browser stops blocking. Now any tab the user has open can hit your API, read the response, and exfiltrate data - assuming the user is authenticated.
This is most dangerous when combined with session cookies or JWTs stored in localStorage. A malicious page loads silently, fires requests as the authenticated user, reads the results. Nobody gets an alert.
Why AI editors keep generating this
The pattern is everywhere in tutorials. The most-upvoted Express CORS answer on Stack Overflow shows app.use(cors()) with a comment that says it "allows all origins." GitHub is full of MERN boilerplate with the same line.
Models trained on this data associate "set up CORS in Express" with cors() with no arguments. It makes the request work. The developer moves on. Nobody fails a demo because of permissive CORS. The bug is silent until it gets exploited.
The fix: an explicit allowlist
// CWE-942 mitigated
const ALLOWED_ORIGINS = [
'https://yourapp.com',
'https://staging.yourapp.com'
];
app.use(cors({
origin: function (origin, callback) {
// allow server-to-server requests (no Origin header)
if (!origin) return callback(null, true);
if (ALLOWED_ORIGINS.includes(origin)) return callback(null, true);
callback(new Error('Not allowed by CORS policy'));
},
credentials: true
}));
A few specifics worth noting:
The !origin check lets server-to-server requests through (curl, Postman, other backend services) without breaking your integration tests. The credentials: true flag is required if your frontend sends cookies or Authorization headers. Browsers drop credentials silently without it.
One combination to never use: credentials: true with origin: '*'. Browsers refuse to send credentials to a wildcard origin. If you have that in your config, your auth is silently broken and your CORS is still wide open. Two bugs for the price of one.
What to check right now
Open your app.js or server.ts and search for cors(. Empty parens anywhere means you have this problem.
Also check:
- The actual
Access-Control-Allow-Originheader in your responses (Network tab in devtools) - Any route behind session middleware or cookie auth - those are the ones that matter
- Your API gateway or CDN CORS config - it can override app-level settings silently
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.




