From 3534d057ce61b783a5572d7b444f39c0d8ff9dcc Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:48:24 +0200 Subject: [PATCH 1/2] feat(core): Add and use `dataCollection.databaseQueryData` (for e.g. Supabase filter values and mutation bodies) --- packages/astro/test/server/middleware.test.ts | 1 + packages/core/src/integrations/supabase.ts | 5 ++-- packages/core/src/types/datacollection.ts | 7 +++++ .../defaultPiiToCollectionOptions.ts | 4 +++ .../resolveDataCollectionOptions.ts | 2 ++ .../lib/integrations/mcp-server/testUtils.ts | 1 + .../test/lib/integrations/supabase.test.ts | 30 +++++++++---------- .../defaultPiiToCollectionOptions.test.ts | 4 +++ .../resolveDataCollectionOptions.test.ts | 19 +++++++++++- packages/core/test/lib/utils/request.test.ts | 1 + .../hooks/wrapMiddlewareHandler.test.ts | 1 + packages/remix/test/server/errors.test.ts | 1 + 12 files changed, 58 insertions(+), 18 deletions(-) diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index 208e9a522d0a..2f3ceb7972f4 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -65,6 +65,7 @@ describe('sentryMiddleware', () => { httpBodies: [], queryParams: true, genAI: { inputs: false, outputs: false }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 5, }), diff --git a/packages/core/src/integrations/supabase.ts b/packages/core/src/integrations/supabase.ts index 64865c123faf..64a75359bcbd 100644 --- a/packages/core/src/integrations/supabase.ts +++ b/packages/core/src/integrations/supabase.ts @@ -385,7 +385,8 @@ function instrumentPostgRESTFilterBuilder( } const client = getClient(); - const shouldSendData = _options.sendOperationData ?? client?.getDataCollectionOptions().userInfo === true; + const shouldSendData = + _options.sendOperationData ?? client?.getDataCollectionOptions().databaseQueryData === true; const bodyPayload = getMutationBodyPayloadForTelemetry(typedThis.body, body); // Adding operation to the beginning of the description if it's not a `select` operation @@ -563,7 +564,7 @@ interface SupabaseIntegrationOptions { * Whether to attach PostgREST query filters and mutation body payloads * to Sentry telemetry. * - * Falls back to `dataCollection.userInfo` when not set. + * Falls back to `dataCollection.databaseQueryData` when not set. * @default undefined */ sendOperationData?: boolean; diff --git a/packages/core/src/types/datacollection.ts b/packages/core/src/types/datacollection.ts index 8d4de6484544..ede0f1075942 100644 --- a/packages/core/src/types/datacollection.ts +++ b/packages/core/src/types/datacollection.ts @@ -59,6 +59,13 @@ export interface DataCollection { outputs?: boolean; }; + /** + * Include data associated with database queries, such as parameters provided to a query, statement values, or returned results. + * Structural metadata (database system, query summary, table) is always collected regardless of this setting. + * @default true + */ + databaseQueryData?: boolean; + /** * Capture local variable values in stack frames. * @default true diff --git a/packages/core/src/utils/data-collection/defaultPiiToCollectionOptions.ts b/packages/core/src/utils/data-collection/defaultPiiToCollectionOptions.ts index 361275f62f02..4ff59cfb6082 100644 --- a/packages/core/src/utils/data-collection/defaultPiiToCollectionOptions.ts +++ b/packages/core/src/utils/data-collection/defaultPiiToCollectionOptions.ts @@ -17,6 +17,7 @@ export function defaultPiiToCollectionOptions(sendDefaultPii?: boolean): Resolve httpBodies: ['incomingRequest', 'outgoingRequest', 'incomingResponse', 'outgoingResponse'], queryParams: true, genAI: { inputs: true, outputs: true }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 7, // default should be 5, but ContextLines integration uses 7 } @@ -27,6 +28,9 @@ export function defaultPiiToCollectionOptions(sendDefaultPii?: boolean): Resolve httpBodies: [], queryParams: { deny: PII_HEADER_SNIPPETS }, genAI: { inputs: false, outputs: false }, + // Database query values were only sent with `sendDefaultPii: true` (e.g. Supabase gated on it), + // so map the legacy "off" state to `false`. + databaseQueryData: false, stackFrameVariables: true, frameContextLines: 7, // default should be 5, but ContextLines integration uses 7 }; diff --git a/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts b/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts index 558bb05a468c..3d62eb821c64 100644 --- a/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts +++ b/packages/core/src/utils/data-collection/resolveDataCollectionOptions.ts @@ -8,6 +8,7 @@ const DEFAULTS: ResolvedDataCollection = { httpBodies: ['incomingRequest', 'outgoingRequest', 'incomingResponse', 'outgoingResponse'], queryParams: true, genAI: { inputs: true, outputs: true }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 5, }; @@ -47,6 +48,7 @@ export function resolveDataCollectionOptions(options: { inputs: dc.genAI?.inputs ?? base.genAI.inputs, outputs: dc.genAI?.outputs ?? base.genAI.outputs, }, + databaseQueryData: dc.databaseQueryData ?? base.databaseQueryData, stackFrameVariables: dc.stackFrameVariables ?? base.stackFrameVariables, frameContextLines: dc.frameContextLines ?? base.frameContextLines, }; diff --git a/packages/core/test/lib/integrations/mcp-server/testUtils.ts b/packages/core/test/lib/integrations/mcp-server/testUtils.ts index fce9bf7d2ae8..a134085819a0 100644 --- a/packages/core/test/lib/integrations/mcp-server/testUtils.ts +++ b/packages/core/test/lib/integrations/mcp-server/testUtils.ts @@ -18,6 +18,7 @@ export function createMockClient(userInfo = true, genAI?: { inputs: boolean; out httpBodies: [], queryParams: true, genAI: genAIOptions, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 5, }), diff --git a/packages/core/test/lib/integrations/supabase.test.ts b/packages/core/test/lib/integrations/supabase.test.ts index a1837daf460a..361af18f19fc 100644 --- a/packages/core/test/lib/integrations/supabase.test.ts +++ b/packages/core/test/lib/integrations/supabase.test.ts @@ -39,8 +39,8 @@ type CreateMockSupabaseClientOptions = { method?: string; url?: URL | string; body?: unknown; - /** When set, configures the mocked Sentry client's `dataCollection.userInfo`. Omit to leave `getClient` to the test file `beforeEach`. */ - dataCollectionUserInfo?: boolean; + /** When set, configures the mocked Sentry client's `dataCollection.databaseQueryData`. Omit to leave `getClient` to the test file `beforeEach`. */ + dataCollectionDatabaseQueryData?: boolean; }; const DEFAULT_MOCK_SUPABASE_REST_URL = 'https://example.supabase.co/rest/v1/todos'; @@ -53,9 +53,9 @@ const MOCK_SUPABASE_PII_SCENARIO: Pick ({ userInfo: options.dataCollectionUserInfo }), + getDataCollectionOptions: () => ({ databaseQueryData: options.dataCollectionDatabaseQueryData }), } as any); } @@ -237,10 +237,10 @@ describe('Supabase Integration', () => { vi.restoreAllMocks(); }); - it('omits db.query, db.body, and breadcrumb query/body when dataCollection.userInfo is false', async () => { + it('omits db.query, db.body, and breadcrumb query/body when dataCollection.databaseQueryData is false', async () => { const client = createMockSupabaseClient( { status: 200 }, - { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: false }, + { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: false }, ); instrumentSupabaseClient(client); @@ -259,10 +259,10 @@ describe('Supabase Integration', () => { expect(breadcrumb).not.toHaveProperty('data'); }); - it('includes db.query, db.body, and breadcrumb query/body when dataCollection.userInfo is true', async () => { + it('includes db.query, db.body, and breadcrumb query/body when dataCollection.databaseQueryData is true', async () => { const client = createMockSupabaseClient( { status: 200 }, - { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: true }, + { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: true }, ); instrumentSupabaseClient(client); @@ -290,10 +290,10 @@ describe('Supabase Integration', () => { ); }); - it('includes data when sendOperationData option is set, regardless of dataCollection.userInfo', async () => { + it('includes data when sendOperationData option is set, regardless of dataCollection.databaseQueryData', async () => { const client = createMockSupabaseClient( { status: 200 }, - { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: false }, + { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: false }, ); instrumentSupabaseClient(client, { sendOperationData: true }); @@ -312,10 +312,10 @@ describe('Supabase Integration', () => { ); }); - it('sendOperationData: false takes precedence over dataCollection.userInfo: true', async () => { + it('sendOperationData: false takes precedence over dataCollection.databaseQueryData: true', async () => { const client = createMockSupabaseClient( { status: 200 }, - { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: true }, + { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: true }, ); instrumentSupabaseClient(client, { sendOperationData: false }); @@ -330,7 +330,7 @@ describe('Supabase Integration', () => { expect(spanOptions.attributes['db.body']).toBeUndefined(); }); - it('includes data when legacy sendDefaultPii: true is bridged to dataCollection.userInfo', async () => { + it('includes data when legacy sendDefaultPii: true is bridged to dataCollection.databaseQueryData', async () => { const resolved = resolveDataCollectionOptions({ sendDefaultPii: true }); currentScopesMocks.getClient.mockReturnValue({ getDataCollectionOptions: () => resolved, @@ -378,7 +378,7 @@ describe('Supabase Integration', () => { it('omits supabase error context query/body when data collection is off', async () => { const client = createMockSupabaseClient( { status: 400, error: { message: 'Bad request', code: '400' } }, - { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionUserInfo: false }, + { ...MOCK_SUPABASE_PII_SCENARIO, dataCollectionDatabaseQueryData: false }, ); instrumentSupabaseClient(client); @@ -417,7 +417,7 @@ describe('Supabase Integration', () => { method: 'POST', url: 'https://example.supabase.co/rest/v1/todos?columns=', body: [{ title: 'Test Todo' }], - dataCollectionUserInfo: true, + dataCollectionDatabaseQueryData: true, }, ); instrumentSupabaseClient(client); diff --git a/packages/core/test/lib/utils/data-collection/defaultPiiToCollectionOptions.test.ts b/packages/core/test/lib/utils/data-collection/defaultPiiToCollectionOptions.test.ts index fcd9463e7f0f..c492bb00af1c 100644 --- a/packages/core/test/lib/utils/data-collection/defaultPiiToCollectionOptions.test.ts +++ b/packages/core/test/lib/utils/data-collection/defaultPiiToCollectionOptions.test.ts @@ -10,6 +10,7 @@ describe('defaultPiiToCollectionOptions', () => { httpBodies: ['incomingRequest', 'outgoingRequest', 'incomingResponse', 'outgoingResponse'], queryParams: true, genAI: { inputs: true, outputs: true }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 7, }); @@ -26,6 +27,7 @@ describe('defaultPiiToCollectionOptions', () => { httpBodies: [], queryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, genAI: { inputs: false, outputs: false }, + databaseQueryData: false, stackFrameVariables: true, frameContextLines: 7, }); @@ -42,6 +44,7 @@ describe('defaultPiiToCollectionOptions', () => { httpBodies: [], queryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, genAI: { inputs: false, outputs: false }, + databaseQueryData: false, stackFrameVariables: true, frameContextLines: 7, }); @@ -58,6 +61,7 @@ describe('defaultPiiToCollectionOptions', () => { httpBodies: [], queryParams: { deny: ['forwarded', '-ip', 'remote-', 'via', '-user'] }, genAI: { inputs: false, outputs: false }, + databaseQueryData: false, stackFrameVariables: true, frameContextLines: 7, }); diff --git a/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts b/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts index 2cd76e599a05..2825dab7e34f 100644 --- a/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts +++ b/packages/core/test/lib/utils/data-collection/resolveDataCollectionOptions.test.ts @@ -9,6 +9,7 @@ describe('resolveDataCollectionOptions', () => { httpBodies: ['incomingRequest', 'outgoingRequest', 'incomingResponse', 'outgoingResponse'], queryParams: true, genAI: { inputs: true, outputs: true }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 5, }; @@ -21,6 +22,7 @@ describe('resolveDataCollectionOptions', () => { expect(result.userInfo).toBe(false); expect(result.httpBodies).toEqual([]); expect(result.genAI).toEqual({ inputs: false, outputs: false }); + expect(result.databaseQueryData).toBe(false); expect(result.stackFrameVariables).toBe(true); expect(result.frameContextLines).toBe(7); }); @@ -46,6 +48,7 @@ describe('resolveDataCollectionOptions', () => { expect(result.httpBodies).toEqual(['incomingRequest', 'outgoingRequest', 'incomingResponse', 'outgoingResponse']); expect(result.queryParams).toBe(true); expect(result.genAI).toEqual({ inputs: true, outputs: true }); + expect(result.databaseQueryData).toBe(true); }); it('bridges sendDefaultPii: false to restrictive config', () => { @@ -54,6 +57,7 @@ describe('resolveDataCollectionOptions', () => { expect(result.userInfo).toBe(false); expect(result.httpBodies).toEqual([]); expect(result.genAI).toEqual({ inputs: false, outputs: false }); + expect(result.databaseQueryData).toBe(false); }); }); @@ -69,6 +73,7 @@ describe('resolveDataCollectionOptions', () => { // Remaining fields use spec defaults (not sendDefaultPii bridge) expect(result.httpBodies).toEqual(['incomingRequest', 'outgoingRequest', 'incomingResponse', 'outgoingResponse']); expect(result.genAI).toEqual({ inputs: true, outputs: true }); + expect(result.databaseQueryData).toBe(true); }); }); @@ -88,6 +93,7 @@ describe('resolveDataCollectionOptions', () => { expect(result.httpHeaders).toEqual({ request: true, response: true }); expect(result.queryParams).toBe(true); expect(result.genAI).toEqual({ inputs: true, outputs: true }); + expect(result.databaseQueryData).toBe(true); expect(result.stackFrameVariables).toBe(true); expect(result.frameContextLines).toBe(5); }); @@ -133,13 +139,23 @@ describe('resolveDataCollectionOptions', () => { expect(result.queryParams).toBe(false); }); + + it('supports turning off database query data', () => { + const result = resolveDataCollectionOptions({ + dataCollection: { + databaseQueryData: false, + }, + }); + + expect(result.databaseQueryData).toBe(false); + }); }); describe('return type completeness', () => { it('always returns all fields', () => { const result = resolveDataCollectionOptions({}); - expect(Object.keys(result)).toHaveLength(8); + expect(Object.keys(result)).toHaveLength(9); expect(result).toHaveProperty('userInfo'); expect(result).toHaveProperty('cookies'); expect(result).toHaveProperty('httpHeaders'); @@ -150,6 +166,7 @@ describe('resolveDataCollectionOptions', () => { expect(result).toHaveProperty('genAI'); expect(result).toHaveProperty('genAI.inputs'); expect(result).toHaveProperty('genAI.outputs'); + expect(result).toHaveProperty('databaseQueryData'); expect(result).toHaveProperty('stackFrameVariables'); expect(result).toHaveProperty('frameContextLines'); }); diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 609dde8a05ab..41e25fe2f34c 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -875,6 +875,7 @@ describe('request utils', () => { httpBodies: [], queryParams: true, genAI: { inputs: true, outputs: true }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 5, }; diff --git a/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts b/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts index d022eef4492f..43cd48c2846c 100644 --- a/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts +++ b/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts @@ -48,6 +48,7 @@ describe('wrapMiddlewareHandlerWithSentry', () => { httpBodies: [], queryParams: true, genAI: { inputs: false, outputs: false }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 5, }), diff --git a/packages/remix/test/server/errors.test.ts b/packages/remix/test/server/errors.test.ts index a3faf2dfdfdc..25da70f54a55 100644 --- a/packages/remix/test/server/errors.test.ts +++ b/packages/remix/test/server/errors.test.ts @@ -18,6 +18,7 @@ function createMockClient(httpBodies: string[] = []): Client { httpBodies, queryParams: true, genAI: { inputs: true, outputs: true }, + databaseQueryData: true, stackFrameVariables: true, frameContextLines: 5, }), From 3fafe1beec30869d5da4af30ca0670ad6dfd6cab Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 15 Jul 2026 15:01:56 +0200 Subject: [PATCH 2/2] reword --- packages/core/src/types/datacollection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/src/types/datacollection.ts b/packages/core/src/types/datacollection.ts index e9ef57d793db..88af5b354ebd 100644 --- a/packages/core/src/types/datacollection.ts +++ b/packages/core/src/types/datacollection.ts @@ -79,8 +79,9 @@ export interface DataCollection { }; /** - * Include data associated with database queries, such as parameters provided to a query, statement values, or returned results. - * Structural metadata (database system, query summary, table) is always collected regardless of this setting. + * Include data associated with database queries. This controls collection of query parameters, inline literal values within query text, mutation/request bodies, and returned result data. + * + * Sanitized or parameterized DB statements (`db.query.text`) are **not** controlled by this property. Structural metadata such as the database system, query summary, operation name, or the table being acted upon is also **always** collected. * @default true */ databaseQueryData?: boolean;