-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(core): Parse individual cookies from cookie header #18325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
22cb73e
27cc608
5a8f350
e5407f0
1b4d3c5
f3231a3
0a6a4b2
d3de185
51656f0
01afe8c
0e91b47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -128,21 +128,29 @@ function getAbsoluteUrl({ | |
| return undefined; | ||
| } | ||
|
|
||
| // "-user" because otherwise it would match "user-agent" | ||
| const SENSITIVE_HEADER_SNIPPETS = [ | ||
| 'auth', | ||
| 'token', | ||
| 'secret', | ||
| 'cookie', | ||
| '-user', | ||
| 'session', // for the user_session cookie | ||
| 'password', | ||
| 'passwd', | ||
| 'pwd', | ||
| 'key', | ||
| 'jwt', | ||
| 'bearer', | ||
| 'sso', | ||
| 'saml', | ||
| 'csrf', | ||
| 'xsrf', | ||
| 'credentials', | ||
| // Always treat cookie headers as sensitive in case individual key-value cookie pairs cannot properly be extracted | ||
| 'set-cookie', | ||
| 'cookie', | ||
| ]; | ||
|
|
||
| const PII_HEADER_SNIPPETS = ['x-forwarded-', '-user']; | ||
|
|
||
| /** | ||
| * Converts incoming HTTP request headers to OpenTelemetry span attributes following semantic conventions. | ||
| * Header names are converted to the format: http.request.header.<key> | ||
|
|
@@ -152,6 +160,7 @@ const SENSITIVE_HEADER_SNIPPETS = [ | |
| */ | ||
| export function httpHeadersToSpanAttributes( | ||
| headers: Record<string, string | string[] | undefined>, | ||
| sendDefaultPii: boolean = false, | ||
| ): Record<string, string> { | ||
| const spanAttributes: Record<string, string> = {}; | ||
|
|
||
|
|
@@ -161,16 +170,29 @@ export function httpHeadersToSpanAttributes( | |
| return; | ||
| } | ||
|
|
||
| const lowerCasedKey = key.toLowerCase(); | ||
| const isSensitive = SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet)); | ||
| const normalizedKey = `http.request.header.${lowerCasedKey.replace(/-/g, '_')}`; | ||
| const lowerCasedHeaderKey = key.toLowerCase(); | ||
| const isCookieHeader = lowerCasedHeaderKey === 'cookie' || lowerCasedHeaderKey === 'set-cookie'; | ||
|
|
||
| if (isCookieHeader && typeof value === 'string' && value !== '') { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. q: do we handle arrays of cookie headers? (or is this not relevant for cookie/set-cookie?)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the sake of simplicity (as cookies are one string), an array would just be parsed as |
||
| // Set-Cookie: single cookie with attributes ("name=value; HttpOnly; Secure") | ||
| // Cookie: multiple cookies separated by "; " ("cookie1=value1; cookie2=value2") | ||
| const isSetCookie = lowerCasedHeaderKey === 'set-cookie'; | ||
| const semicolonIndex = value.indexOf(';'); | ||
|
Comment on lines
+179
to
+180
This comment was marked as outdated.
Sorry, something went wrong. |
||
| const cookieString = isSetCookie && semicolonIndex !== -1 ? value.substring(0, semicolonIndex) : value; | ||
| const cookies = isSetCookie ? [cookieString] : cookieString.split('; '); | ||
|
|
||
| for (const cookie of cookies) { | ||
| // Split only at the first '=' to preserve '=' characters in cookie values | ||
| const equalSignIndex = cookie.indexOf('='); | ||
| const cookieKey = equalSignIndex !== -1 ? cookie.substring(0, equalSignIndex) : cookie; | ||
| const cookieValue = equalSignIndex !== -1 ? cookie.substring(equalSignIndex + 1) : ''; | ||
|
|
||
| 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; | ||
| const lowerCasedCookieKey = cookieKey.toLowerCase(); | ||
|
|
||
| addSpanAttribute(spanAttributes, lowerCasedHeaderKey, lowerCasedCookieKey, cookieValue, sendDefaultPii); | ||
| } | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else { | ||
| addSpanAttribute(spanAttributes, lowerCasedHeaderKey, '', value, sendDefaultPii); | ||
| } | ||
| }); | ||
| } catch { | ||
|
|
@@ -180,6 +202,47 @@ export function httpHeadersToSpanAttributes( | |
| return spanAttributes; | ||
| } | ||
This comment was marked as outdated.
Sorry, something went wrong. |
||
|
|
||
| function normalizeAttributeKey(key: string): string { | ||
| return key.replace(/-/g, '_'); | ||
| } | ||
|
|
||
| function addSpanAttribute( | ||
| spanAttributes: Record<string, string>, | ||
| headerKey: string, | ||
| cookieKey: string, | ||
| value: string | string[] | undefined, | ||
| sendPii: boolean, | ||
| ): void { | ||
| const normalizedKey = cookieKey | ||
| ? `http.request.header.${normalizeAttributeKey(headerKey)}.${normalizeAttributeKey(cookieKey)}` | ||
| : `http.request.header.${normalizeAttributeKey(headerKey)}`; | ||
|
|
||
| const headerValue = handleHttpHeader(cookieKey || headerKey, value, sendPii); | ||
| if (headerValue !== undefined) { | ||
| spanAttributes[normalizedKey] = headerValue; | ||
| } | ||
| } | ||
|
|
||
| function handleHttpHeader( | ||
| lowerCasedKey: string, | ||
| value: string | string[] | undefined, | ||
| sendPii: boolean, | ||
| ): string | undefined { | ||
| const isSensitive = sendPii | ||
| ? SENSITIVE_HEADER_SNIPPETS.some(snippet => lowerCasedKey.includes(snippet)) | ||
| : [...PII_HEADER_SNIPPETS, ...SENSITIVE_HEADER_SNIPPETS].some(snippet => lowerCasedKey.includes(snippet)); | ||
|
|
||
| if (isSensitive) { | ||
| return '[Filtered]'; | ||
| } else if (Array.isArray(value)) { | ||
| return value.map(v => (v != null ? String(v) : v)).join(';'); | ||
| } else if (typeof value === 'string') { | ||
| return value; | ||
| } | ||
|
|
||
| return undefined; | ||
| } | ||
|
|
||
| /** Extract the query params from an URL. */ | ||
| export function extractQueryParamsFromUrl(url: string): string | undefined { | ||
| // url is path and query string | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
l: probably saves us a few bytes: