-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
fix(core): Filter collected HTTP bodies and redact browser GraphQL document literals #24178
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
base: develop
Are you sure you want to change the base?
Changes from all commits
c943d35
9c9af4a
d553f79
2216e26
4911a6c
253015b
7eca651
e93cb39
4190fe4
ab6c468
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 |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| import { isPlainObject } from '../is'; | ||
| import { FILTERED_VALUE } from './filtering-snippets'; | ||
| import { shouldFilterDataKey } from './filterKeyValueData'; | ||
| import { filterQueryParams } from './filterQueryParams'; | ||
|
|
||
| /** | ||
| * One `&`-separated form segment: empty, a bare key, or `key=value`. Keys are limited to the | ||
| * characters `application/x-www-form-urlencoded` encoding produces and raw whitespace disqualifies | ||
| * (encoded forms write spaces as `+` or `%20`), so XML, multipart, prose, and URLs never count as | ||
| * a pseudo-form that the filter would then rewrite. | ||
| */ | ||
| const FORM_SEGMENT_RE = /^(?:[\w%.*+-]+(?:=[^&\s]*)?)?$/; | ||
|
|
||
| /** | ||
| * A form body is `&`-separated `key=value` pairs, the only non-JSON shape whose keys the denylist | ||
| * can check. Valueless keys, empty segments, and a trailing `&` are tolerated — a too-strict gate | ||
| * would let a body like `password=secret&` skip the filter and ship raw. At least one `=` is | ||
| * required so prose is never rewritten as a pseudo-form. | ||
| */ | ||
| function isFormBody(body: string): boolean { | ||
| return body.includes('=') && body.split('&').every(segment => FORM_SEGMENT_RE.test(segment)); | ||
| } | ||
|
sentry[bot] marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Scrubs the values of known-sensitive keys in an HTTP body the SDK collected itself, before it | ||
|
Comment on lines
+23
to
+25
Contributor
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. Bug: The Suggested FixModify the logic in Prompt for AI Agent |
||
| * becomes `request.data` or `http.request.body.data`. | ||
| * | ||
| * Only values the SDK can attribute to a sensitive key are replaced. Everything else passes | ||
| * through unchanged: Relay scrubs server-side anyway and cannot tell an SDK-filtered value from a | ||
| * literal one, so client-side filtering beyond known-sensitive keys only destroys data. | ||
| */ | ||
| export function filterCollectedHttpBody(body: unknown): unknown { | ||
| if (typeof body === 'string') { | ||
| return filterCollectedHttpBodyString(body); | ||
| } | ||
|
|
||
| return isPlainObject(body) || Array.isArray(body) ? filterBodyValue(body) : body; | ||
| } | ||
|
|
||
| /** | ||
| * String-only variant of {@link filterCollectedHttpBody}. Capture sites call this before they | ||
| * truncate, because a truncated JSON body no longer parses and would pass through unfiltered. | ||
| */ | ||
| export function filterCollectedHttpBodyString(body: string): string { | ||
| if (!body) { | ||
| return body; | ||
| } | ||
|
|
||
| try { | ||
| const json: unknown = JSON.parse(body); | ||
| if (typeof json === 'object' && json !== null) { | ||
| return JSON.stringify(filterBodyValue(json)); | ||
| } | ||
| } catch { | ||
| // Not JSON. The form-encoded attempt below runs instead. | ||
| } | ||
|
|
||
| if (isFormBody(body)) { | ||
| // The query-param filter keeps the body's original encoding byte-for-byte. | ||
| return filterQueryParams(body, true) ?? body; | ||
| } | ||
|
|
||
| return body; | ||
| } | ||
|
|
||
| function filterBodyValue(value: unknown): unknown { | ||
| if (Array.isArray(value)) { | ||
| return value.map(filterBodyValue); | ||
| } | ||
|
|
||
| if (!isPlainObject(value)) { | ||
| return value; | ||
| } | ||
|
|
||
| // `Object.fromEntries` instead of assigning `result[key]`, so user-controlled keys like | ||
| // `__proto__` never hit a computed property write (CodeQL js/remote-property-injection). | ||
| return Object.fromEntries( | ||
| Object.entries(value).map(([key, nested]) => [ | ||
| key, | ||
| shouldFilterDataKey(key, true) ? FILTERED_VALUE : filterBodyValue(nested), | ||
|
logaretm marked this conversation as resolved.
|
||
| ]), | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.