TL;DR
- AI editors almost always default to
cors()with no config -- which setsAccess-Control-Allow-Origin: * - Wildcard CORS on authenticated APIs exposes your users to cross-site request attacks
- Fix: replace the wildcard with an explicit origin allowlist controlled by an env var
I was reviewing a side project a dev built entirely in Cursor. The Express backend looked clean -- structured routes, solid error handling, decent auth middleware. Then I checked the CORS setup.
app.use(cors()); // defaults to Access-Control-Allow-Origin: *
One line. Auto-suggested by Cursor from a starter template. Left in production because it made the frontend stop complaining in dev. Except this was prod.
Wildcard CORS feels harmless compared to SQL injection. No immediate data breach. But for any API using cookies or session tokens, a wildcard CORS config means any website -- a phishing page, a malicious ad iframe -- can make authenticated requests on behalf of your logged-in users without them knowing.
The vulnerable pattern (CWE-942)
Here's what Cursor and Claude Code almost always suggest when you ask for a working Express backend:
// CWE-942: Permissive Cross-domain Policy
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors()); // Allows all origins -- no questions asked
app.use(express.json());
app.get('/api/user/profile', authenticate, (req, res) => {
res.json(req.user);
});
The cors() call with no arguments sets Access-Control-Allow-Origin: * on every response. If your frontend then adds credentials: true to fetch calls, modern browsers will block the request and throw a CORS error. At that point developers often "fix" it like this:
// Even worse
app.use(cors({ origin: '*', credentials: true }));
Browsers block this combination too (you cannot use wildcard origin with credentials). But the intent matters: developers are being guided step by step toward maximally permissive configs, just to silence console errors.
Why AI editors keep generating this
Training data skews toward "this works" not "this is safe." Hundreds of tutorials, Stack Overflow answers, and README files show app.use(cors()) as the one-liner to get your frontend talking to your backend. That's what the model learned. When Cursor generates a backend starter, it's optimizing for immediate functionality -- wildcard CORS delivers that, so it gets generated.
The security implications don't surface until much later, if at all. Most projects never get a security review.
The actual fix
Replace the wildcard with an explicit origin allowlist. Keep the list in environment variables so staging and production configs stay separate:
// Explicit origin allowlist
const allowedOrigins = process.env.ALLOWED_ORIGINS
? process.env.ALLOWED_ORIGINS.split(',')
: [];
app.use(cors({
origin: (origin, callback) => {
// Allow server-to-server requests (no Origin header)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`CORS blocked: ${origin}`));
}
},
credentials: true // safe -- origin is explicitly verified before this applies
}));
Your .env:
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com
If you're not using the cors library:
// Manual headers -- explicit and auditable
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
The critical difference: you're echoing back the verified origin, not broadcasting a wildcard. Every CORS request gets an explicit answer.
One more thing -- add this to your semgrep config to catch wildcard CORS before it reaches code review:
rules:
- id: cors-wildcard
patterns:
- pattern: cors({ ..., origin: '*', ... })
- pattern: cors()
message: Wildcard CORS detected -- replace with explicit allowlist
severity: WARNING
languages: [javascript, typescript]
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 will catch most wildcard CORS configs in seconds. The important thing is catching it early, whatever tool you use.
Top comments (0)