AI Prompt Engineering

Getting ChatGPT to Write Accurate CORS Configs Without Wildcard Security Holes

July 12, 2026 9 min read

You ask ChatGPT for a CORS configuration and it hands you Access-Control-Allow-Origin: * within seconds. That works in development. In production, where your API accepts cookies or authorization headers, it's a door left wide open. The model isn't being reckless β€” it's being helpful with incomplete information, and you have to be specific enough to fix that.

What You'll Learn

  • Why ChatGPT gravitates toward wildcard CORS configs and how to counteract it
  • How to craft prompts that produce an explicit, environment-aware origin allowlist
  • The credential + wildcard incompatibility and how to make ChatGPT handle it correctly
  • How to prompt for preflight (OPTIONS) handling that actually matches your real routes
  • Common review steps to catch CORS mistakes before they ship

Prerequisites

This guide assumes you're writing server-side CORS middleware in Node.js/Express, Python/FastAPI, or a similar framework. You should already understand the browser same-origin policy at a conceptual level. The prompting patterns here transfer to any language or framework.

How CORS Actually Works (The Part ChatGPT Skips)

A browser enforces the same-origin policy by blocking cross-origin responses unless the server explicitly permits them via CORS headers. When your frontend on https://app.example.com fetches from https://api.example.com, the browser first checks whether the response includes an Access-Control-Allow-Origin header that matches the request's Origin.

For simple requests (GET, HEAD, certain POST variants), that check happens on the actual response. For anything with custom headers, or methods like PUT/DELETE/PATCH, the browser sends a preflight OPTIONS request first. If the preflight fails, the real request never fires.

Three headers do most of the work:

  • Access-Control-Allow-Origin β€” which origins can read the response
  • Access-Control-Allow-Credentials β€” whether cookies and auth headers are included
  • Access-Control-Allow-Headers and Access-Control-Allow-Methods β€” what the preflight permits

ChatGPT knows this. The problem is that it treats the wildcard as a safe default unless you push back explicitly.

The Wildcard Problem in Practice

A wildcard origin (*) tells the browser "any page on any domain can read this response." For a truly public API with no authentication, that's fine. For anything that checks an Authorization header or a session cookie, it is not.

More critically, the browser refuses to combine Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true. The spec explicitly forbids it. So if your API relies on credentialed requests and ChatGPT gives you a wildcard config, you get a runtime CORS error in the browser and, if you paper over it by removing credential checking, you've opened up a cross-site request forgery vector.

A typical unhelpful ChatGPT output to a vague prompt looks like this:

app.use(cors({
  origin: '*',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

It's not wrong for a dev server. It's wrong for production with auth headers in play.

Prompting for an Explicit Origin Allowlist

The fix starts at the prompt. Give ChatGPT everything it needs to know: your actual origins, whether requests are credentialed, and what methods and headers your routes actually use. Vague prompts produce vague configs.

Here's a prompt template that consistently produces better output:

I need a CORS middleware config for an Express 4 API.

Requirements:
- Allowed origins: https://app.example.com and https://admin.example.com only. No wildcard.
- Requests include an Authorization header (Bearer tokens). Credentials mode is 'include' on the client.
- Methods in use: GET, POST, PUT, DELETE, PATCH, OPTIONS.
- Custom headers sent by the client: Content-Type, Authorization, X-Request-ID.
- Preflight responses should be cached for 600 seconds.
- Any origin not in the allowlist must receive no CORS headers (not even an error origin).
- Do not use Access-Control-Allow-Origin: * anywhere.

Please also add a comment explaining why the wildcard cannot be used with credentials.

That last line matters. Asking ChatGPT to justify the constraint forces it to reason about the rule, which catches cases where it might otherwise silently revert to a wildcard in one of the config branches.

The output you should expect:

const ALLOWED_ORIGINS = [
  'https://app.example.com',
  'https://admin.example.com',
];

// Access-Control-Allow-Origin cannot be '*' when credentials (cookies,
// Authorization headers) are included. The browser blocks such responses
// per the CORS spec. We reflect the request origin only if it is on the
// explicit allowlist.
app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin || ALLOWED_ORIGINS.includes(origin)) {
        callback(null, origin || false);
      } else {
        callback(new Error(`Origin ${origin} not allowed`));
      }
    },
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
    maxAge: 600,
  })
);

