Skip to content

fix(api-client): Treat * as wildcard in preflight Access-Control-Allow-Headers#12503

Merged
anthonyshew merged 2 commits intovercel:mainfrom
bitttttten:fix/preflight-wildcard-cors
Mar 31, 2026
Merged

fix(api-client): Treat * as wildcard in preflight Access-Control-Allow-Headers#12503
anthonyshew merged 2 commits intovercel:mainfrom
bitttttten:fix/preflight-wildcard-cors

Conversation

@bitttttten
Copy link
Copy Markdown
Contributor

fix(api-client): treat * as wildcard in preflight Access-Control-Allow-Headers check

Background

We run a self-hosted Turborepo remote cache backed by an AWS Lambda Function URL with TURBO_PREFLIGHT=true. Our CORS configuration uses Access-Control-Allow-Headers: *. I'm thinking this is likely what most self-hosted remote cache deployments use too, which is why I am proposing handling this in turborepo core too. I appreciate that a fix is to be explicit with CORS headers, like allow_headers = ["Authorization", "Content-Type", "User-Agent", "x-artifact-duration", "x-artifact-tag", "x-artifact-sha", "x-artifact-dirty-hash" ] etc. although some may not have control and I thought that * is quite a common value.

The problem

With TURBO_PREFLIGHT=true, every artifact request returns 401 despite the token being valid. Without preflight, the same token works fine. We traced it to do_preflight in turborepo-api-client. After the OPTIONS preflight, turbo checks Access-Control-Allow-Headers to decide whether to include the Authorization header:

let allow_auth = AUTHORIZATION_REGEX.is_match(allowed_headers);

The regex (?i)(?:^|,) *authorization *(?:,|$) looks for the literal string authorization in a comma-separated list. The CORS wildcard * — which per the Fetch spec means "all headers are allowed" — doesn't match. So allow_auth is false, turbo strips the Bearer token, and the server returns 401. So anyone configuring CORS with * would run into this too.

The fix

Short-circuit with allowed_headers == "*" before the regex, matching the CORS spec where * means "allow all headers". A test case is added alongside the existing allow-auth and no-allow-auth tests.

Let me know if you have any questions. Rust isn't my strongest language so this PR was helped along with Opus 🫶

…w-Headers

The CORS spec defines * as a wildcard meaning "all headers are
allowed". The preflight check in do_preflight only matched the
literal string "authorization" via regex, so a server responding
with Access-Control-Allow-Headers: * caused turbo to strip the
Authorization header from subsequent requests — resulting in 401s.

Short-circuit with an exact "*" check before the regex.
@bitttttten bitttttten requested a review from a team as a code owner March 31, 2026 10:02
@bitttttten bitttttten requested review from tknickman and removed request for a team March 31, 2026 10:02
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Mar 31, 2026

@bitttttten is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown
Contributor

@anthonyshew anthonyshew left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @bitttttten, thanks. Simple enough change that will be helpful for folks.

@anthonyshew anthonyshew changed the title fix(api-client): treat * as wildcard in preflight Access-Control-Allow-Headers fix(api-client): Treat * as wildcard in preflight Access-Control-Allow-Headers Mar 31, 2026
Comment thread crates/turborepo-api-client/src/lib.rs Outdated
@anthonyshew anthonyshew enabled auto-merge (squash) March 31, 2026 13:43
@anthonyshew
Copy link
Copy Markdown
Contributor

The windows partition failure is a flake. We are okay to merge.

@anthonyshew anthonyshew disabled auto-merge March 31, 2026 14:53
@anthonyshew anthonyshew merged commit 960b2eb into vercel:main Mar 31, 2026
44 of 56 checks passed
github-actions Bot added a commit that referenced this pull request Mar 31, 2026
## Release v2.9.2-canary.2

Versioned docs: https://v2-9-2-canary-2.turborepo.dev

### Changes

- release(turborepo): 2.9.2-canary.1 (#12508) (`2bebb91`)
- fix: Unblock `watch` loop so interruptible persistent tasks restart on
file changes (#12509) (`95ec9ed`)
- fix(api-client): Treat * as wildcard in preflight
Access-Control-Allow-Headers (#12503) (`960b2eb`)

Co-authored-by: Turbobot <turbobot@vercel.com>
@bitttttten
Copy link
Copy Markdown
Contributor Author

Thanks gang, big up turborepo 🥳

github-actions Bot added a commit that referenced this pull request Mar 31, 2026
## Release v2.9.2-canary.3

Versioned docs: https://v2-9-2-canary-3.turborepo.dev

### Changes

- fix(api-client): Treat * as wildcard in preflight
Access-Control-Allow-Headers (#12503) (`960b2eb`)
- release(turborepo): 2.9.2-canary.2 (#12510) (`d4d3f9d`)
- docs: Document `turbo.*` generator variables (#12511) (`aa014c7`)
- fix: Backfill missing pnpm workspace importer entries during prune
(#12514) (`45f2f43`)

Co-authored-by: Turbobot <turbobot@vercel.com>
github-actions Bot added a commit that referenced this pull request Mar 31, 2026
## Release v2.9.2

Versioned docs: https://v2-9-2.turborepo.dev

### Changes

- release(turborepo): 2.9.1 (#12498) (`4240dd4`)
- fix: Add retry logic to example update workflow push step (#12499)
(`00a4aae`)
- docs: Add documentation for cacheMaxAge and cacheMaxSize options
(#12500) (`5b65487`)
- feat(examples): Add Next.js + Elysia full-stack starter template
(#12414) (`1559d0f`)
- fix: Resolve correct nested bun lockfile versions during prune
(#12506) (`ee5bcbf`)
- Revert "fix: Avoid `setsid()` in PTY spawn to prevent macOS Gatekeeper
CPU spikes" (#12507) (`6c3b8a6`)
- release(turborepo): 2.9.2-canary.1 (#12508) (`2bebb91`)
- fix: Unblock `watch` loop so interruptible persistent tasks restart on
file changes (#12509) (`95ec9ed`)
- fix(api-client): Treat * as wildcard in preflight
Access-Control-Allow-Headers (#12503) (`960b2eb`)
- release(turborepo): 2.9.2-canary.2 (#12510) (`d4d3f9d`)
- docs: Document `turbo.*` generator variables (#12511) (`aa014c7`)
- fix: Backfill missing pnpm workspace importer entries during prune
(#12514) (`45f2f43`)
- release(turborepo): 2.9.2-canary.3 (#12515) (`59a42a5`)
- fix: Include transitive dependencies in engine graph pruning for
affected paths using Task Graph (#12516) (`8c88521`)
- release(turborepo): 2.9.2-canary.4 (#12518) (`aed2066`)
- chore: Update AI-generated response text for clarity (#12517)
(`4a4e661`)
- fix: Preserve shallow install strategy during npm lockfile pruning
(#12520) (`72577db`)

---------

Co-authored-by: Turbobot <turbobot@vercel.com>
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.

2 participants