TL;DR
- Every Express app I scaffold with Cursor ships with
app.use(cors())— no origin filter, open to any domain - Wildcard CORS lets any website make credentialed requests to your API from a victim's browser
- Three lines of config fixes it; AI editors don't add them because tutorials never did either
I built a side project over a few weeks, mostly vibe-coded in Cursor. Shipping fast, not overthinking the scaffolding. A friend who does security reviews stopped at one line in my Express setup and said: "this is open to the entire internet."
The line was app.use(cors()).
I'd seen it in hundreds of AI-generated tutorials. Cursor drops it in automatically when you ask for an API setup. The CORS errors stop. The app runs. And your API is now reachable from any origin on the web.
The Vulnerable Pattern (CWE-942)
When you ask Cursor or Claude Code to scaffold a Node.js REST API, you typically get this:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(express.json());
app.use(cors()); // ❌ CWE-942: any origin allowed, no restriction
No options. No origin list. cors() called bare.
That is equivalent to setting Access-Control-Allow-Origin: * on every response. The browser reads that header and allows cross-origin requests from literally any domain. For a truly public read-only API this might be fine. For anything behind authentication, it is a real problem.
Here is the attack path: a malicious site loads in a victim's browser, fires a request to your API, and if cookies are involved the browser sends them along. With a wildcard origin and credentials in the mix, the attacker can read the response.
Why AI Editors Keep Generating This
The pattern is everywhere in tutorials and Stack Overflow answers from 2015-2022. When someone searches "how to fix CORS error Express", the accepted answer is almost always app.use(cors()). That's the data the models trained on.
It's not wrong in local development. The problem is AI editors don't distinguish between "quick demo" and "production API". The boilerplate that unblocks the browser in dev ships unchanged into prod.
Nobody catches it because the tests pass, the frontend works, and nothing logs an error.
The Fix
Replace the bare call with an explicit origin allowlist:
const cors = require('cors');
const allowedOrigins = [
'https://yourdomain.com',
'https://app.yourdomain.com',
];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
The !origin guard handles server-to-server calls (no origin header sent). credentials: true is needed if you use cookies or HTTP auth. Without an explicit origin check, credentials: true combined with a wildcard origin causes the browser to reject the request anyway -- but not all clients are browsers.
On Python/FastAPI, same issue, same fix:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourdomain.com"], # not ["*"]
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
)
Find It in Your Existing Code
# Node/Express
grep -rn "cors()" --include="*.js" --include="*.ts" .
# Python/FastAPI
grep -rn 'allow_origins=["*"]' --include="*.py" .
Any bare cors() call in a file that isn't a local dev config is worth a second look. The fix takes under two minutes once you know what you're changing.
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)