Skip to content

fix: require every Origin to be allowed on a CORS request - #1262

Open
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:cors-consistent-multi-origin-handling
Open

fix: require every Origin to be allowed on a CORS request#1262
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:cors-consistent-multi-origin-handling

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Motivation

validateOrigins accepted a request as soon as one of the origins in the Origin header matched:

if (allowedOrigins == HttpOriginMatcher.* || origins.exists(allowedOrigins.matches)) Nil

but the response echoes back every origin it was given — CorsSettingsImpl.accessControlAllowOrigin renders the whole list. A request carrying an allowed origin next to a disallowed one therefore passed validation and echoed the disallowed origin back in Access-Control-Allow-Origin.

The pre-flight path is unaffected (it already only accepts a single origin), so this was limited to simple/actual requests. It is not directly browser-exploitable — the Fetch spec sends exactly one Origin, and a multi-origin Access-Control-Allow-Origin is invalid so a browser rejects it — but a value that never passed validation should not be reflected.

Modification

Require all origins to match rather than any, keeping the existing allowed-origins = * short-circuit. The added nonEmpty guard preserves today's handling of a null origin (an empty origin list), which must still be rejected when the allowed origins are restricted — forall alone would accept it, since Seq.empty.forall(_) is true.

Result

An actual request is accepted only when every origin it lists is allowed, so no origin that failed validation is echoed back. A request whose origins are all disallowed is still rejected with InvalidOrigin listing them, exactly as before.

Tests

  • sbt http-cors/test — pass (47 tests). A new test asserts that an actual request listing a disallowed origin next to an allowed one is rejected with InvalidOrigin. Verified it fails with the fix stashed (the request is accepted and both origins are echoed). The existing "there are two origins" rejection test and the restricted-null-origin test both still pass, which is what pins the nonEmpty guard.
  • sbt http-cors/mimaReportBinaryIssues — pass.
  • Native scalafmt on the changed files — clean.

References

None - requires all origins of a CORS request to be allowed

🤖 Generated with Claude Code

Motivation:
`validateOrigins` accepted a request as soon as *one* of the origins in
the `Origin` header matched the allowed matcher, but the response
echoes back *every* origin it was given
(`CorsSettingsImpl.accessControlAllowOrigin` renders the whole list). A
request carrying an allowed origin next to a disallowed one therefore
passed validation and echoed the disallowed origin back in
`Access-Control-Allow-Origin`. The pre-flight path is not affected
because it already only accepts a single origin, so this was limited to
simple/actual requests.

Modification:
Require all origins to match rather than any, keeping the existing
`allowed-origins = *` short-circuit. The extra `nonEmpty` guard
preserves today's handling of a `null` origin (an empty origin list),
which must still be rejected when the allowed origins are restricted -
`forall` alone would accept it.

Result:
An actual request is accepted only when every origin it lists is
allowed, so no origin that failed validation is echoed back. A request
whose origins are all disallowed is still rejected with
`InvalidOrigin` listing them, as before.

Tests:
- sbt http-cors/test - pass (47 tests); a new test asserts that an actual request listing a disallowed origin next to an allowed one is rejected with `InvalidOrigin`. Verified it fails with the fix stashed (the request is accepted and both origins are echoed). The existing "there are two origins" rejection test and the restricted-`null`-origin test both still pass.
- sbt http-cors/mimaReportBinaryIssues - pass

References:
None - requires all origins of a CORS request to be allowed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant