Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 1 addition & 4 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,7 @@ async function instrumentRequestStartHttpServerSpan(
// This is here for backwards compatibility, we used to set this here before
method,
url: stripUrlQueryAndFragment(ctx.url.href),
...httpHeadersToSpanAttributes(
winterCGHeadersToDict(request.headers),
getClient()?.getOptions().sendDefaultPii ?? false,
),
...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)),
};

if (parametrizedRoute) {
Expand Down
3 changes: 1 addition & 2 deletions packages/bun/src/integrations/bunserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,7 @@ function wrapRequestHandler<T extends RouteHandler = RouteHandler>(
}

const client = getClient();
const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false;
Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), sendDefaultPii));
Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON()));

isolationScope.setSDKProcessingMetadata({
normalizedRequest: {
Expand Down
3 changes: 1 addition & 2 deletions packages/cloudflare/src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ export function wrapRequestHandler(
attributes['user_agent.original'] = userAgentHeader;
}

const sendDefaultPii = options.sendDefaultPii ?? false;
Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), sendDefaultPii));
Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)));

attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';

Expand Down
39 changes: 25 additions & 14 deletions packages/core/src/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,19 @@ function getAbsoluteUrl({
}

// "-user" because otherwise it would match "user-agent"
const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user', 'password', 'key'];
const SENSITIVE_HEADER_SNIPPETS = [
'auth',
'token',
'secret',
'cookie',
'-user',
'password',
'key',
'jwt',
'bearer',
'sso',
'saml',
];

/**
* Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions.
Expand All @@ -140,26 +152,25 @@ const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user',
*/
export function httpHeadersToSpanAttributes(
headers: Record<string, string | string[] | undefined>,
sendDefaultPii: boolean = false,
): Record<string, string> {
const spanAttributes: Record<string, string> = {};

try {
Comment on lines 153 to 158

This comment was marked as resolved.

Object.entries(headers).forEach(([key, value]) => {
if (value !== undefined) {
const lowerCasedKey = key.toLowerCase();

if (!sendDefaultPii && SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))) {
return;
}
if (value == null) {
return;
}

const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`;
const lowerCasedKey = key.toLowerCase();
const isSensitive = SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet));
const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`;

if (Array.isArray(value)) {
spanAttributes[normalizedKey] = value.map(v => (v !== null && v !== undefined ? String(v) : v)).join(';');
} else if (typeof value === 'string') {
spanAttributes[normalizedKey] = value;
}
if (isSensitive) {
spanAttributes[normalizedKey] = '[Filtered]';
} else if (Array.isArray(value)) {
spanAttributes[normalizedKey] = value.map(v => (v != null ? String(v) : v)).join(';');
} else if (typeof value === 'string') {
spanAttributes[normalizedKey] = value;
}
});
} catch {
Expand Down
76 changes: 33 additions & 43 deletions packages/core/test/lib/utils/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,61 +613,25 @@ describe('request utils', () => {
});

describe('PII filtering', () => {
it('filters out sensitive headers when sendDefaultPii is false (default)', () => {
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'test-agent',
Authorization: 'Bearer secret-token',
Cookie: 'session=abc123',
'X-API-Key': 'api-key-123',
'X-Auth-Token': 'auth-token-456',
};

const result = httpHeadersToSpanAttributes(headers, false);

expect(result).toEqual({
'http.request.header.content_type': 'application/json',
'http.request.header.user_agent': 'test-agent',
// Sensitive headers should be filtered out
});
});

it('includes sensitive headers when sendDefaultPii is true', () => {
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'test-agent',
Authorization: 'Bearer secret-token',
Cookie: 'session=abc123',
'X-API-Key': 'api-key-123',
};

const result = httpHeadersToSpanAttributes(headers, true);

expect(result).toEqual({
'http.request.header.content_type': 'application/json',
'http.request.header.user_agent': 'test-agent',
'http.request.header.authorization': 'Bearer secret-token',
'http.request.header.cookie': 'session=abc123',
'http.request.header.x_api_key': 'api-key-123',
});
});

it('filters sensitive headers case-insensitively', () => {
const headers = {
AUTHORIZATION: 'Bearer secret-token',
Cookie: 'session=abc123',
'x-api-key': 'key-123',
'x-aPi-kEy': 'key-123',
'Content-Type': 'application/json',
};

const result = httpHeadersToSpanAttributes(headers, false);
const result = httpHeadersToSpanAttributes(headers);

expect(result).toEqual({
'http.request.header.content_type': 'application/json',
'http.request.header.cookie': '[Filtered]',
'http.request.header.x_api_key': '[Filtered]',
'http.request.header.authorization': '[Filtered]',
});
});

it('filters comprehensive list of sensitive headers', () => {
it('filters comprehensive list of sensitive headers when $description', () => {
const headers = {
'Content-Type': 'application/json',
'User-Agent': 'test-agent',
Expand All @@ -692,15 +656,41 @@ describe('request utils', () => {
'X-Private-Key': 'private',
'X-Forwarded-user': 'user',
'X-Forwarded-authorization': 'auth',
'x-jwt-token': 'jwt',
'x-bearer-token': 'bearer',
'x-sso-token': 'sso',
'x-saml-token': 'saml',
};

const result = httpHeadersToSpanAttributes(headers, false);
const result = httpHeadersToSpanAttributes(headers);

// Sensitive headers are always included and redacted
expect(result).toEqual({
'http.request.header.content_type': 'application/json',
'http.request.header.user_agent': 'test-agent',
'http.request.header.accept': 'application/json',
'http.request.header.host': 'example.com',
'http.request.header.authorization': '[Filtered]',
'http.request.header.cookie': '[Filtered]',
'http.request.header.set_cookie': '[Filtered]',
'http.request.header.x_api_key': '[Filtered]',
'http.request.header.x_auth_token': '[Filtered]',
'http.request.header.x_secret': '[Filtered]',
'http.request.header.x_secret_key': '[Filtered]',
'http.request.header.www_authenticate': '[Filtered]',
'http.request.header.proxy_authorization': '[Filtered]',
'http.request.header.x_access_token': '[Filtered]',
'http.request.header.x_csrf_token': '[Filtered]',
'http.request.header.x_xsrf_token': '[Filtered]',
'http.request.header.x_session_token': '[Filtered]',
'http.request.header.x_password': '[Filtered]',
'http.request.header.x_private_key': '[Filtered]',
'http.request.header.x_forwarded_user': '[Filtered]',
'http.request.header.x_forwarded_authorization': '[Filtered]',
'http.request.header.x_jwt_token': '[Filtered]',
'http.request.header.x_bearer_token': '[Filtered]',
'http.request.header.x_sso_token': '[Filtered]',
'http.request.header.x_saml_token': '[Filtered]',
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/src/common/utils/addHeadersAsAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function addHeadersAsAttributes(
? winterCGHeadersToDict(headers as Headers)
: headers;

const headerAttributes = httpHeadersToSpanAttributes(headersDict, sendDefaultPii);
const headerAttributes = httpHeadersToSpanAttributes(headersDict);

if (span) {
span.setAttributes(headerAttributes);
Expand Down
Loading