Notice the dynamic origin callback: it reflects the matched origin back, not a wildcard. That's the pattern you need for credentialed requests.

Handling Credentials and the Wildcard Incompatibility

If your frontend uses fetch with credentials: 'include', or Axios with withCredentials: true, the browser will reject any response where Access-Control-Allow-Origin is *. Full stop. There is no fallback.

ChatGPT sometimes acknowledges this rule but then produces a config where one code path (usually the fallback branch) still returns a wildcard. Prevent this by adding a specific instruction:

"Under no branch of the code should the server ever set Access-Control-Allow-Origin: *. If the origin is not on the allowlist, omit the header entirely."

Also ask ChatGPT to explain what happens to a credentialed preflight when the origin is rejected. If it can describe the sequence (browser gets no ACAO header, preflight fails, actual request is never sent), the config it writes will be correct. If it can't, prompt it again with more context before trusting the code.

This pattern of asking for an explanation before accepting code is something the ChatGPT database transaction logic guide covers well β€” the same principle applies here: reasoning-first prompts catch silent mistakes that code-first prompts miss.

Preflight Requests: What Gets Missed

Preflight is the most common place a ChatGPT-generated CORS config breaks in production. The model often generates middleware that handles actual requests correctly but forgets to explicitly respond to OPTIONS at every route, or configures a maxAge that conflicts with framework defaults.

Two things to specify in your prompt explicitly:

  • Whether you want a single top-level preflight handler or per-route handling
  • The exact maxAge in seconds (browsers cap this; Chrome caps it around 7200 seconds)

For FastAPI, the prompt needs one extra detail because FastAPI's CORSMiddleware and its routing interact differently than Express:

I'm using FastAPI with CORSMiddleware. My routes use PUT and DELETE.
The middleware must handle OPTIONS preflights before they reach the route handlers.
Do not add explicit OPTIONS route handlers β€” let the middleware intercept them.
Allowed origins: https://app.example.com only. Credentials: true. Max age: 600.

Without that "let the middleware intercept" instruction, ChatGPT occasionally adds explicit @app.options decorators that shadow the middleware, resulting in malformed preflight responses.

Environment-Aware CORS Configs

Local development legitimately needs looser CORS rules. Production does not. ChatGPT will merge these two contexts unless you tell it to keep them separate.

A prompt that produces a clean environment split:

Generate a CORS config for Express that reads allowed origins from an environment variable
CORS_ALLOWED_ORIGINS (comma-separated). In development (NODE_ENV=development), allow
http://localhost:3000 and http://localhost:5173. In production, only allow origins from
the environment variable. Never use a wildcard in production. Throw an error on startup
if NODE_ENV is 'production' and CORS_ALLOWED_ORIGINS is empty or unset.

That startup error guard is important. It forces a misconfiguration to fail loudly at boot rather than silently serving a broken or open config to live users.

const isProd = process.env.NODE_ENV === 'production';

let allowedOrigins;
if (isProd) {
  if (!process.env.CORS_ALLOWED_ORIGINS) {
    throw new Error('CORS_ALLOWED_ORIGINS must be set in production');
  }
  allowedOrigins = process.env.CORS_ALLOWED_ORIGINS.split(',').map(o => o.trim());
} else {
  allowedOrigins = [
    'http://localhost:3000',
    'http://localhost:5173',
  ];
}

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin || allowedOrigins.includes(origin)) {
        callback(null, origin || false);
      } else {
        callback(new Error(`Origin ${origin} not permitted`));
      }
    },
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    maxAge: 600,
  })
);

This is also a good place to mention that similar environment-aware configuration patterns appear in structured logging configs β€” keeping dev and prod behavior separate prevents a whole class of subtle production bugs.

Common Pitfalls When Using ChatGPT for CORS

Reflecting origin without validating it

A naive implementation of the dynamic origin callback looks like this: callback(null, requestOrigin) without checking the allowlist. ChatGPT occasionally generates this when the prompt says "reflect the origin" without saying "only if it's on the allowlist." Always verify the output's callback logic explicitly.

Missing the Vary header

When you reflect the request origin dynamically, you must include Vary: Origin in the response. Without it, a CDN or reverse proxy may cache a response with one origin's value and serve it to a request from a different origin. The cors npm package adds this automatically, but if ChatGPT generates raw middleware, check that Vary: Origin is explicitly set.

Overly broad method lists

Prompts that say "allow all HTTP methods" will get you methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE']. TRACE and CONNECT have no place in a standard web API's CORS config. List only the methods your routes actually handle.

Forgetting that CORS is not authentication

CORS headers control what browsers allow, not what your server permits. A curl request bypasses CORS entirely. ChatGPT sometimes implies that a locked-down CORS config means your API is secure. It doesn't β€” authentication and authorization still need to happen server-side on every request. This is worth calling out explicitly in your prompt: "Add a comment clarifying that CORS is enforced by the browser and is not a substitute for server-side auth."

If you're also generating auth-adjacent configurations with ChatGPT, the techniques in the feature flag logic guide and the Prometheus alerting rules guide share the same principle: spell out failure modes in the prompt to stop the model from producing confident-looking configs that miss the hard edge cases.

No test for rejected origins

Ask ChatGPT to generate a test suite alongside the middleware. A prompt addition like "also write Jest tests that verify: (1) an allowed origin gets the correct headers, (2) a disallowed origin gets no CORS headers, (3) a credentialed preflight from an allowed origin returns status 204" will surface gaps in the config logic before you ship it.

Wrapping Up

ChatGPT is genuinely useful for boilerplate CORS configs, but you have to drive it precisely. Here are the concrete actions to take from here:

  1. Always name your allowed origins explicitly in the prompt β€” never say "allow my frontend" and expect the model to infer the domain.
  2. State the credential mode upfront. If your client sends cookies or an Authorization header, say so, and forbid wildcard origins explicitly in the prompt.
  3. Ask for a startup guard that fails fast when the origin allowlist is missing or empty in a production environment variable.
  4. Request a justification comment in the generated code explaining why wildcards are excluded β€” it forces the model to reason about the constraint rather than paper over it.
  5. Ask for tests alongside the config. A test that verifies a rejected origin gets no CORS headers will catch the most common ChatGPT output mistake before it reaches production.

Frequently Asked Questions

Why does ChatGPT keep generating Access-Control-Allow-Origin wildcard in CORS configs?

ChatGPT defaults to wildcards because they resolve the most common CORS errors quickly and require no domain-specific knowledge. When your prompt doesn't mention credentials, specific origins, or production constraints, the model optimizes for a configuration that works broadly rather than one that is secure.

Can I use Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true?

No β€” the CORS specification explicitly forbids this combination. Browsers will reject the response with a CORS error if a server tries to use a wildcard origin alongside credentials: true. You must reflect the exact matched origin from your allowlist instead.

How do I make ChatGPT generate CORS middleware that handles preflight OPTIONS requests correctly?

Tell ChatGPT explicitly whether you want a single top-level preflight handler or per-route handling, specify the exact maxAge value, and name the framework version. For FastAPI, clarify that CORSMiddleware should intercept OPTIONS before route handlers to prevent shadowing issues.

Does a restrictive CORS policy make my API secure from unauthorized access?

No β€” CORS is a browser enforcement mechanism and curl or any non-browser client ignores it entirely. A strict CORS policy protects against certain cross-site attacks in browser contexts, but your server still needs proper authentication and authorization on every request regardless of CORS settings.

What is the Vary: Origin header and why does a dynamic CORS config need it?

Vary: Origin tells caches (CDNs, reverse proxies) that the response differs depending on the request's Origin header. Without it, a cache might serve a CORS response intended for one origin to a request from a different origin, causing unexpected behavior or leaking cross-origin data.

πŸ“€ Share this article

Sign in to save

Comments (0)

No comments yet. Be the first!

Leave a Comment

Sign in to comment with your profile.

πŸ“¬ Weekly Newsletter

Stay ahead of the curve

Get the best programming tutorials, data analytics tips, and tool reviews delivered to your inbox every week.

No spam. Unsubscribe anytime.