Skip to content

wheels.middleware.Cors emits raw comma-list as Access-Control-Allow-Origin when no Origin header is present (spec violation) #2704

Description

@bpamiri

Severity: medium — spec violation on every same-origin / S2S response when the middleware is enabled with multiple allowed origins.

Surfaced by: titan Phase 2.6 (Wheels 4.0 upgrade), 2026-05-15.

Repro

set(middleware = [
    new wheels.middleware.Cors(allowOrigins="https://portal.pai.com,https://portal.paiindustries.com")
]);

Request with no Origin header:

GET /api/v1/jvm HTTP/1.1

→ Response includes:

Access-Control-Allow-Origin: https://portal.pai.com,https://portal.paiindustries.com

The CORS spec requires Access-Control-Allow-Origin to be a single origin or * — never a comma-delimited list. This pollutes same-origin and server-to-server responses, may poison CDN/proxy caches that key on response headers, and confuses any client that inspects the header strictly.

Root cause

vendor/wheels/middleware/Cors.cfc:60-80:

local.allowOrigin = variables.allowOrigins;  // raw comma-list as the default
if (Len(local.origin)) {
    // ... only reassigns local.allowOrigin when an Origin header is present
}
cfheader(name="Access-Control-Allow-Origin", value=local.allowOrigin);

When Origin is absent, the default is never overwritten and the raw list goes out as the header value.

Suggested fix

Default local.allowOrigin = "" and only emit the header when there's a matched origin:

local.allowOrigin = "";
if (Len(local.origin) && originIsAllowed(local.origin)) {
    local.allowOrigin = local.origin;
}
if (Len(local.allowOrigin)) {
    cfheader(name="Access-Control-Allow-Origin", value=local.allowOrigin);
}

cc @bpamiri

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions