TL;DR
- Cursor, Claude Code, and Copilot consistently generate
Access-Control-Allow-Origin: *in new Express and FastAPI projects - Wildcard CORS combined with cookie auth opens your API to cross-origin attacks from any website on the internet
- Fix: swap the wildcard for an explicit origin allowlist -- takes about two minutes
I was reviewing a friend's side project last week. Node/Express backend, Cursor-generated, looked clean. Then I spotted it buried in the middleware setup:
app.use(cors());
No options. Default wildcard. That one line means any website can make requests to his API from a visitor's browser. He was planning to add user accounts the following week.
This isn't a one-off. I've looked through dozens of vibe-coded repos. The pattern shows up constantly. The AI isn't wrong by its training data standards -- cors() with no arguments is the first result in every Express tutorial from 2019. It learned the pattern without learning the context it was written for.
The Vulnerable Pattern (CWE-942)
When you ask Cursor to "add CORS support to the Express app," this is what comes back:
const cors = require('cors');
app.use(cors()); // Access-Control-Allow-Origin: *
FastAPI gets the same treatment:
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
That allow_credentials=True with allow_origins=["*"] is the messy combination. The CORS spec actually forbids reflecting * when credentials are involved -- browsers block it. But some CORS libraries respond by reflecting the requesting Origin header instead, which bypasses that protection entirely. When developers later add session cookies without revisiting the CORS config, the wildcard was "fine" during development until it isn't.
Why This Pattern Sticks
The training corpus is saturated with cors() and no options. Stack Overflow answers, official quick-start docs, every "build a REST API in 10 minutes" post. All written for localhost, where wildcard is harmless.
The AI learned the pattern without the annotation that says "this is fine for dev, not for prod." When you prompt it to add CORS support, it matches that to the most common example it knows. It doesn't know your frontend runs on a specific domain. It doesn't know you're about to wire up auth.
The fix is simple. The problem is nobody asks for it in the prompt.
The Fix
For Express, swap the wildcard for an explicit list:
const allowedOrigins = [
'https://yourapp.com',
'https://staging.yourapp.com',
process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null
].filter(Boolean);
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true
}));
For FastAPI:
origins = [
"https://yourapp.com",
"https://staging.yourapp.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
Pull the origins from an environment variable. One ALLOWED_ORIGINS env var, comma-separated, parsed at startup. Staging and production stay separate without touching code.
Two more things to check while you're in there: preflight handling and Access-Control-Allow-Methods. AI-generated code often sets that to * in custom middleware too. Explicit method lists take 30 seconds.
If you're running a genuinely public API with no auth -- open data endpoints, public webhooks -- wildcard is fine and correct. The problem is specifically when wildcard meets user-specific data and auth.
I've been running SafeWeave for this. It hooks into Cursor and Claude Code as an MCP server and flags CORS misconfigurations before I move on. That said, even a basic pre-commit hook with semgrep and a CORS rule will catch most of what's in this post. The important thing is catching it early, whatever tool you use.




