Skip to content

Commit 2a499ec

Browse files
authored
fix(scrubber): Scrub request bodies that are JSON arrays (#7561)
### Description `scrub_request` only calls `scrub_dict` on `event["request"]["data"]`, and `scrub_dict` returns immediately for anything that isn't a dict. So a body that parses to a top level JSON array is skipped entirely and every secret in it ships in the clear. It happens under the default scrubber, no config needed. `scrub_list` already handles this shape. Its docstring says it walks a list and any nested lists and calls `scrub_dict` on every dictionary it finds, and `scrub_dict` already hands nested lists to it when `recursive` is on. It just was never called on the request body. What changed: * `scrub_request` now calls `scrub_dict` and then `scrub_list` on `event["request"]["data"]`. Each one ignores the type it doesn't handle, so dict bodies behave exactly as before. * Two tests in `tests/test_scrubber.py`, one for a list body under the default scrubber and one for a dict nested inside a list body with `recursive=True`. One thing worth your call: `scrub_list` recurses into nested lists no matter what `recursive` is set to, so `[[{"password": "x"}]]` now gets filtered where today it isn't. That only ever filters more, never less. If you'd rather keep the shallow path exact I can gate the call on `self.recursive` instead. Fixes #7543
1 parent 7fd0934 commit 2a499ec

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

‎sentry_sdk/scrubber.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ def scrub_request(self, event: "Event") -> None:
128128
if "cookies" in event["request"]:
129129
self.scrub_dict(event["request"]["cookies"])
130130
if "data" in event["request"]:
131-
self.scrub_dict(event["request"]["data"])
131+
data = event["request"]["data"]
132+
self.scrub_dict(data) # no-op unless data is a dict
133+
self.scrub_list(data) # no-op unless data is a list
132134

133135
def scrub_extra(self, event: "Event") -> None:
134136
with capture_internal_exceptions():

‎tests/test_scrubber.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,59 @@ def test_request_scrubbing(sentry_init, capture_events):
6262
}
6363

6464

65+
def test_request_scrubbing_list_body(sentry_init, capture_events):
66+
sentry_init()
67+
events = capture_events()
68+
69+
try:
70+
1 / 0
71+
except ZeroDivisionError:
72+
ev, _hint = event_from_exception(sys.exc_info())
73+
74+
ev["request"] = {
75+
"data": [
76+
{"token": "secret", "foo": "bar"},
77+
{"password": "secret", "baz": "qux"},
78+
],
79+
}
80+
81+
capture_event(ev)
82+
83+
(event,) = events
84+
85+
assert event["request"] == {
86+
"data": [
87+
{"token": "[Filtered]", "foo": "bar"},
88+
{"password": "[Filtered]", "baz": "qux"},
89+
],
90+
}
91+
92+
assert event["_meta"]["request"] == {
93+
"data": {
94+
"0": {"token": {"": {"rem": [["!config", "s"]]}}},
95+
"1": {"password": {"": {"rem": [["!config", "s"]]}}},
96+
}
97+
}
98+
99+
100+
def test_recursive_request_scrubbing_list_body(sentry_init, capture_events):
101+
sentry_init(event_scrubber=EventScrubber(recursive=True))
102+
events = capture_events()
103+
104+
try:
105+
1 / 0
106+
except ZeroDivisionError:
107+
ev, _hint = event_from_exception(sys.exc_info())
108+
109+
ev["request"] = {"data": [{"deep": {"password": "secret"}}]}
110+
111+
capture_event(ev)
112+
113+
(event,) = events
114+
115+
assert event["request"] == {"data": [{"deep": {"password": "[Filtered]"}}]}
116+
117+
65118
def test_ip_address_not_scrubbed_when_pii_enabled(sentry_init, capture_events):
66119
sentry_init(send_default_pii=True)
67120
events = capture_events()

0 commit comments

Comments
 (0)