Skip to content

Commit 1a4bbd9

Browse files
committed
fix(core): Filter more cookie names for PII
1 parent 64fc5b9 commit 1a4bbd9

2 files changed

Lines changed: 116 additions & 17 deletions

File tree

packages/core/src/utils/request.ts

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,49 @@ const SENSITIVE_HEADER_SNIPPETS = [
149149
'cookie',
150150
];
151151

152+
/**
153+
* Extra substrings matched only against individual Cookie / Set-Cookie **names** (not header names),
154+
* so we can cover common session secrets that do not match {@link SENSITIVE_HEADER_SNIPPETS}
155+
* (e.g. `connect.sid` does not contain `session`) without false positives on arbitrary HTTP headers.
156+
*/
157+
const SENSITIVE_COOKIE_NAME_SNIPPETS = [
158+
// Express / Connect default session cookie
159+
'.sid',
160+
// PHP session cookie
161+
'phpsess',
162+
// Common opaque session id suffix / cookie names (e.g. ASPSESSIONID*, BIGipServer*)
163+
'sessid',
164+
// Laravel etc. "remember me" tokens
165+
'remember',
166+
// OAuth / OIDC auxiliary cookies
167+
'oauth',
168+
'oidc',
169+
'pkce',
170+
'nonce',
171+
// Explicit token-style cookie names
172+
'id_token',
173+
'access_token',
174+
'refresh_token',
175+
// RFC 6265bis cookie name prefixes for high-security cookies
176+
'__secure-',
177+
'__host-',
178+
// Load balancer / CDN sticky-session cookies (opaque routing tokens)
179+
'awsalb',
180+
'awselb',
181+
'akamai',
182+
// BaaS / IdP session cookies (names often omit "session")
183+
'__stripe',
184+
'cognito',
185+
'firebase',
186+
'supabase',
187+
'sb-',
188+
// Auth.js / NextAuth.js
189+
'next-auth',
190+
// Step-up / MFA cookies
191+
'mfa',
192+
'2fa',
193+
];
194+
152195
const PII_HEADER_SNIPPETS = ['x-forwarded-', '-user'];
153196

154197
/**
@@ -196,17 +239,23 @@ export function httpHeadersToSpanAttributes(
196239

197240
const lowerCasedCookieKey = cookieKey.toLowerCase();
198241

199-
addSpanAttribute(
242+
addSpanAttribute({
200243
spanAttributes,
201-
lowerCasedHeaderKey,
202-
lowerCasedCookieKey,
203-
cookieValue,
244+
headerKey: lowerCasedHeaderKey,
245+
cookieKey: lowerCasedCookieKey,
246+
value: cookieValue,
204247
sendDefaultPii,
205248
lifecycle,
206-
);
249+
});
207250
}
208251
} else {
209-
addSpanAttribute(spanAttributes, lowerCasedHeaderKey, '', value, sendDefaultPii, lifecycle);
252+
addSpanAttribute({
253+
spanAttributes,
254+
headerKey: lowerCasedHeaderKey,
255+
value,
256+
sendDefaultPii,
257+
lifecycle,
258+
});
210259
}
211260
});
212261
} catch {
@@ -220,15 +269,31 @@ function normalizeAttributeKey(key: string): string {
220269
return key.replace(/-/g, '_');
221270
}
222271

223-
function addSpanAttribute(
224-
spanAttributes: Record<string, string>,
225-
headerKey: string,
226-
cookieKey: string,
227-
value: string | string[] | undefined,
228-
sendPii: boolean,
229-
lifecycle: 'request' | 'response',
230-
): void {
231-
const headerValue = handleHttpHeader(cookieKey || headerKey, value, sendPii);
272+
type AddSpanAttributeOptions = {
273+
spanAttributes: Record<string, string>;
274+
/** Lowercased HTTP header name (e.g. `cookie`, `set-cookie`, `accept`). */
275+
headerKey: string;
276+
/**
277+
* Lowercased cookie name when this attribute comes from a parsed `Cookie` / `Set-Cookie` value.
278+
* Omit for non-cookie headers; when present and non-empty, cookie-specific sensitivity rules apply.
279+
*/
280+
cookieKey?: string;
281+
value: string | string[] | undefined;
282+
sendDefaultPii: boolean;
283+
lifecycle: 'request' | 'response';
284+
};
285+
286+
function addSpanAttribute({
287+
spanAttributes,
288+
headerKey,
289+
cookieKey,
290+
value,
291+
sendDefaultPii,
292+
lifecycle,
293+
}: AddSpanAttributeOptions): void {
294+
const isCookieSubKey = Boolean(cookieKey);
295+
const nameForSensitivity = cookieKey || headerKey;
296+
const headerValue = handleHttpHeader(nameForSensitivity, value, sendDefaultPii, isCookieSubKey);
232297
if (headerValue == null) {
233298
return;
234299
}
@@ -241,10 +306,15 @@ function handleHttpHeader(
241306
lowerCasedKey: string,
242307
value: string | string[] | undefined,
243308
sendPii: boolean,
309+
isCookieSubKey: boolean = false,
244310
): string | undefined {
311+
const snippetsForSensitivity = isCookieSubKey
312+
? [...SENSITIVE_HEADER_SNIPPETS, ...SENSITIVE_COOKIE_NAME_SNIPPETS]
313+
: SENSITIVE_HEADER_SNIPPETS;
314+
245315
const isSensitive = sendPii
246-
? SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))
247-
: [...PII_HEADER_SNIPPETS, ...SENSITIVE_HEADER_SNIPPETS].some(snippet => lowerCasedKey.includes(snippet));
316+
? snippetsForSensitivity.some(snippet => lowerCasedKey.includes(snippet))
317+
: [...PII_HEADER_SNIPPETS, ...snippetsForSensitivity].some(snippet => lowerCasedKey.includes(snippet));
248318

249319
if (isSensitive) {
250320
return '[Filtered]';

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,35 @@ describe('request utils', () => {
650650
});
651651
});
652652

653+
it('filters common framework and provider session-style cookie names', () => {
654+
const headers = {
655+
Cookie:
656+
'connect.sid=s3cr3t; express.sid=opaque; PHPSESSID=abcd; theme=light; sb-access-token=x; __stripe_mid=y',
657+
};
658+
659+
const result = httpHeadersToSpanAttributes(headers);
660+
661+
expect(result).toEqual({
662+
'http.request.header.cookie.connect.sid': '[Filtered]',
663+
'http.request.header.cookie.express.sid': '[Filtered]',
664+
'http.request.header.cookie.phpsessid': '[Filtered]',
665+
'http.request.header.cookie.theme': 'light',
666+
'http.request.header.cookie.sb_access_token': '[Filtered]',
667+
'http.request.header.cookie.__stripe_mid': '[Filtered]',
668+
});
669+
});
670+
671+
it('still filters session-style cookie names when sendDefaultPii is true', () => {
672+
const headers = { Cookie: 'connect.sid=s3cr3t; analytics=1' };
673+
674+
const result = httpHeadersToSpanAttributes(headers, true);
675+
676+
expect(result).toEqual({
677+
'http.request.header.cookie.connect.sid': '[Filtered]',
678+
'http.request.header.cookie.analytics': '1',
679+
});
680+
});
681+
653682
it('adds a filtered cookie header when cookie header is present, but has no valid key=value pairs', () => {
654683
const headers1 = { Cookie: ['key', 'val'] };
655684
const result1 = httpHeadersToSpanAttributes(headers1);

0 commit comments

Comments
 (0)