From 31e1de679fc9e10b8a96bb0f2eb90cb8589f4fc8 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Thu, 24 Sep 2026 21:20:14 +0200 Subject: [PATCH 1/3] fix(ai-gateway): require opt-in for API request logging --- .../ai-gateway/rewriteModelResponse.test.ts | 20 ++++++++++++++++++ .../lib/ai-gateway/rewriteModelResponse.ts | 21 +++++++------------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts b/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts index 88c6929f4e..ef53d931b3 100644 --- a/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts +++ b/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts @@ -1382,6 +1382,7 @@ describe('rewriteModelResponse', () => { }); expect(result).not.toBeNull(); + expect(mockedAfter).toHaveBeenCalledTimes(1); }); test('does not schedule a log insert for non-custom models without opt-in', async () => { @@ -1398,6 +1399,25 @@ describe('rewriteModelResponse', () => { expect(mockedOptIn).toHaveBeenCalled(); }); + test.each(['user@anaconda.com', 'user@kilocode.ai'])( + 'requires an explicit opt-in for %s', + async email => { + await rewriteModelResponse({ + response: jsonResponse({ model: 'openai/gpt-5' }), + model: 'openai/gpt-5', + providerId: 'openrouter', + kind: 'chat_completions', + logging: makeLogging({ + user: { id: 'test-user', google_user_email: email } as RequestLoggingParams['user'], + }), + responseTransforms: null, + }); + + expect(mockedAfter).not.toHaveBeenCalled(); + expect(mockedOptIn).toHaveBeenCalledWith({ accountId: 'test-user', organizationId: null }); + } + ); + test('always schedules a log insert for custom models', async () => { await rewriteModelResponse({ response: jsonResponse({ model: 'kilo-internal/my-model' }), diff --git a/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts b/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts index 884f946d5b..74c4d58c64 100644 --- a/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts +++ b/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts @@ -73,19 +73,6 @@ type CapturedResponseBody = | { text: string; readError?: never } | { readError: string; text?: string }; -async function isLoggingEnabledForUser( - user: User | null, - organizationId: string | null -): Promise { - // Hardcoded opt-ins mainly for local testing - if (user?.google_user_email.endsWith('@anaconda.com')) return true; - if (user?.google_user_email.endsWith('@kilocode.ai')) return true; - return isDynamicallyOptedIntoRequestLogging({ - accountId: user?.id ?? null, - organizationId, - }); -} - export function sanitizeApiRequestLogRequest(request: GatewayRequest): unknown { const gateway = request.body.providerOptions?.gateway; if (!gateway?.byok) { @@ -115,7 +102,13 @@ async function createRequestLogCapture( logging: RequestLoggingParams ): Promise { const { user, organization_id, session_id, vercel_request_id, request } = logging; - if (provider !== 'custom' && !(await isLoggingEnabledForUser(user, organization_id))) { + if ( + provider !== 'custom' && + !(await isDynamicallyOptedIntoRequestLogging({ + accountId: user?.id ?? null, + organizationId: organization_id, + })) + ) { return null; } const status = response.status; From 93bf47709bb819e9a6ac0334fd95be7c96ad9338 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Thu, 24 Sep 2026 21:31:45 +0200 Subject: [PATCH 2/3] fix(ai-gateway): require opt-in for custom request logs --- .../RequestLoggingOptInsContent.tsx | 7 +++-- .../ai-gateway/rewriteModelResponse.test.ts | 26 ++++++++++++++----- .../lib/ai-gateway/rewriteModelResponse.ts | 1 - 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx b/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx index f63ad610ed..b44f9c61d7 100644 --- a/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx +++ b/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx @@ -139,7 +139,10 @@ export default function RequestLoggingOptInsContent() { Active opt-ins - Hardcoded email-domain opt-ins are not listed here. + + Requests are logged only when the account or organization is opted in, including custom + provider requests. + @@ -164,7 +167,7 @@ export default function RequestLoggingOptInsContent() { {!isLoading && optIns?.length === 0 && ( - No dynamic request logging opt-ins. + No request logging opt-ins. )} diff --git a/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts b/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts index ef53d931b3..f0c22bbb0c 100644 --- a/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts +++ b/apps/web/src/lib/ai-gateway/rewriteModelResponse.test.ts @@ -1385,7 +1385,7 @@ describe('rewriteModelResponse', () => { expect(mockedAfter).toHaveBeenCalledTimes(1); }); - test('does not schedule a log insert for non-custom models without opt-in', async () => { + test('does not schedule a log insert without opt-in', async () => { await rewriteModelResponse({ response: jsonResponse({ model: 'openai/gpt-5' }), model: 'openai/gpt-5', @@ -1418,7 +1418,22 @@ describe('rewriteModelResponse', () => { } ); - test('always schedules a log insert for custom models', async () => { + test('does not log custom models without opt-in', async () => { + await rewriteModelResponse({ + response: jsonResponse({ model: 'kilo-internal/my-model' }), + model: 'kilo-internal/my-model', + providerId: 'custom', + kind: 'chat_completions', + logging: makeLogging(), + responseTransforms: null, + }); + + expect(mockedAfter).not.toHaveBeenCalled(); + expect(mockedOptIn).toHaveBeenCalledWith({ accountId: null, organizationId: null }); + }); + + test('logs custom models with opt-in', async () => { + mockedOptIn.mockResolvedValueOnce(true); await rewriteModelResponse({ response: jsonResponse({ model: 'kilo-internal/my-model' }), model: 'kilo-internal/my-model', @@ -1429,10 +1444,9 @@ describe('rewriteModelResponse', () => { }); expect(mockedAfter).toHaveBeenCalledTimes(1); - expect(mockedOptIn).not.toHaveBeenCalled(); }); - test('always logs unrewritten custom model responses', async () => { + test('does not log unrewritten custom model responses without opt-in', async () => { await logUnrewrittenResponse({ response: jsonResponse({ error: 'upstream error' }, 400), model: 'kilo-internal/my-model', @@ -1440,8 +1454,8 @@ describe('rewriteModelResponse', () => { logging: makeLogging(), }); - expect(mockedAfter).toHaveBeenCalledTimes(1); - expect(mockedOptIn).not.toHaveBeenCalled(); + expect(mockedAfter).not.toHaveBeenCalled(); + expect(mockedOptIn).toHaveBeenCalledWith({ accountId: null, organizationId: null }); }); }); diff --git a/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts b/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts index 74c4d58c64..85973bc449 100644 --- a/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts +++ b/apps/web/src/lib/ai-gateway/rewriteModelResponse.ts @@ -103,7 +103,6 @@ async function createRequestLogCapture( ): Promise { const { user, organization_id, session_id, vercel_request_id, request } = logging; if ( - provider !== 'custom' && !(await isDynamicallyOptedIntoRequestLogging({ accountId: user?.id ?? null, organizationId: organization_id, From 2d72f88ebd7c208c0a160e8e91149493cb225f33 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Thu, 24 Sep 2026 21:36:38 +0200 Subject: [PATCH 3/3] chore(admin): simplify request logging opt-in copy --- .../request-logging-opt-ins/RequestLoggingOptInsContent.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx b/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx index b44f9c61d7..6c849eb496 100644 --- a/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx +++ b/apps/web/src/app/admin/request-logging-opt-ins/RequestLoggingOptInsContent.tsx @@ -140,8 +140,7 @@ export default function RequestLoggingOptInsContent() { Active opt-ins - Requests are logged only when the account or organization is opted in, including custom - provider requests. + Requests are logged only when the account or organization is opted in.