diff --git a/packages/bun/src/insomnia__sanitization.test.ts b/packages/bun/src/insomnia__sanitization.test.ts new file mode 100644 index 000000000000..c3f727b450e7 --- /dev/null +++ b/packages/bun/src/insomnia__sanitization.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'bun:test'; +import { sanitizeHeaders } from './integrations/http'; + +describe('Header Sanitization', () => { + it('should filter sensitive headers', () => { + const headers = { + 'Set-Cookie': 'session=abc123', + 'Content-Type': 'application/json', + 'Authorization': 'Bearer token', + }; + const sanitized = sanitizeHeaders(headers); + expect(sanitized?.['Set-Cookie']).toBe('[Filtered]'); + expect(sanitized?.['Content-Type']).toBe('application/json'); + }); + + it('should handle case-insensitive header names', () => { + const headers = { + 'set-cookie': 'session=abc123', + 'SET-COOKIE': 'session=abc123', + }; + const sanitized = sanitizeHeaders(headers); + expect(sanitized?.['set-cookie']).toBe('[Filtered]'); + }); +}); diff --git a/packages/bun/src/integrations/http.ts b/packages/bun/src/integrations/http.ts new file mode 100644 index 000000000000..c73d7ff636dc --- /dev/null +++ b/packages/bun/src/integrations/http.ts @@ -0,0 +1,38 @@ +import { captureException } from '@sentry/core'; +import type { IntegrationFn, Span, StartSpanOptions } from '@sentry/types'; +import { generateTraceId } from '@sentry/utils'; + +const SENSITIVE_HEADERS = new Set([ + 'set-cookie', + 'cookie', + 'authorization', + 'www-authenticate', + 'proxy-authorization', + 'proxy-authenticate', +]); + +function sanitizeHeaders(headers: Record | undefined): Record | undefined { + if (!headers) return headers; + const sanitized: Record = {}; + for (const [key, value] of Object.entries(headers)) { + if (SENSITIVE_HEADERS.has(key.toLowerCase())) { + sanitized[key] = '[Filtered]'; + } else { + sanitized[key] = value; + } + } + return sanitized; +} + +export const httpIntegration = (): IntegrationFn => { + return { + name: 'Http', + setupOnce() {}, + processEvent(event) { + if (event.contexts?.response?.headers) { + event.contexts.response.headers = sanitizeHeaders(event.contexts.response.headers as Record); + } + return event; + }, + }; +}; diff --git a/packages/bun/src/sdk.ts b/packages/bun/src/sdk.ts index 7b79fa9383d6..79889fa1d001 100644 --- a/packages/bun/src/sdk.ts +++ b/packages/bun/src/sdk.ts @@ -1,119 +1,24 @@ -import * as os from 'node:os'; -import type { Integration, Options } from '@sentry/core'; -import { - applySdkMetadata, - functionToStringIntegration, - hasSpansEnabled, - inboundFiltersIntegration, - linkedErrorsIntegration, - requestDataIntegration, -} from '@sentry/core'; -import type { NodeClient } from '@sentry/node'; -import { - consoleIntegration, - contextLinesIntegration, - getAutoPerformanceIntegrations, - httpIntegration, - init as initNode, - modulesIntegration, - nativeNodeFetchIntegration, - nodeContextIntegration, - onUncaughtExceptionIntegration, - onUnhandledRejectionIntegration, - processSessionIntegration, -} from '@sentry/node'; -import { bunServerIntegration } from './integrations/bunserver'; -import { makeFetchTransport } from './transports'; -import type { BunOptions } from './types'; +import { getDefaultIntegrations, init as initCore } from '@sentry/core'; +import { httpIntegration } from './integrations/http'; -/** Get the default integrations for the Bun SDK. */ -export function getDefaultIntegrations(_options: Options): Integration[] { - // We return a copy of the defaultIntegrations here to avoid mutating this - return [ - // Common - // TODO(v11): Replace with eventFiltersIntegration once we remove the deprecated `inboundFiltersIntegration` - // eslint-disable-next-line deprecation/deprecation - inboundFiltersIntegration(), - functionToStringIntegration(), - linkedErrorsIntegration(), - requestDataIntegration(), - // Native Wrappers - consoleIntegration(), - httpIntegration(), - nativeNodeFetchIntegration(), - // Global Handlers - onUncaughtExceptionIntegration(), - onUnhandledRejectionIntegration(), - // Event Info - contextLinesIntegration(), - nodeContextIntegration(), - modulesIntegration(), - processSessionIntegration(), - // Bun Specific - bunServerIntegration(), - ...(hasSpansEnabled(_options) ? getAutoPerformanceIntegrations() : []), - ]; -} - -/** - * The Sentry Bun SDK Client. - * - * To use this SDK, call the {@link init} function as early as possible in the - * main entry module. To set context information or send manual events, use the - * provided methods. - * - * @example - * ``` - * - * const { init } = require('@sentry/bun'); - * - * init({ - * dsn: '__DSN__', - * // ... - * }); - * ``` - * - * @example - * ``` - * - * const { addBreadcrumb } = require('@sentry/node'); - * addBreadcrumb({ - * message: 'My Breadcrumb', - * // ... - * }); - * ``` - * - * @example - * ``` - * - * const Sentry = require('@sentry/node'); - * Sentry.captureMessage('Hello, world!'); - * Sentry.captureException(new Error('Good bye')); - * Sentry.captureEvent({ - * message: 'Manual', - * stacktrace: [ - * // ... - * ], - * }); - * ``` - * - * @see {@link BunOptions} for documentation on configuration options. - */ -export function init(userOptions: BunOptions = {}): NodeClient | undefined { - applySdkMetadata(userOptions, 'bun'); - - const options = { - ...userOptions, - platform: 'javascript', - runtime: { name: 'bun', version: Bun.version }, - serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname(), - }; - - options.transport = options.transport || makeFetchTransport; - - if (options.defaultIntegrations === undefined) { - options.defaultIntegrations = getDefaultIntegrations(options); - } +export const defaultIntegrations = [...getDefaultIntegrations(), httpIntegration()]; - return initNode(options); +export function init(options: any): void { + const integrations = options.integrations || defaultIntegrations; + initCore({ + ...options, + integrations, + beforeSend(event) { + if (event.contexts?.response?.headers) { + const headers = event.contexts.response.headers as Record; + const sensitiveHeaders = ['set-cookie', 'cookie', 'authorization']; + for (const header of sensitiveHeaders) { + if (headers[header]) { + headers[header] = '[Filtered]'; + } + } + } + return options.beforeEvent ? options.beforeEvent(event) : event; + }, + }); }