Skip to content

wheels.middleware.Cors does not emit Vary: Origin when reflecting allowed origins #2707

Closed

Description

@bpamiri

Severity: low — but real. Caching layers may serve a response with the wrong Access-Control-Allow-Origin to a different origin.

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

Description

When wheels.middleware.Cors reflects an allowed origin back via Access-Control-Allow-Origin: <origin>, it does not also emit Vary: Origin. CDN, reverse-proxy, and browser disk caches may then serve a cached response with the wrong ACAO to a subsequent request from a different origin, breaking CORS for that request or (worse) leaking a cached response to a different cross-origin caller.

The legacy Global.cfc:$setCORSHeaders (3.x global-setting path) at line 3564 DOES emit Vary: Origin. The new middleware should at least match.

Suggested fix

One-line addition in vendor/wheels/middleware/Cors.cfc alongside the existing cfheader(name=\"Access-Control-Allow-Origin\", ...) block:

cfheader(name=\"Vary\", value=\"Origin\");

Optionally append to an existing Vary header rather than replacing it, if other middleware emits Vary first.

cc @bpamiri

Activity

  1. wheels-bot commented on May 15, 2026

    @wheels-bot
    Contributor

    Wheels Bot — Triage

    Classified as bug.

    Suspected layer

    middleware — see vendor/wheels/middleware/Cors.cfc and the legacy reference at vendor/wheels/Global.cfc:3565 for the canonical Vary: Origin emission pattern.

    Fix sketch

    The reflected-origin branch of vendor/wheels/middleware/Cors.cfc (around line 72, the cfheader(name = "Access-Control-Allow-Origin", value = local.allowOrigin) call) needs an accompanying cfheader(name = "Vary", value = "Origin") so caches key on the request Origin. The legacy Global.cfc:$setCORSHeaders already does this at line 3565 — the new middleware should match. Care should be taken to emit Vary: Origin only when the origin is actually being reflected (i.e. allow-list mode, not on * wildcard responses) and ideally to append to any pre-existing Vary header rather than overwrite it. A regression test belongs in vendor/wheels/tests/specs/middleware/ asserting the header is present on reflected-origin responses and absent (or unmodified) on wildcard responses.

    Confidence: medium

    Reproducible from the issue body and the cited legacy reference; auto-downgraded from high because the fix lives in vendor/wheels/middleware/**, so propose-fix's safety net should weigh in before a PR lands.

    <!-- wheels-bot:triage:2707 -->
    <!-- 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: #2724

    Spec: vendor/wheels/tests/specs/middleware/CorsSpec.cfc — failing → passing (5 new specs under describe("Vary: Origin header"))
    Implementation: vendor/wheels/middleware/Cors.cfc

    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 a commit that references this issue on May 15, 2026
    d418bdd
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