Skip to content

fix(effect): preserve Vary: Origin in CORS preflight when Access-Control-Request-Headers is present#2144

Open
kitlangton wants to merge 3 commits into
Effect-TS:mainfrom
kitlangton:kit/fix-cors-vary-merge
Open

fix(effect): preserve Vary: Origin in CORS preflight when Access-Control-Request-Headers is present#2144
kitlangton wants to merge 3 commits into
Effect-TS:mainfrom
kitlangton:kit/fix-cors-vary-merge

Conversation

@kitlangton
Copy link
Copy Markdown
Contributor

Summary

HttpMiddleware.cors was silently dropping Vary: Origin from preflight responses whenever the request also had Access-Control-Request-Headers and allowedOrigins was a predicate or a multi-entry list.

headersFromRequestOptions builds the response headers via object spread:

return Headers.fromRecordUnsafe({
  ...allowOrigin(origin),                          // { vary: "Origin", ... }
  ...allowCredentials,
  ...exposeHeaders,
  ...allowMethods,
  ...allowHeaders(accessControlRequestHeaders),    // { vary: "Access-Control-Request-Headers", ... }
  ...maxAge
})

Because both helpers set the same lowercase vary key, the second spread wins and the response goes out with Vary: Access-Control-Request-Headers only. With dynamic origin echoing, that means a shared cache can serve a preflight cached for one origin to a request from a different origin.

Fix

  • Stop returning vary from allowOrigin / allowHeaders.
  • Compute the Vary header explicitly in headersFromRequest / headersFromRequestOptions from a list of contributing entries, joining with , .

The non-preflight path was correct (only allowOrigin contributes to Vary), but it now goes through the same explicit computation for consistency.

Tests

  • New reproducer test that fails on main and passes with the fix.
  • Test that confirms Vary: Origin (and only Origin) when allowedHeaders is configured statically.
  • Test that confirms no Vary header for the wildcard origin case.

Test plan

  • pnpm vitest run test/unstable/http/HttpMiddleware.test.ts (4/4 passing)
  • pnpm check:tsgo
  • pnpm lint-fix
  • pnpm docgen (effect package)
  • Changeset added (.changeset/fix-cors-vary-merge.md, patch)

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 9, 2026

🦋 Changeset detected

Latest commit: bc66d03

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kitlangton kitlangton marked this pull request as ready for review May 9, 2026 03:15
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