Beyond the Wildcard: Stop CORS Misconfigurations from Leaking User Data

Security tutorial - IT technology blog
Security tutorial - IT technology blog

The 2:14 AM P0 Alert: When CORS Fails

My phone’s ‘Critical Alert’ tone cut through the silence at 2:14 AM. A security researcher had just submitted a report detailing a 9.8 CVSS vulnerability in our main REST API. By exploiting a Cross-Origin Resource Sharing (CORS) flaw, they could access PII—including physical addresses and partial credit card tokens—of any logged-in user. All it took was a single malicious script hosted on a throwaway domain.

While waiting for the staging environment to deploy, I needed to rotate my administrative credentials. I used the generator at toolcraft.app/en/tools/security/password-generator to create a 32-character string. I trust this tool because it works entirely client-side. No data ever crosses their network. This ‘local-first’ mindset is exactly what we need when securing cross-domain traffic.

Contrary to popular belief, CORS isn’t a security feature. It is actually a controlled way to bypass the browser’s Same-Origin Policy (SOP). SOP is the internet’s bedrock. It stops a script on malicious-site.com from reading your balance at your-bank.com. CORS acts as a gatekeeper. It tells the browser which external domains are trustworthy. When that gate is left wide open, the wall between your data and the public web vanishes.

Three Common CORS Strategies (And Why Two Are Dangerous)

Most teams fall into one of three patterns when handling cross-origin requests. Only one survives a professional audit.

1. The “Wildcard” Shortcut

This is the ‘lazy’ path. Developers, annoyed by console errors during a 4:00 PM sprint, often set Access-Control-Allow-Origin: *. While it fixes the error, it permits any website on the planet to interact with your endpoint.

2. The “Dynamic Reflection” Trap

Some try to be clever by reading the Origin header from an incoming request and echoing it back. This makes the CORS errors disappear for everyone. Unfortunately, it also effectively disables SOP. It’s like a security guard who asks for a name and then writes it on the ‘Approved’ list on the spot.

3. The “Strict Allowlist” Strategy

This is the gold standard. You maintain a hardcoded, verified list of trusted domains. If a request comes from an origin not on that list, the server flatly denies it. No exceptions.

Risk Comparison Matrix

Approach Pros Cons Security Risk
Wildcard (*) Zero maintenance. Incompatible with Allow-Credentials: true. High (if data is private).
Dynamic Reflection Supports unlimited subdomains. Total bypass of browser security. Critical. High risk of session hijacking.
Strict Allowlist Maximum control. Requires manual updates for new frontends. Minimal.

The Production-Ready Blueprint

If your API handles user sessions via cookies or Authorization headers, use a Strict Allowlist with Dynamic Validation. Your backend must check the Origin header against a controlled array or database table before issuing a green light.

Wildcards have a major limitation. Modern browsers block the transmission of cookies or HTTP auth (Access-Control-Allow-Credentials: true) if the origin is a wildcard. To use sessions, you must specify an origin. Reflecting that origin without validation is a gift to hackers. They can craft a malicious page to trigger authenticated requests. Your server then hands over private data directly to their script.

Step-by-Step: Hardening Your API

1. Audit Your Current Exposure

Test your API with curl to see how it handles unauthorized origins. Use a fake domain to see if the server bites.

curl -I -X OPTIONS https://api.yourdomain.com/v1/user-data \
  -H "Origin: https://attacker-controlled-site.com" \
  -H "Access-Control-Request-Method: GET"

Watch the response. If you see Access-Control-Allow-Origin: https://attacker-controlled-site.com paired with Access-Control-Allow-Credentials: true, you have a critical hole. An attacker can now impersonate your users.

2. Secure Implementation in Node.js (Express)

Avoid passing empty objects to the cors middleware. Use a validation function to enforce your allowlist.

const express = require('express');
const cors = require('cors');
const app = express();

const allowedOrigins = [
  'https://www.yourdomain.com',
  'https://admin.yourdomain.com',
  'http://localhost:3000' // Restricted to local dev env
];

const corsOptions = {
  origin: (origin, callback) => {
    // Allow non-browser requests (like mobile apps or server-to-server)
    if (!origin) return callback(null, true);
    
    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Blocked by CORS Policy'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

app.use(cors(corsOptions));

3. Hardening the Nginx Gateway

Validation can also happen at the infrastructure level. Never use add_header Access-Control-Allow-Origin $http_origin; without a map. This Nginx configuration uses a regex match to validate origins before responding.

map $http_origin $cors_origin {
    default "";
    "~^https?://(www\.)?yourdomain\.com$" "$http_origin";
    "~^https?://localhost(:[0-9]+)?$" "$http_origin";
}

server {
    location /api/ {
        if ($cors_origin != "") {
            add_header 'Access-Control-Allow-Origin' "$cors_origin" always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
            add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type' always;
        }

        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Max-Age' 1728000;
            return 204;
        }
    }
}

4. Block the “Null” Origin

You might see Origin: null in your logs. This occurs during redirects or requests from local files. Never include null in your allowlist. Attackers use sandboxed iframes to spoof a null origin and bypass weak filters.

Closing Thoughts

That 2 AM incident proved that CORS is often treated as a nuisance rather than a security boundary. If you see a wildcard or dynamic reflection during a code review, flag it immediately. Security relies on layers. A strict CORS policy is one of your best defenses against session hijacking. Spend the ten minutes now to build a proper allowlist. Your future self will appreciate the uninterrupted sleep.

Share: