Skip to content

Commit dbf1af1

Browse files
authored
fix(core): Filter more cookie names for PII (#20485)
Raised by claude security review, this PR extends the list of cookie names we filter for PII reasons, covering more ground. This is always best effort so no guarantees, but this should extend the list of things we cover significantly.
1 parent edd1867 commit dbf1af1

2 files changed

Lines changed: 111 additions & 17 deletions

File tree

packages/core/src/utils/request.ts

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,44 @@ 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+
* Cookie names are checked with the same `includes()` list as headers plus these entries; omit redundant
158+
* cookie-only snippets that are already implied by a header match (e.g. `oauth` → `auth`, `id_token` → `token`,
159+
* `next-auth` → `auth`).
160+
*/
161+
const SENSITIVE_COOKIE_NAME_SNIPPETS = [
162+
// Express / Connect default session cookie
163+
'.sid',
164+
// Opaque session ids (PHPSESSID, ASPSESSIONID*, BIGipServer*, *sessid*, …)
165+
'sessid',
166+
// Laravel etc. "remember me" tokens
167+
'remember',
168+
// OIDC / OAuth auxiliary (`oauth*` covered by header snippet `auth`)
169+
'oidc',
170+
'pkce',
171+
'nonce',
172+
// RFC 6265bis high-security cookie name prefixes
173+
'__secure-',
174+
'__host-',
175+
// Load balancer / CDN sticky-session cookies (opaque routing tokens)
176+
'awsalb',
177+
'awselb',
178+
'akamai',
179+
// BaaS / IdP session cookies (names often omit "session")
180+
'__stripe',
181+
'cognito',
182+
'firebase',
183+
'supabase',
184+
'sb-',
185+
// Step-up / MFA cookies
186+
'mfa',
187+
'2fa',
188+
];
189+
152190
const PII_HEADER_SNIPPETS = ['x-forwarded-', '-user'];
153191

154192
/**
@@ -196,17 +234,23 @@ export function httpHeadersToSpanAttributes(
196234

197235
const lowerCasedCookieKey = cookieKey.toLowerCase();
198236

199-
addSpanAttribute(
237+
addSpanAttribute({
200238
spanAttributes,
201-
lowerCasedHeaderKey,
202-
lowerCasedCookieKey,
203-
cookieValue,
239+
headerKey: lowerCasedHeaderKey,
240+
cookieKey: lowerCasedCookieKey,
241+
value: cookieValue,
204242
sendDefaultPii,
205243
lifecycle,
206-
);
244+
});
207245
}
208246
} else {
209-
addSpanAttribute(spanAttributes, lowerCasedHeaderKey, '', value, sendDefaultPii, lifecycle);
247+
addSpanAttribute({
248+
spanAttributes,
249+
headerKey: lowerCasedHeaderKey,
250+
value,
251+
sendDefaultPii,
252+
lifecycle,
253+
});
210254
}
211255
});
212256
} catch {
@@ -220,15 +264,31 @@ function normalizeAttributeKey(key: string): string {
220264
return key.replace(/-/g, '_');
221265
}
222266

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);
267+
type AddSpanAttributeOptions = {
268+
spanAttributes: Record<string, string>;
269+
/** Lowercased HTTP header name (e.g. `cookie`, `set-cookie`, `accept`). */
270+
headerKey: string;
271+
/**
272+
* Lowercased cookie name when this attribute comes from a parsed `Cookie` / `Set-Cookie` value.
273+
* Omit for non-cookie headers; when present and non-empty, cookie-specific sensitivity rules apply.
274+
*/
275+
cookieKey?: string;
276+
value: string | string[] | undefined;
277+
sendDefaultPii: boolean;
278+
lifecycle: 'request' | 'response';
279+
};
280+
281+
function addSpanAttribute({
282+
spanAttributes,
283+
headerKey,
284+
cookieKey,
285+
value,
286+
sendDefaultPii,
287+
lifecycle,
288+
}: AddSpanAttributeOptions): void {
289+
const isCookieSubKey = Boolean(cookieKey);
290+
const nameForSensitivity = cookieKey || headerKey;
291+
const headerValue = handleHttpHeader(nameForSensitivity, value, sendDefaultPii, isCookieSubKey);
232292
if (headerValue == null) {
233293
return;
234294
}
@@ -241,10 +301,15 @@ function handleHttpHeader(
241301
lowerCasedKey: string,
242302
value: string | string[] | undefined,
243303
sendPii: boolean,
304+
isCookieSubKey: boolean = false,
244305
): string | undefined {
306+
const snippetsForSensitivity = isCookieSubKey
307+
? [...SENSITIVE_HEADER_SNIPPETS, ...SENSITIVE_COOKIE_NAME_SNIPPETS]
308+
: SENSITIVE_HEADER_SNIPPETS;
309+
245310
const isSensitive = sendPii
246-
? SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet))
247-
: [...PII_HEADER_SNIPPETS, ...SENSITIVE_HEADER_SNIPPETS].some(snippet => lowerCasedKey.includes(snippet));
311+
? snippetsForSensitivity.some(snippet => lowerCasedKey.includes(snippet))
312+
: [...PII_HEADER_SNIPPETS, ...snippetsForSensitivity].some(snippet => lowerCasedKey.includes(snippet));
248313

249314
if (isSensitive) {
250315
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)