DEV Community

Cover image for Your AI Wrote a CORS Config That Lets Any Website Read Your API
BusyAgents
BusyAgents

Posted on

Your AI Wrote a CORS Config That Lets Any Website Read Your API

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);
});
Enter fullscreen mode Exit fullscreen mode

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
}));
Enter fullscreen mode Exit fullscreen mode

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']
}));
Enter fullscreen mode Exit fullscreen mode

Audit your existing projects with two quick greps:

grep -r "cors()" ./src --include="*.js" | grep -v allowedOrigins
grep -r "origin: '*'" ./src --include="*.js"
Enter fullscreen mode Exit fullscreen mode

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)