Skip to content

Commit dcedc3a

Browse files
authored
feat(integrations): apply data_collection cookie filtering to wsgi, starlette, litestar, starlite (#6797)
Extends the granular cookie collection controls (data_collection.cookies) to _wsgi_common, starlette, litestar, and starlite, matching the behavior already used elsewhere. Falls back to should_send_default_pii() when data_collection is not configured for cookies. HTTP "Cookie" and "set-cookie" headers will continue to be completely filtered with the "[Filtered]" value. Fixes PY-2581 Fixes #6741
1 parent f06d54e commit dcedc3a

10 files changed

Lines changed: 695 additions & 9 deletions

File tree

sentry_sdk/integrations/_wsgi_common.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,14 @@ def extract_into_event(self, event: "Event") -> None:
8989
content_length = self.content_length()
9090
request_info = event.get("request", {})
9191

92-
if should_send_default_pii():
92+
if has_data_collection_enabled(client.options):
93+
cookies = _apply_key_value_collection_filtering(
94+
items=dict(self.cookies()),
95+
behaviour=client.options["data_collection"]["cookies"],
96+
)
97+
if cookies:
98+
request_info["cookies"] = cookies
99+
elif should_send_default_pii():
93100
request_info["cookies"] = dict(self.cookies())
94101

95102
if not request_body_within_bounds(client, content_length):

sentry_sdk/integrations/fastapi.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import sentry_sdk
77
from sentry_sdk.consts import SPANDATA
88
from sentry_sdk.integrations import DidNotEnable
9-
from sentry_sdk.scope import should_send_default_pii
109
from sentry_sdk.traces import StreamedSpan, get_current_span
1110
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
1211
from sentry_sdk.tracing_utils import has_span_streaming_enabled
@@ -118,7 +117,7 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
118117
# Extract information from request
119118
request_info = event.get("request", {})
120119
if info:
121-
if "cookies" in info and should_send_default_pii():
120+
if "cookies" in info:
122121
request_info["cookies"] = info["cookies"]
123122
if "data" in info:
124123
request_info["data"] = info["data"]

sentry_sdk/integrations/litestar.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import sentry_sdk
55
from sentry_sdk.consts import OP, SPANDATA
6+
from sentry_sdk.data_collection import _apply_key_value_collection_filtering
67
from sentry_sdk.integrations import (
78
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
89
DidNotEnable,
@@ -16,6 +17,7 @@
1617
from sentry_sdk.utils import (
1718
ensure_integration_enabled,
1819
event_from_exception,
20+
has_data_collection_enabled,
1921
transaction_from_function,
2022
)
2123

@@ -279,7 +281,8 @@ def patch_http_route_handle() -> None:
279281
async def handle_wrapper(
280282
self: "HTTPRoute", scope: "HTTPScope", receive: "Receive", send: "Send"
281283
) -> None:
282-
if sentry_sdk.get_client().get_integration(LitestarIntegration) is None:
284+
client = sentry_sdk.get_client()
285+
if client.get_integration(LitestarIntegration) is None:
283286
return await old_handle(self, scope, receive, send)
284287

285288
sentry_scope = sentry_sdk.get_isolation_scope()
@@ -318,7 +321,14 @@ async def handle_wrapper(
318321
def event_processor(event: "Event", _: "Hint") -> "Event":
319322
request_info = event.get("request", {})
320323
request_info["content_length"] = len(scope.get("_body", b""))
321-
if should_send_default_pii():
324+
if has_data_collection_enabled(client.options):
325+
cookies = _apply_key_value_collection_filtering(
326+
items=extracted_request_data["cookies"],
327+
behaviour=client.options["data_collection"]["cookies"],
328+
)
329+
if cookies:
330+
request_info["cookies"] = cookies
331+
elif should_send_default_pii():
322332
request_info["cookies"] = extracted_request_data["cookies"]
323333
if request_data is not None:
324334
request_info["data"] = request_data

sentry_sdk/integrations/starlette.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sentry_sdk
1111
from sentry_sdk._types import OVER_SIZE_LIMIT_SUBSTITUTE
1212
from sentry_sdk.consts import OP, SPANDATA
13+
from sentry_sdk.data_collection import _apply_key_value_collection_filtering
1314
from sentry_sdk.integrations import (
1415
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
1516
DidNotEnable,
@@ -35,6 +36,7 @@
3536
capture_internal_exceptions,
3637
ensure_integration_enabled,
3738
event_from_exception,
39+
has_data_collection_enabled,
3840
nullcontext,
3941
parse_version,
4042
transaction_from_function,
@@ -719,8 +721,15 @@ def __init__(self: "StarletteRequestExtractor", request: "Request") -> None:
719721
def extract_cookies_from_request(
720722
self: "StarletteRequestExtractor",
721723
) -> "Optional[Dict[str, Any]]":
724+
client_options = sentry_sdk.get_client().options
722725
cookies: "Optional[Dict[str, Any]]" = None
723-
if should_send_default_pii():
726+
727+
if has_data_collection_enabled(client_options):
728+
cookies = _apply_key_value_collection_filtering(
729+
items=self.cookies(),
730+
behaviour=client_options["data_collection"]["cookies"],
731+
)
732+
elif should_send_default_pii():
724733
cookies = self.cookies()
725734

726735
return cookies
@@ -734,7 +743,14 @@ async def extract_request_info(
734743

735744
with capture_internal_exceptions():
736745
# Add cookies
737-
if should_send_default_pii():
746+
if has_data_collection_enabled(client.options):
747+
cookies = _apply_key_value_collection_filtering(
748+
items=self.cookies(),
749+
behaviour=client.options["data_collection"]["cookies"],
750+
)
751+
if cookies:
752+
request_info["cookies"] = cookies
753+
elif should_send_default_pii():
738754
request_info["cookies"] = self.cookies()
739755

740756
# If there is no body, just return the cookies

sentry_sdk/integrations/starlite.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import sentry_sdk
44
from sentry_sdk.consts import OP, SPANDATA
5+
from sentry_sdk.data_collection import _apply_key_value_collection_filtering
56
from sentry_sdk.integrations import DidNotEnable, Integration
67
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
78
from sentry_sdk.scope import should_send_default_pii
@@ -10,6 +11,7 @@
1011
from sentry_sdk.utils import (
1112
ensure_integration_enabled,
1213
event_from_exception,
14+
has_data_collection_enabled,
1315
nullcontext,
1416
transaction_from_function,
1517
)
@@ -227,7 +229,8 @@ def patch_http_route_handle() -> None:
227229
async def handle_wrapper(
228230
self: "HTTPRoute", scope: "HTTPScope", receive: "Receive", send: "Send"
229231
) -> None:
230-
if sentry_sdk.get_client().get_integration(StarliteIntegration) is None:
232+
client = sentry_sdk.get_client()
233+
if client.get_integration(StarliteIntegration) is None:
231234
return await old_handle(self, scope, receive, send)
232235

233236
sentry_scope = sentry_sdk.get_isolation_scope()
@@ -265,7 +268,14 @@ async def handle_wrapper(
265268
def event_processor(event: "Event", _: "Hint") -> "Event":
266269
request_info = event.get("request", {})
267270
request_info["content_length"] = len(scope.get("_body", b""))
268-
if should_send_default_pii():
271+
if has_data_collection_enabled(client.options):
272+
cookies = _apply_key_value_collection_filtering(
273+
items=extracted_request_data["cookies"],
274+
behaviour=client.options["data_collection"]["cookies"],
275+
)
276+
if cookies:
277+
request_info["cookies"] = cookies
278+
elif should_send_default_pii():
269279
request_info["cookies"] = extracted_request_data["cookies"]
270280
if request_data is not None:
271281
request_info["data"] = request_data

tests/integrations/django/test_data_scrubbing.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,118 @@ def test_scrub_django_custom_session_cookies_filtered(
9999
"csrf_secret": "[Filtered]",
100100
"foo": "bar",
101101
}
102+
103+
104+
@pytest.mark.forked
105+
@pytest_mark_django_db_decorator()
106+
@pytest.mark.parametrize(
107+
"cookies_to_set, data_collection, expected_cookies",
108+
[
109+
pytest.param(
110+
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
111+
{"cookies": {"mode": "off"}},
112+
None,
113+
id="off",
114+
),
115+
pytest.param(
116+
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
117+
{"cookies": {"mode": "denylist"}},
118+
{
119+
"sessionid": "[Filtered]",
120+
"csrftoken": "[Filtered]",
121+
"foo": "bar",
122+
},
123+
id="denylist-default",
124+
),
125+
pytest.param(
126+
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
127+
{"cookies": {"mode": "denylist", "terms": ["foo"]}},
128+
{
129+
"sessionid": "[Filtered]",
130+
"csrftoken": "[Filtered]",
131+
"foo": "[Filtered]",
132+
},
133+
id="denylist-extra-terms",
134+
),
135+
pytest.param(
136+
{"sessionid": "123", "csrftoken": "456", "foo": "bar", "bar": "baz"},
137+
{"cookies": {"mode": "allowlist", "terms": ["foo"]}},
138+
{
139+
"sessionid": "[Filtered]",
140+
"csrftoken": "[Filtered]",
141+
"foo": "bar",
142+
"bar": "[Filtered]",
143+
},
144+
id="allowlist",
145+
),
146+
pytest.param(
147+
{"sessionid": "123", "csrftoken": "456", "foo": "bar", "bar": "baz"},
148+
{"cookies": {"mode": "allowlist", "terms": ["sessionid", "foo"]}},
149+
{
150+
"sessionid": "[Filtered]",
151+
"csrftoken": "[Filtered]",
152+
"foo": "bar",
153+
"bar": "[Filtered]",
154+
},
155+
id="allowlist-cannot-override-sensitive",
156+
),
157+
pytest.param(
158+
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
159+
{},
160+
{
161+
"sessionid": "[Filtered]",
162+
"csrftoken": "[Filtered]",
163+
"foo": "bar",
164+
},
165+
id="cookies-omitted-defaults-to-denylist",
166+
),
167+
],
168+
)
169+
def test_data_collection_cookies(
170+
sentry_init,
171+
client,
172+
capture_items,
173+
cookies_to_set,
174+
data_collection,
175+
expected_cookies,
176+
):
177+
sentry_init(
178+
integrations=[DjangoIntegration()],
179+
_experiments={"data_collection": data_collection},
180+
)
181+
items = capture_items("event")
182+
for name, value in cookies_to_set.items():
183+
werkzeug_set_cookie(client, "localhost", name, value)
184+
client.get(reverse("view_exc"))
185+
186+
(event,) = (item.payload for item in items if item.type == "event")
187+
if expected_cookies is None:
188+
assert "cookies" not in event["request"]
189+
else:
190+
assert event["request"]["cookies"] == expected_cookies
191+
192+
193+
@pytest.mark.forked
194+
@pytest_mark_django_db_decorator()
195+
def test_data_collection_cookies_precedence_over_send_default_pii(
196+
sentry_init, client, capture_items
197+
):
198+
# ``data_collection`` is the single source of truth: even with
199+
# ``send_default_pii=False``, the configured cookie behaviour still applies.
200+
sentry_init(
201+
integrations=[DjangoIntegration()],
202+
send_default_pii=False,
203+
_experiments={"data_collection": {"cookies": {"mode": "denylist"}}},
204+
)
205+
items = capture_items("event")
206+
werkzeug_set_cookie(client, "localhost", "sessionid", "123")
207+
werkzeug_set_cookie(client, "localhost", "csrftoken", "456")
208+
werkzeug_set_cookie(client, "localhost", "foo", "bar")
209+
client.get(reverse("view_exc"))
210+
211+
(event,) = (item.payload for item in items if item.type == "event")
212+
assert event["request"]["cookies"] == {
213+
"sessionid": "[Filtered]",
214+
"csrftoken": "[Filtered]",
215+
"foo": "bar",
216+
}

0 commit comments

Comments
 (0)