TL;DR
- AI assistants default to
Access-Control-Allow-Origin: *— a wildcard that opens your API to every domain on the internet - When combined with a naive credentials check, this escalates from sloppy to exploitable
- Audit every CORS config your AI generated and replace wildcards with explicit origin lists
I was reviewing a side project a friend built with Cursor last month. Node/Express backend, straightforward REST API, nothing fancy. The app worked fine. The CORS setup was a different story.
Every route was wide open. One line at the top of the file: app.use(cors()). No origin list, no credentials check, no thought. Just a call to the cors package with zero config. I've seen this pattern in probably a third of AI-generated Express apps I've touched this year.
The default behavior of cors() in Express sets Access-Control-Allow-Origin: *. That means any website on the internet can make cross-origin requests to the API from a visitor's browser. For a read-only public API, that's maybe acceptable. For an API that handles user data or authentication, it's a real problem.
The Vulnerable Code (CWE-942)
Here's the pattern that appears constantly in AI-generated backends:
// server.js — AI-generated Express setup
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors()); // wildcard — Access-Control-Allow-Origin: *
app.get('/api/user/profile', requireAuth, async (req, res) => {
const user = await User.findById(req.user.id);
res.json(user);
});
app.get('/api/admin/users', requireAdmin, async (req, res) => {
const users = await User.findAll();
res.json(users);
});
The damage compounds when you see this paired with a naive origin callback:
// Even worse pattern — accepts every origin including null
app.use(cors({
origin: (origin, callback) => callback(null, true),
credentials: true
}));
Now you have a path for cross-site request attacks. Any malicious page a logged-in user visits can pull their profile data, drain their session, or hit admin routes — because the server told the browser it trusts everyone.
Why AI Generates This
CORS errors are loud. No 'Access-Control-Allow-Origin' header is present is one of the most Googled browser errors. Every tutorial that fixes it reaches for cors() with no arguments or origin: '*'. The AI learned from those tutorials and treats "make the error go away" as a solved problem.
There's also a context issue. When AI generates a backend scaffold, it defaults to "it should work" over "it should be locked down." Wide CORS gets the app running immediately. A restricted config requires knowing the production domain up front, which the AI doesn't have at generation time.
The Fix
Replace the default cors() call with an explicit origin allowlist:
const allowedOrigins = [
'https://yourapp.com',
'https://www.yourapp.com',
process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null
].filter(Boolean);
app.use(cors({
origin: (origin, callback) => {
// Allow server-to-server requests (no origin header) or allowlisted origins
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`CORS blocked: ${origin}`));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
Audit your existing projects with two quick greps:
grep -r "cors()" ./src --include="*.js" | grep -v allowedOrigins
grep -r "origin: '*'" ./src --include="*.js"
Any match is worth reviewing manually before shipping.
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)