Why Cursor Keeps Generating Wildcard CORS -- And How to Fix It

Dev.to / 4/12/2026

💬 OpinionDeveloper Stack & InfrastructureIdeas & Deep AnalysisTools & Practical Usage

Key Points

  • Many AI-assisted coding tools recommend Express CORS middleware with default settings (e.g., `cors()`), which implicitly allows all origins via `Access-Control-Allow-Origin: *`.
  • Wildcard CORS is particularly risky for authenticated APIs that rely on cookies or session tokens, because it enables cross-site request attacks where a malicious site can trigger requests as logged-in users.
  • The article frames the issue as a known vulnerability pattern (CWE-942: permissive cross-domain policy) and highlights how it can be overlooked when AI-suggested boilerplate is left unchanged from templates.
  • The recommended fix is to replace wildcard CORS with an explicit origin allowlist, ideally controlled through an environment variable so deployments can be configured safely.

TL;DR

  • AI editors almost always default to cors() with no config -- which sets Access-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.