From 7a8e8606f1877131d33220070dd814246160e09c Mon Sep 17 00:00:00 2001 From: shulaoda <165626830+shulaoda@users.noreply.github.com> Date: Fri, 26 Jun 2026 06:37:29 +0800 Subject: [PATCH] fix(auth): gate the dev secret on the dev flag, not the request host --- packages/void/src/router/compile.ts | 2 +- .../void/src/runtime/better-auth-shared.ts | 12 ++++------- packages/void/test/unit/better-auth.test.ts | 21 ++++++++++++++++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/void/src/router/compile.ts b/packages/void/src/router/compile.ts index 966e01dca..3491522f6 100644 --- a/packages/void/src/router/compile.ts +++ b/packages/void/src/router/compile.ts @@ -802,7 +802,7 @@ export function compileRouteTable( if (auth) { lines.push( - `const __voidAuthOptions = { dialect: ${JSON.stringify(auth.dialect)}, providers: ${JSON.stringify(auth.providers)}, userConfig: ${auth.authConfigPath ? '__voidAuthConfig' : 'undefined'} };`, + `const __voidAuthOptions = { dialect: ${JSON.stringify(auth.dialect)}, providers: ${JSON.stringify(auth.providers)}, userConfig: ${auth.authConfigPath ? '__voidAuthConfig' : 'undefined'}, dev: ${JSON.stringify(options.dev === true)} };`, ); if (webSockets.length > 0) { lines.push( diff --git a/packages/void/src/runtime/better-auth-shared.ts b/packages/void/src/runtime/better-auth-shared.ts index 011bca4ba..c4fdae023 100644 --- a/packages/void/src/runtime/better-auth-shared.ts +++ b/packages/void/src/runtime/better-auth-shared.ts @@ -17,6 +17,7 @@ export type VoidAuthRuntimeOptions = { dialect: VoidAuthDialect; providers: Array | null; userConfig?: VoidAuthConfig; + dev?: boolean; }; export type VoidAuthEnv = Record & { @@ -86,11 +87,7 @@ export type VoidAuthRuntimeApi = { ): Promise; }; -function isLocalHost(hostname: string): boolean { - return hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local'); -} - -function resolveSecret(request: Request, env: VoidAuthEnv): string { +function resolveSecret(env: VoidAuthEnv, dev: boolean): string { const explicit = typeof env.BETTER_AUTH_SECRET === 'string' && env.BETTER_AUTH_SECRET.length > 0 ? env.BETTER_AUTH_SECRET @@ -99,8 +96,7 @@ function resolveSecret(request: Request, env: VoidAuthEnv): string { return explicit; } - const url = new URL(request.url); - if (isLocalHost(url.hostname)) { + if (dev) { return 'void-dev-better-auth-secret-1234567890'; } @@ -296,7 +292,7 @@ export function createVoidAuthRuntime( const defaults: BetterAuthOptions = { baseURL: origin, basePath: '/api/auth', - secret: resolveSecret(request, env), + secret: resolveSecret(env, runtime.dev === true), database, ...(emailAndPassword ? { emailAndPassword } : {}), ...(socialProviders ? { socialProviders } : {}), diff --git a/packages/void/test/unit/better-auth.test.ts b/packages/void/test/unit/better-auth.test.ts index 8c408605d..693da805b 100644 --- a/packages/void/test/unit/better-auth.test.ts +++ b/packages/void/test/unit/better-auth.test.ts @@ -132,7 +132,7 @@ describe('resolveVoidAuthOptions', () => { ).rejects.toThrow("auth: Missing credentials for provider 'google'"); }); - it('requires BETTER_AUTH_SECRET outside localhost', async () => { + it('requires BETTER_AUTH_SECRET in production when none is set', async () => { await expect( resolveVoidAuthOptions( new Request('https://example.com/api/auth'), @@ -148,13 +148,28 @@ describe('resolveVoidAuthOptions', () => { ).rejects.toThrow('auth: Missing Better Auth secret. Set BETTER_AUTH_SECRET.'); }); - it('uses the localhost fallback when BETTER_AUTH_SECRET is missing', async () => { + it('does not use the dev fallback secret for a spoofed localhost Host header in production', async () => { + await expect( + resolveVoidAuthOptions( + new Request('http://localhost:3000/api/auth'), + {}, + { + dialect: 'sqlite', + providers: null, + }, + {} as never, + ), + ).rejects.toThrow('auth: Missing Better Auth secret. Set BETTER_AUTH_SECRET.'); + }); + + it('uses the dev fallback secret only when running in dev and BETTER_AUTH_SECRET is missing', async () => { const { options } = await resolveVoidAuthOptions( - new Request('http://localhost:3000/api/auth'), + new Request('https://example.com/api/auth'), {}, { dialect: 'sqlite', providers: null, + dev: true, }, {} as never, );