import { describe, expect, it } from 'vitest' import type { ErrorEvent as SentryErrorEvent } from '@sentry/vue' import { scrubEvent } from '../scrubber' function makeEvent(overrides: Partial = {}): SentryErrorEvent { return { type: undefined, ...overrides, } as SentryErrorEvent } describe('scrubEvent', () => { describe('request body', () => { it('scrubs password in request body', () => { const event = makeEvent({ request: { data: { email: 'a@b.test', password: 'p@ss' } } }) const result = scrubEvent(event) as SentryErrorEvent const data = result.request?.data as Record expect(data.password).toBe('[scrubbed]') expect(data.email).toBe('a@b.test') }) it('scrubs password_confirmation', () => { const event = makeEvent({ request: { data: { password_confirmation: 'x', current_password: 'y' } } }) const result = scrubEvent(event) as SentryErrorEvent const data = result.request?.data as Record expect(data.password_confirmation).toBe('[scrubbed]') expect(data.current_password).toBe('[scrubbed]') }) it('scrubs every SENSITIVE_BODY_KEY at top level', () => { const allKeys = { password: 'a', token: 'b', api_key: 'c', secret: 'd', webhook_secret: 'e', dsn: 'f', signature: 'g', authorization: 'h', cookie: 'i', bearer: 'j', iban: 'k', bic: 'l', passport_number: 'm', bsn: 'n', } const event = makeEvent({ request: { data: allKeys } }) const result = scrubEvent(event) as SentryErrorEvent const data = result.request?.data as Record for (const key of Object.keys(allKeys)) expect(data[key]).toBe('[scrubbed]') }) it('scrubs sensitive keys at nested levels (recursive)', () => { const event = makeEvent({ request: { data: { profile: { address: { iban: 'NL91...', street: 'Damrak 1' } } }, }, }) const result = scrubEvent(event) as SentryErrorEvent const data = result.request?.data as Record>> expect(data.profile.address.iban).toBe('[scrubbed]') expect(data.profile.address.street).toBe('Damrak 1') }) it('replaces form_values payload wholesale', () => { const event = makeEvent({ request: { data: { form_values: { email: 'x@y.com', dietary: 'vegan' } } }, }) const result = scrubEvent(event) as SentryErrorEvent const data = result.request?.data as Record expect(data.form_values).toBe('[scrubbed_form_values]') const serialised = JSON.stringify(data) expect(serialised).not.toContain('x@y.com') expect(serialised).not.toContain('vegan') }) it('does not leak email or other non-sensitive keys', () => { const event = makeEvent({ request: { data: { email: 'x@y.com', name: 'Bob' } } }) const result = scrubEvent(event) as SentryErrorEvent const data = result.request?.data as Record expect(data.email).toBe('x@y.com') expect(data.name).toBe('Bob') }) it('hits max_depth guard at depth 11', () => { let deep: unknown = { v: 'leaf' } for (let i = 0; i < 15; i++) deep = { nest: deep } const event = makeEvent({ request: { data: deep as Record } }) const result = scrubEvent(event) as SentryErrorEvent const serialised = JSON.stringify(result.request?.data) expect(serialised).toContain('[max_depth]') }) it('returns event unchanged when request is undefined', () => { const event = makeEvent() const result = scrubEvent(event) expect(result).toBe(event) expect(result?.request).toBeUndefined() }) it('returns event unchanged when request.data is null', () => { const event = makeEvent({ request: { data: null as unknown as undefined } }) const result = scrubEvent(event) as SentryErrorEvent expect(result.request?.data).toBeNull() }) }) describe('headers', () => { it('scrubs Authorization header', () => { const event = makeEvent({ request: { headers: { Authorization: 'Bearer abc' } } }) const result = scrubEvent(event) as SentryErrorEvent expect((result.request?.headers as Record).Authorization).toBe('[scrubbed]') }) it('scrubs Cookie header', () => { const event = makeEvent({ request: { headers: { Cookie: 'sess=abc' } } }) const result = scrubEvent(event) as SentryErrorEvent expect((result.request?.headers as Record).Cookie).toBe('[scrubbed]') }) it('scrubs case-insensitive header names', () => { const event = makeEvent({ request: { headers: { 'X-API-KEY': 'k', 'x-impersonation-token': 't' } } }) const result = scrubEvent(event) as SentryErrorEvent const headers = result.request?.headers as Record expect(headers['X-API-KEY']).toBe('[scrubbed]') expect(headers['x-impersonation-token']).toBe('[scrubbed]') }) }) describe('query string', () => { it('scrubs token query param', () => { const event = makeEvent({ request: { query_string: 'token=abc&keep=me' } }) const result = scrubEvent(event) as SentryErrorEvent const qs = result.request?.query_string as string expect(qs).toContain('token=%5Bscrubbed%5D') expect(qs).toContain('keep=me') }) it('scrubs api_key query param', () => { const event = makeEvent({ request: { query_string: 'api_key=xyz&page=2' } }) const result = scrubEvent(event) as SentryErrorEvent const qs = result.request?.query_string as string expect(qs).toContain('api_key=%5Bscrubbed%5D') expect(qs).toContain('page=2') }) it('preserves non-sensitive query params unchanged', () => { const event = makeEvent({ request: { query_string: 'page=2&sort=name' } }) const result = scrubEvent(event) as SentryErrorEvent const qs = result.request?.query_string as string expect(qs).toBe('page=2&sort=name') }) }) describe('cookies + storage', () => { it('scrubs cookies wholesale', () => { const event = makeEvent({ request: { cookies: { sess: 'abc', tracking: 'xyz' } } }) const result = scrubEvent(event) as SentryErrorEvent expect(result.request?.cookies).toEqual({ scrubbed: '[scrubbed]' }) }) it('strips storage context if present (RFC §3.7 frontend point 2)', () => { const event = makeEvent({ contexts: { storage: { local: { token: 'abc' } } } as unknown as SentryErrorEvent['contexts'], }) const result = scrubEvent(event) as SentryErrorEvent expect(result.contexts).not.toHaveProperty('storage') }) it('strips user.cookies if present (RFC §3.7 frontend point 1)', () => { const event = makeEvent({ user: { id: 'ulid', cookies: 'session=...' } as unknown as SentryErrorEvent['user'], }) const result = scrubEvent(event) as SentryErrorEvent expect(result.user).not.toHaveProperty('cookies') expect(result.user?.id).toBe('ulid') }) }) })