Skip to content

VNX-SEC-012: CORS wildcard or origin reflection

Detects a server that answers cross-origin requests from any website: Access-Control-Allow-Origin echoing the request Origin without an allowlist check, set to *, or CORS middleware configured for every origin.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-SEC-012
Severity High
SARIF level warning
Kind sast
Languages Any file
CWE CWE-942
CAPEC CAPEC-111
ATT&CK T1189
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags cors misconfiguration web
Source internal/sast/rules/vnx-sec-012.rego

Overview

This rule reports three shapes: the Access-Control-Allow-Origin header set to the request’s own Origin header (directly or through a variable read from it a few lines above) with no allowlist check on the origin; the header set to * (w.Header().Set, response[...] =, PHP header(), nginx add_header, Apache Header set, the ingress-nginx cors-allow-origin annotation); and CORS middleware configured for every origin (AWS CDK allowOrigins: Cors.ALL_ORIGINS, FastAPI allow_origins=["*"], Flask-CORS CORS(app), django-cors-headers CORS_ALLOW_ALL_ORIGINS = True, gin AllowAllOrigins, rs/cors AllowOriginFunc returning true, Spring allowedOriginPatterns("*") and bare @CrossOrigin, ASP.NET Core AllowAnyOrigin() and SetIsOriginAllowed(_ => true), Laravel and rack-cors wildcards). It does not report a wildcard whose configuration explicitly disables credentials nearby, specific origins, an origin checked against an allowlist, the header sent by client code (fetch/axios request headers), assertions, tests and documentation. CORS in JavaScript and TypeScript servers (res.setHeader, the cors package, Fastify and NestJS) is reported by VNX-942, so one problem is not reported twice; for those files this rule reports only the AWS CDK construct. CORS controls which websites can read responses from your API using cross-origin JavaScript. Setting it too broadly means any malicious website can make authenticated requests to your API on behalf of a logged-in user and read the responses.

Why This Matters

There is an important distinction between two types of permissive CORS:

Wildcard (*): This allows any origin to make cross-origin requests, but browsers will not send cookies or Authorization headers with wildcards. This is appropriate for truly public APIs (like a CDN or public data endpoint) but dangerous for any API that uses cookies or tokens for authentication.

Origin reflection (origin: true in Express, or echoing Origin back as Access-Control-Allow-Origin): This is the more dangerous pattern. It appears to be dynamic, but effectively allows any origin, and crucially, it can be combined with Access-Control-Allow-Credentials: true. With credentials enabled and an overly permissive origin, any website can make credentialed cross-origin requests to your API, read the responses, and exfiltrate data on behalf of your logged-in users.

The CORS misconfiguration in the 2018 British Airways breach (MITRE ATT&CK T1189) exemplifies how attackers exploit this to steal session tokens and payment data.

What Gets Flagged

# FLAGGED: FastAPI wildcard CORS
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,  # dangerous combined with wildcard
    allow_methods=["*"],
)
// FLAGGED: AWS CDK API Gateway preflight for every origin
defaultCorsPreflightOptions: {
  allowOrigins: apigw.Cors.ALL_ORIGINS,
  allowCredentials: true,
},

Express, Fastify and NestJS servers (res.setHeader('Access-Control-Allow-Origin', req.headers.origin), app.use(cors()), cors({ origin: true })) are reported by VNX-942.

// FLAGGED
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
r.Use(cors.New(cors.Config{AllowAllOrigins: true, AllowCredentials: true}))
# FLAGGED
add_header Access-Control-Allow-Origin $http_origin always;

Not flagged: if (ALLOWED.has(origin)) res.setHeader('Access-Control-Allow-Origin', origin), allow_origins=["https://app.example.com"], and allow_origins=["*"] beside allow_credentials=False.

Remediation

  1. Replace the wildcard or reflection with an explicit allowlist of trusted origins:
# SAFE: explicit allowlist of trusted origins (FastAPI)
from fastapi.middleware.cors import CORSMiddleware

ALLOWED_ORIGINS = [
    "https://app.example.com",
    "https://admin.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["GET", "POST"],
    allow_headers=["Authorization", "Content-Type"],
)
// SAFE: validate origin against allowlist in Express
const ALLOWED_ORIGINS = new Set([
    'https://app.example.com',
    'https://admin.example.com',
]);

app.use(cors({
    origin: (origin, callback) => {
        if (!origin || ALLOWED_ORIGINS.has(origin)) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    credentials: true,
}));
  1. Never combine Access-Control-Allow-Credentials: true with a wildcard or reflected origin. Browsers will block this combination in modern versions, but older browsers may not, and the coding pattern itself signals a CORS misconfiguration.

  2. For truly public APIs (no authentication, no sensitive data), a wildcard without credentials is acceptable:

# SAFE: public read-only API, wildcard without credentials is OK
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,  # explicitly no credentials
)
  1. Review preflight handling. Ensure that OPTIONS preflight requests are handled correctly and that Access-Control-Max-Age is set to cache preflight results and reduce latency.

  2. Test your CORS policy using browser devtools or tools like curl -H "Origin: https://evil.com" -v to verify that unauthorized origins receive an appropriate rejection.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-sec-012
// nosec vnx-sec-012

Or record an ignore rule: vulnetix ignore add --rule vnx-sec-012 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-sec-012.rego, cli/internal/sast/docs/vnx-sec-012.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.