Skip to content

Commit a7304ce

Browse files
committed
fix(core): Always include sensitive headers and redact content regardless of sendDefaultPii
1 parent 108b027 commit a7304ce

File tree

6 files changed

+62
-66
lines changed

6 files changed

+62
-66
lines changed

packages/astro/src/server/middleware.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,7 @@ async function instrumentRequestStartHttpServerSpan(
219219
// This is here for backwards compatibility, we used to set this here before
220220
method,
221221
url: stripUrlQueryAndFragment(ctx.url.href),
222-
...httpHeadersToSpanAttributes(
223-
winterCGHeadersToDict(request.headers),
224-
getClient()?.getOptions().sendDefaultPii ?? false,
225-
),
222+
...httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)),
226223
};
227224

228225
if (parametrizedRoute) {

packages/bun/src/integrations/bunserver.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,7 @@ function wrapRequestHandler<T extends RouteHandler = RouteHandler>(
208208
}
209209

210210
const client = getClient();
211-
const sendDefaultPii = client?.getOptions().sendDefaultPii ?? false;
212-
Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON(), sendDefaultPii));
211+
Object.assign(attributes, httpHeadersToSpanAttributes(request.headers.toJSON()));
213212

214213
isolationScope.setSDKProcessingMetadata({
215214
normalizedRequest: {

packages/cloudflare/src/request.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ export function wrapRequestHandler(
6666
attributes['user_agent.original'] = userAgentHeader;
6767
}
6868

69-
const sendDefaultPii = options.sendDefaultPii ?? false;
70-
Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers), sendDefaultPii));
69+
Object.assign(attributes, httpHeadersToSpanAttributes(winterCGHeadersToDict(request.headers)));
7170

7271
attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';
7372

packages/core/src/utils/request.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,19 @@ function getAbsoluteUrl({
129129
}
130130

131131
// "-user" because otherwise it would match "user-agent"
132-
const SENSITIVE_HEADER_SNIPPETS = ['auth', 'token', 'secret', 'cookie', '-user', 'password', 'key'];
132+
const SENSITIVE_HEADER_SNIPPETS = [
133+
'auth',
134+
'token',
135+
'secret',
136+
'cookie',
137+
'-user',
138+
'password',
139+
'key',
140+
'jwt',
141+
'bearer',
142+
'sso',
143+
'saml',
144+
];
133145

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

147158
try {
148159
Object.entries(headers).forEach(([key, value]) => {
149-
if (value !== undefined) {
150-
const lowerCasedKey = key.toLowerCase();
151-
152-
if (!sendDefaultPii && SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))) {
153-
return;
154-
}
160+
if (value == null) {
161+
return;
162+
}
155163

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

158-
if (Array.isArray(value)) {
159-
spanAttributes[normalizedKey] = value.map(v => (v !== null && v !== undefined ? String(v) : v)).join(';');
160-
} else if (typeof value === 'string') {
161-
spanAttributes[normalizedKey] = value;
162-
}
168+
if (isSensitive) {
169+
spanAttributes[normalizedKey] = '[Filtered]';
170+
} else if (Array.isArray(value)) {
171+
spanAttributes[normalizedKey] = value.map(v => (v != null ? String(v) : v)).join(';');
172+
} else if (typeof value === 'string') {
173+
spanAttributes[normalizedKey] = value;
163174
}
164175
});
165176
} catch {

packages/core/test/lib/utils/request.test.ts

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -613,61 +613,25 @@ describe('request utils', () => {
613613
});
614614

615615
describe('PII filtering', () => {
616-
it('filters out sensitive headers when sendDefaultPii is false (default)', () => {
617-
const headers = {
618-
'Content-Type': 'application/json',
619-
'User-Agent': 'test-agent',
620-
Authorization: 'Bearer secret-token',
621-
Cookie: 'session=abc123',
622-
'X-API-Key': 'api-key-123',
623-
'X-Auth-Token': 'auth-token-456',
624-
};
625-
626-
const result = httpHeadersToSpanAttributes(headers, false);
627-
628-
expect(result).toEqual({
629-
'http.request.header.content_type': 'application/json',
630-
'http.request.header.user_agent': 'test-agent',
631-
// Sensitive headers should be filtered out
632-
});
633-
});
634-
635-
it('includes sensitive headers when sendDefaultPii is true', () => {
636-
const headers = {
637-
'Content-Type': 'application/json',
638-
'User-Agent': 'test-agent',
639-
Authorization: 'Bearer secret-token',
640-
Cookie: 'session=abc123',
641-
'X-API-Key': 'api-key-123',
642-
};
643-
644-
const result = httpHeadersToSpanAttributes(headers, true);
645-
646-
expect(result).toEqual({
647-
'http.request.header.content_type': 'application/json',
648-
'http.request.header.user_agent': 'test-agent',
649-
'http.request.header.authorization': 'Bearer secret-token',
650-
'http.request.header.cookie': 'session=abc123',
651-
'http.request.header.x_api_key': 'api-key-123',
652-
});
653-
});
654-
655616
it('filters sensitive headers case-insensitively', () => {
656617
const headers = {
657618
AUTHORIZATION: 'Bearer secret-token',
658619
Cookie: 'session=abc123',
659-
'x-api-key': 'key-123',
620+
'x-aPi-kEy': 'key-123',
660621
'Content-Type': 'application/json',
661622
};
662623

663-
const result = httpHeadersToSpanAttributes(headers, false);
624+
const result = httpHeadersToSpanAttributes(headers);
664625

665626
expect(result).toEqual({
666627
'http.request.header.content_type': 'application/json',
628+
'http.request.header.cookie': '[Filtered]',
629+
'http.request.header.x_api_key': '[Filtered]',
630+
'http.request.header.authorization': '[Filtered]',
667631
});
668632
});
669633

670-
it('filters comprehensive list of sensitive headers', () => {
634+
it('filters comprehensive list of sensitive headers when $description', () => {
671635
const headers = {
672636
'Content-Type': 'application/json',
673637
'User-Agent': 'test-agent',
@@ -692,15 +656,41 @@ describe('request utils', () => {
692656
'X-Private-Key': 'private',
693657
'X-Forwarded-user': 'user',
694658
'X-Forwarded-authorization': 'auth',
659+
'x-jwt-token': 'jwt',
660+
'x-bearer-token': 'bearer',
661+
'x-sso-token': 'sso',
662+
'x-saml-token': 'saml',
695663
};
696664

697-
const result = httpHeadersToSpanAttributes(headers, false);
665+
const result = httpHeadersToSpanAttributes(headers);
698666

667+
// Sensitive headers are always included and redacted
699668
expect(result).toEqual({
700669
'http.request.header.content_type': 'application/json',
701670
'http.request.header.user_agent': 'test-agent',
702671
'http.request.header.accept': 'application/json',
703672
'http.request.header.host': 'example.com',
673+
'http.request.header.authorization': '[Filtered]',
674+
'http.request.header.cookie': '[Filtered]',
675+
'http.request.header.set_cookie': '[Filtered]',
676+
'http.request.header.x_api_key': '[Filtered]',
677+
'http.request.header.x_auth_token': '[Filtered]',
678+
'http.request.header.x_secret': '[Filtered]',
679+
'http.request.header.x_secret_key': '[Filtered]',
680+
'http.request.header.www_authenticate': '[Filtered]',
681+
'http.request.header.proxy_authorization': '[Filtered]',
682+
'http.request.header.x_access_token': '[Filtered]',
683+
'http.request.header.x_csrf_token': '[Filtered]',
684+
'http.request.header.x_xsrf_token': '[Filtered]',
685+
'http.request.header.x_session_token': '[Filtered]',
686+
'http.request.header.x_password': '[Filtered]',
687+
'http.request.header.x_private_key': '[Filtered]',
688+
'http.request.header.x_forwarded_user': '[Filtered]',
689+
'http.request.header.x_forwarded_authorization': '[Filtered]',
690+
'http.request.header.x_jwt_token': '[Filtered]',
691+
'http.request.header.x_bearer_token': '[Filtered]',
692+
'http.request.header.x_sso_token': '[Filtered]',
693+
'http.request.header.x_saml_token': '[Filtered]',
704694
});
705695
});
706696
});

packages/nextjs/src/common/utils/addHeadersAsAttributes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function addHeadersAsAttributes(
2020
? winterCGHeadersToDict(headers as Headers)
2121
: headers;
2222

23-
const headerAttributes = httpHeadersToSpanAttributes(headersDict, sendDefaultPii);
23+
const headerAttributes = httpHeadersToSpanAttributes(headersDict);
2424

2525
if (span) {
2626
span.setAttributes(headerAttributes);

0 commit comments

Comments
 (0)