You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
wheels.middleware.Cors emits raw comma-list as Access-Control-Allow-Origin when no Origin header is present (spec violation) #2704
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);
}
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.
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.
Activity
wheels-bot commentedon May 15, 2026
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 defaultlocal.allowOrigin = variables.allowOriginsseeds the raw comma-delimited list, and theLen(local.origin)guard at line 61 only reassigns it when anOriginrequest header is present. When the request has noOrigin(same-origin, server-to-server, curl without-H), the raw list flows through to thecfheadercall at line 72, violating the CORS spec requirement thatAccess-Control-Allow-Originbe a single origin or*. The reporter's suggested fix is correct: defaultlocal.allowOrigin = "", only set it when the incomingOriginmatches the allowlist (or whenallowOrigins == "*"), and gate the entirecfheaderblock onLen(local.allowOrigin)(already present at line 70). Tests live undervendor/wheels/tests/specs/middleware/— propose-fix should add coverage for the "noOriginheader + multi-origin allowlist" case.Confidence:
mediumThe report is unambiguous and the fix is mechanical, but middleware-layer changes are auto-downgraded from
highper 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 -->
wheels-bot commentedon May 15, 2026
Wheels Bot — Fix proposed
Draft PR: #2725
Spec:
vendor/wheels/tests/specs/middleware/CorsSpec.cfc— added anAccess-Control-Allow-Origin header resolutiondescribe block covering the no-Origin-header path against comma-list / single-origin / wildcard / matched / non-matched / empty configs. Pre-fix: 6 errors ($resolveAllowOriginundefined). Post-fix: 16/16 passing.Implementation:
vendor/wheels/middleware/Cors.cfc— extracted origin resolution into a pure$resolveAllowOrigin()method; theAccess-Control-Allow-Originheader 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 bybot-update-docs.yml.fix(middleware): Cors no longer emits raw comma list as Access-Contro…
fix(middleware): Cors no longer emits raw comma list as Access-Contro…
fix(middleware): Cors no longer emits raw comma list as Access-Contro…