Repository navigation
Conversation
shulaoda
marked this pull request as draft
June 25, 2026 22:54
shulaoda
marked this pull request as ready for review
June 30, 2026 23:42
…v_flag_not_the_request_host
shulaoda
marked this pull request as draft
June 30, 2026 23:46
cpojer
pushed a commit
that referenced
this pull request
Sep 11, 2026
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Member
|
Done on main. |
shulaoda
deleted the
06-26-fix_auth_gate_the_dev_secret_on_the_dev_flag_not_the_request_host
branch
September 30, 2026 02:41
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.
Summary (the vulnerability)
resolveSecretinpackages/void/src/runtime/better-auth-shared.tsreturned a hardcoded fallback secret (void-dev-better-auth-secret-1234567890) wheneverBETTER_AUTH_SECRETwas unset and the request's hostname looked local (localhost,127.0.0.1, or*.local). It derived that hostname fromnew URL(request.url).hostname.Why this is a real, exploitable issue (necessity)
On a deployed Cloudflare Worker,
request.urlis reconstructed from the client-suppliedHost/:authorityheader, so the hostname check is fully attacker-controlled. Against a Worker that was deployed withoutBETTER_AUTH_SECRET, an attacker only has to sendHost: localhost(orHost: anything.local) forresolveSecretto hand Better Auth the well-known constant secret. Better Auth then signs and verifies session cookies with a secret the attacker also knows, which permits session/cookie forgery and full account impersonation. BecauseresolveSecretis reached on every auth request viaresolveVoidAuthOptions(used by the auth middleware, the auth handler, and the WebSocket auth path), this is not an edge case but the whole authentication surface. The hardcoded secret is convenient for local development, so it cannot simply be deleted; it has to be made unreachable in production.Fix
The dev fallback is now gated on a build-time signal that the request cannot influence:
VoidAuthRuntimeOptionsgains an optionaldev?: boolean, and the generated worker entry bakes the value in as a literal:compile.tsnow emitsdev: ${options.dev === true}into__voidAuthOptions.options.devisconfig.command === 'serve', so it istrueonly undervite devandfalsefor every production build.isLocalHostand thenew URL(request.url)lookup are removed.Why this is correct (correctness)
The dev signal is authoritative and request-independent:
options.devis the sameconfig.command === 'serve'flag already used elsewhere in codegen (for example__VOID_DEV_ROUTING), and it is serialized into the generated module as a constant, so nothing an HTTP client sends can change it. Baking a literal (rather than emittingimport.meta.env.DEV) avoids depending on Vite replacing that expression inside the publishedvoidpackage, where bundling of dependency code is not guaranteed to substitute it. The change is fail-closed:devis optional and defaults to falsy, so every production build and any direct caller that does not opt in gets the throw path, and a configuredBETTER_AUTH_SECRETcontinues to take precedence over everything. Local development is unaffected becausevite devsetsoptions.dev === true, so the fallback still applies there.Behavior change
A deployed Worker that has no
BETTER_AUTH_SECRETnow fails closed withauth: Missing Better Auth secret. Set BETTER_AUTH_SECRET.instead of silently running on the shared dev secret. That previous state was insecure by definition, so failing loudly is the intended outcome; production deployments must setBETTER_AUTH_SECRET(whichvoid deployalready enforces for prod secrets).Testing
Updated the two affected unit tests and added a regression test asserting that a spoofed
Host: localhostwith no dev flag throws rather than returning the fallback. Behavior was also verified by drivingresolveVoidAuthOptionsdirectly across three cases:dev: trueon a non-localhost host returns the fallback, a spoofedHost: localhostwithdevunset throws, and an explicitBETTER_AUTH_SECRETwins.tsgotypecheck passes.