Skip to content

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

Closed

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

Activity

  1. wheels-bot commented on May 15, 2026

    @wheels-bot
    Contributor

    Wheels Bot — Triage

    Classified as bug.

    Suspected layer

    middleware — see .ai/wheels/middleware/ for canonical patterns.

    Fix sketch

    Single-file change in vendor/wheels/middleware/Cors.cfc. At line 60, the default local.allowOrigin = variables.allowOrigins seeds the raw comma-delimited list, and the Len(local.origin) guard at line 61 only reassigns it when an Origin request header is present. When the request has no Origin (same-origin, server-to-server, curl without -H), the raw list flows through to the cfheader call at line 72, violating the CORS spec requirement that Access-Control-Allow-Origin be a single origin or *. The reporter's suggested fix is correct: default local.allowOrigin = "", only set it when the incoming Origin matches the allowlist (or when allowOrigins == "*"), and gate the entire cfheader block on Len(local.allowOrigin) (already present at line 70). Tests live under vendor/wheels/tests/specs/middleware/ — propose-fix should add coverage for the "no Origin header + multi-origin allowlist" case.

    Confidence: medium

    The report is unambiguous and the fix is mechanical, but middleware-layer changes are auto-downgraded from high per the triage rails so propose-fix's safety net can re-evaluate.

    <!-- wheels-bot:triage:2704 -->
    <!-- wheels-bot:triage-class:bug -->
    <!-- wheels-bot:triage-confidence:medium -->

  2. wheels-bot commented on May 15, 2026

    @wheels-bot
    Contributor

    Wheels Bot — Fix proposed

    Draft PR: #2725

    Spec: vendor/wheels/tests/specs/middleware/CorsSpec.cfc — added an Access-Control-Allow-Origin header resolution describe block covering the no-Origin-header path against comma-list / single-origin / wildcard / matched / non-matched / empty configs. Pre-fix: 6 errors ($resolveAllowOrigin undefined). Post-fix: 16/16 passing.
    Implementation: vendor/wheels/middleware/Cors.cfc — extracted origin resolution into a pure $resolveAllowOrigin() method; the Access-Control-Allow-Origin header is now only emitted when there is a value to emit.

    A human review is required before merge. Reviewer A and Reviewer B will weigh in shortly. Doc updates (MDX guides, .ai/wheels/, CLAUDE.md) are handled separately by bot-update-docs.yml.

  3. added 2 commits that reference this issue on May 15, 2026
    a9f1367
    8878dd5
  4. added a commit that references this issue on May 16, 2026
    f9a4312
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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