fix(core): route 504 CORS through allowlist + cap Access-Control-Max-Age at 7200 - #315
Merged
Conversation
…Age at 7200 The 504 timeout path in createLambdaHandler built its CORS headers by hand: it reflected the request Origin verbatim (falling back to '*') and always sent Access-Control-Allow-Credentials: true. That reflected an unvalidated origin back to the caller with credentials enabled, and the '*' fallback is outright invalid in combination with credentials, so a timed-out request either leaked a permissive CORS grant or produced a response the browser rejects. The timeout path now goes through buildCorsHeaders(origin), the same allowlist-validated, fail-closed helper the normal response path uses: an origin that is not on the allowlist gets no CORS grant at all. Also lowers Access-Control-Max-Age from 86400 to 7200 on the OPTIONS preflight response and in the dev server. 86400 is misleading — browsers cap preflight caching well below that (Chromium 7200s, Firefox 86400s but Safari/WebKit far lower), so the advertised value never applied and made the preflight cache lifetime look longer than it is. 7200 is the largest value that is actually honoured by the strictest mainstream engine. Found during the bug bash (P3 report on the CORS preflight Max-Age value); the 504 reflection was spotted while confirming the Max-Age code path. Tests updated for the new Max-Age, and the old test asserting reflective 504 behaviour is replaced by two allowlist-aware tests (allowed origin gets the grant, disallowed origin gets none). 81/81 unit tests pass.
🦋 Changeset detectedLatest commit: 6b22f4c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
…gins Move buildCorsHeaders into cors.ts and cover the isOriginAllowed===false branch directly, plus assert the 403 preemption on the timeout path.
osama-rizk
approved these changes
Aug 7, 2026
Simone319
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The 504 timeout path in
createLambdaHandlerbuilt its CORS headers by hand: it reflected the requestOriginverbatim (falling back to'*') and always sentAccess-Control-Allow-Credentials: true. That reflected an unvalidated origin back to the caller with credentials enabled, and'*'+ credentials is outright invalid, so a timed-out request either leaked a permissive CORS grant or produced a response the browser rejects. The timeout path now goes throughbuildCorsHeaders(origin)— the same allowlist-validated, fail-closed helper the normal response path uses — so an origin that is not on the allowlist gets no CORS grant at all.Also lowers
Access-Control-Max-Agefrom86400to7200on the OPTIONS preflight response and in the dev server. 86400 is misleading: browsers cap preflight caching well below that (Chromium 7200s, Safari/WebKit lower still), so the advertised value never applied and made the preflight cache lifetime look longer than it really is. 7200 is the largest value actually honoured by the strictest mainstream engine.Found during the bug bash (P3 report on the CORS preflight Max-Age value); the 504 origin reflection was spotted while confirming the Max-Age code path.
Changes
packages/core/src/lambda-handler.ts— 504 path usesbuildCorsHeaders(origin); OPTIONSAccess-Control-Max-Age86400 → 7200.packages/core/src/scripts/dev-server.ts—Access-Control-Max-Age86400 → 7200.packages/core/src/cors.test.ts— preflight Max-Age expectation updated to 7200.packages/core/src/lambda-handler.test.ts— the old test asserting reflective 504 behaviour is replaced by two allowlist-aware tests (allowed origin gets the grant, disallowed origin gets none).Testing
npx tsx --test src/cors.test.ts src/lambda-handler.test.tsinpackages/core: 81/81 pass, 0 fail (21 suites).The 2 pre-existing
hosting.tsbuild errors in this package are unrelated to this change and are left untouched.