Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/bun/src/insomnia__sanitization.test.ts
Original file line number Diff line number Diff line change
@@ -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]');
});
});
38 changes: 38 additions & 0 deletions packages/bun/src/integrations/http.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | string[] | undefined> | undefined): Record<string, string | string[] | undefined> | undefined {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sanitizeHeaders not exported but imported by test

Medium Severity

The sanitizeHeaders function in http.ts is declared without the export keyword, but the test file insomnia__sanitization.test.ts imports it as a named export. This means all tests for header sanitization will fail, providing no verification for the security fix this PR introduces.

Additional Locations (1)
Fix in Cursor Fix in Web

if (!headers) return headers;
const sanitized: Record<string, string | string[] | undefined> = {};
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',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Integration name collision replaces real HTTP instrumentation

High Severity

The new sanitization integration uses name: 'Http', which is identical to the existing node httpIntegration's INTEGRATION_NAME. Sentry's filterDuplicates deduplicates integrations by name, keeping the last one. When defaultIntegrations combines getDefaultIntegrations() (which includes the real Http integration) with this custom httpIntegration(), the sanitization-only stub replaces the real HTTP instrumentation integration, losing all HTTP request/response tracking.

Additional Locations (1)
Fix in Cursor Fix in Web

setupOnce() {},
processEvent(event) {
if (event.contexts?.response?.headers) {
event.contexts.response.headers = sanitizeHeaders(event.contexts.response.headers as Record<string, string | string[] | undefined>);
}
return event;
},
};
};
137 changes: 21 additions & 116 deletions packages/bun/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | string[] | undefined>;
const sensitiveHeaders = ['set-cookie', 'cookie', 'authorization'];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent sensitive header lists between sanitization paths

Medium Severity

Two separate sanitization mechanisms exist with different header lists. The SENSITIVE_HEADERS set in http.ts includes six headers (www-authenticate, proxy-authorization, proxy-authenticate in addition to the base three), while the sensitiveHeaders array in sdk.ts beforeSend only lists three (set-cookie, cookie, authorization). This inconsistency means sensitive authentication-related headers may not be consistently sanitized depending on which path processes the event.

Additional Locations (1)
Fix in Cursor Fix in Web

for (const header of sensitiveHeaders) {
if (headers[header]) {
headers[header] = '[Filtered]';
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case-sensitive header matching misses mixed-case headers

High Severity

The beforeSend sanitization in sdk.ts checks for headers using exact lowercase keys (e.g., set-cookie, authorization) via direct property access. HTTP headers commonly use mixed case like Set-Cookie or Authorization, so these won't match and sensitive values will leak unsanitized into events. The sanitizeHeaders function in http.ts correctly uses key.toLowerCase(), but the beforeSend logic does not.

Fix in Cursor Fix in Web

}
return options.beforeEvent ? options.beforeEvent(event) : event;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The init function incorrectly references options.beforeEvent instead of the correct options.beforeSend, causing user-provided beforeSend callbacks to be silently ignored.
Severity: HIGH

Suggested Fix

In packages/bun/src/sdk.ts on line 21, change the reference from options.beforeEvent to options.beforeSend to correctly chain to the user-provided callback.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/bun/src/sdk.ts#L21

Potential issue: In the `init` function, a custom `beforeSend` handler is defined to
sanitize headers. This handler attempts to chain to a user-provided callback by checking
for `options.beforeEvent`. However, the correct Sentry SDK option is
`options.beforeSend`. As `options.beforeEvent` will always be undefined, any
`beforeSend` callback provided by the user during initialization will be silently
ignored, preventing them from modifying or filtering events before they are sent.

Did we get this right? 👍 / 👎 to inform future reviews.

},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User's beforeSend callback silently overwritten

High Severity

The init function defines its own beforeSend after spreading ...options, which silently overwrites any user-provided beforeSend callback. The code then attempts to chain to options.beforeEvent, but beforeEvent is not a valid Sentry option — the correct property name is beforeSend. Since the user's original beforeSend has already been replaced, it can never be invoked, effectively dropping user-defined event filtering/modification entirely.

Fix in Cursor Fix in Web

});
}