Skip to content
Open
9 changes: 8 additions & 1 deletion sentry_sdk/integrations/_wsgi_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,14 @@ def extract_into_event(self, event: "Event") -> None:
content_length = self.content_length()
request_info = event.get("request", {})

if should_send_default_pii():
if has_data_collection_enabled(client.options):
cookies = _apply_key_value_collection_filtering(
items=dict(self.cookies()),
behaviour=client.options["data_collection"]["cookies"],
)
if cookies:
request_info["cookies"] = cookies
elif should_send_default_pii():
request_info["cookies"] = dict(self.cookies())

if not request_body_within_bounds(client, content_length):
Expand Down
3 changes: 1 addition & 2 deletions sentry_sdk/integrations/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import sentry_sdk
from sentry_sdk.consts import SPANDATA
from sentry_sdk.integrations import DidNotEnable
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.traces import StreamedSpan, get_current_span
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
from sentry_sdk.tracing_utils import has_span_streaming_enabled
Expand Down Expand Up @@ -118,7 +117,7 @@ def event_processor(event: "Event", hint: "Dict[str, Any]") -> "Event":
# Extract information from request
request_info = event.get("request", {})
if info:
if "cookies" in info and should_send_default_pii():
if "cookies" in info:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the should_send_default_pii check here is no longer needed since this check happens within StarletteRequestExtractor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Been some time since I last looked at the extractor code -- so the Starlette extractor runs after this and overwrites the cookies?

Just wanted to double-check that we have the precedence right (and that the cookies are really missing if should_send_default_pii=False after this change)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Been some time since I last looked at the extractor code -- so the Starlette extractor runs after this and overwrites the cookies?

It runs before this, and then, yes, overwrites the cookies.

If should_send_default_pii is false or if the data collection configuration filters out cookies, then the cookies key/value doesn't get set on info within the Starlette extractor's extract_request_info (and line 121 doesn't run).

request_info["cookies"] = info["cookies"]
if "data" in info:
request_info["data"] = info["data"]
Expand Down
14 changes: 12 additions & 2 deletions sentry_sdk/integrations/litestar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.data_collection import _apply_key_value_collection_filtering
from sentry_sdk.integrations import (
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
DidNotEnable,
Expand All @@ -16,6 +17,7 @@
from sentry_sdk.utils import (
ensure_integration_enabled,
event_from_exception,
has_data_collection_enabled,
transaction_from_function,
)

Expand Down Expand Up @@ -279,7 +281,8 @@ def patch_http_route_handle() -> None:
async def handle_wrapper(
self: "HTTPRoute", scope: "HTTPScope", receive: "Receive", send: "Send"
) -> None:
if sentry_sdk.get_client().get_integration(LitestarIntegration) is None:
client = sentry_sdk.get_client()
if client.get_integration(LitestarIntegration) is None:
return await old_handle(self, scope, receive, send)

sentry_scope = sentry_sdk.get_isolation_scope()
Expand Down Expand Up @@ -318,7 +321,14 @@ async def handle_wrapper(
def event_processor(event: "Event", _: "Hint") -> "Event":
request_info = event.get("request", {})
request_info["content_length"] = len(scope.get("_body", b""))
if should_send_default_pii():
if has_data_collection_enabled(client.options):
cookies = _apply_key_value_collection_filtering(
items=extracted_request_data["cookies"],
behaviour=client.options["data_collection"]["cookies"],
)
if cookies:
request_info["cookies"] = cookies
elif should_send_default_pii():
request_info["cookies"] = extracted_request_data["cookies"]
if request_data is not None:
request_info["data"] = request_data
Expand Down
20 changes: 18 additions & 2 deletions sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import sentry_sdk
from sentry_sdk._types import OVER_SIZE_LIMIT_SUBSTITUTE
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.data_collection import _apply_key_value_collection_filtering
from sentry_sdk.integrations import (
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
DidNotEnable,
Expand All @@ -35,6 +36,7 @@
capture_internal_exceptions,
ensure_integration_enabled,
event_from_exception,
has_data_collection_enabled,
nullcontext,
parse_version,
transaction_from_function,
Expand Down Expand Up @@ -719,8 +721,15 @@ def __init__(self: "StarletteRequestExtractor", request: "Request") -> None:
def extract_cookies_from_request(
self: "StarletteRequestExtractor",
) -> "Optional[Dict[str, Any]]":
client_options = sentry_sdk.get_client().options
cookies: "Optional[Dict[str, Any]]" = None
if should_send_default_pii():
Comment thread
cursor[bot] marked this conversation as resolved.

if has_data_collection_enabled(client_options):
cookies = _apply_key_value_collection_filtering(
items=self.cookies(),
behaviour=client_options["data_collection"]["cookies"],
)
elif should_send_default_pii():
cookies = self.cookies()

return cookies
Expand All @@ -734,7 +743,14 @@ async def extract_request_info(

with capture_internal_exceptions():
# Add cookies
if should_send_default_pii():
if has_data_collection_enabled(client.options):
cookies = _apply_key_value_collection_filtering(
items=self.cookies(),
behaviour=client.options["data_collection"]["cookies"],
)
if cookies:
request_info["cookies"] = cookies
elif should_send_default_pii():
request_info["cookies"] = self.cookies()

# If there is no body, just return the cookies
Expand Down
14 changes: 12 additions & 2 deletions sentry_sdk/integrations/starlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.data_collection import _apply_key_value_collection_filtering
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
from sentry_sdk.scope import should_send_default_pii
Expand All @@ -10,6 +11,7 @@
from sentry_sdk.utils import (
ensure_integration_enabled,
event_from_exception,
has_data_collection_enabled,
nullcontext,
transaction_from_function,
)
Expand Down Expand Up @@ -227,7 +229,8 @@ def patch_http_route_handle() -> None:
async def handle_wrapper(
self: "HTTPRoute", scope: "HTTPScope", receive: "Receive", send: "Send"
) -> None:
if sentry_sdk.get_client().get_integration(StarliteIntegration) is None:
client = sentry_sdk.get_client()
if client.get_integration(StarliteIntegration) is None:
return await old_handle(self, scope, receive, send)

sentry_scope = sentry_sdk.get_isolation_scope()
Expand Down Expand Up @@ -265,7 +268,14 @@ async def handle_wrapper(
def event_processor(event: "Event", _: "Hint") -> "Event":
request_info = event.get("request", {})
request_info["content_length"] = len(scope.get("_body", b""))
if should_send_default_pii():
if has_data_collection_enabled(client.options):
cookies = _apply_key_value_collection_filtering(
items=extracted_request_data["cookies"],
behaviour=client.options["data_collection"]["cookies"],
)
if cookies:
request_info["cookies"] = cookies
elif should_send_default_pii():
request_info["cookies"] = extracted_request_data["cookies"]
if request_data is not None:
request_info["data"] = request_data
Expand Down
118 changes: 118 additions & 0 deletions tests/integrations/django/test_data_scrubbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
from django.core.urlresolvers import reverse


NO_COOKIES = object()


@pytest.fixture
def client():
return Client(application)
Expand Down Expand Up @@ -99,3 +102,118 @@ def test_scrub_django_custom_session_cookies_filtered(
"csrf_secret": "[Filtered]",
"foo": "bar",
}


@pytest.mark.forked
@pytest_mark_django_db_decorator()
@pytest.mark.parametrize(
"cookies_to_set, data_collection, expected_cookies",
[
pytest.param(
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
{"cookies": {"mode": "off"}},
NO_COOKIES,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just None?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to try it out to see if it read a bit better, but happy to change this to None to match what's happened on the previous branch if we'd rather not have an extra variable lying around in the test file:)

id="off",
),
pytest.param(
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
{"cookies": {"mode": "denylist"}},
{
"sessionid": "[Filtered]",
"csrftoken": "[Filtered]",
"foo": "bar",
},
id="denylist-default",
),
pytest.param(
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
{"cookies": {"mode": "denylist", "terms": ["foo"]}},
{
"sessionid": "[Filtered]",
"csrftoken": "[Filtered]",
"foo": "[Filtered]",
},
id="denylist-extra-terms",
),
pytest.param(
{"sessionid": "123", "csrftoken": "456", "foo": "bar", "bar": "baz"},
{"cookies": {"mode": "allowlist", "terms": ["foo"]}},
{
"sessionid": "[Filtered]",
"csrftoken": "[Filtered]",
"foo": "bar",
"bar": "[Filtered]",
},
id="allowlist",
),
pytest.param(
{"sessionid": "123", "csrftoken": "456", "foo": "bar", "bar": "baz"},
{"cookies": {"mode": "allowlist", "terms": ["sessionid", "foo"]}},
{
"sessionid": "[Filtered]",
"csrftoken": "[Filtered]",
"foo": "bar",
"bar": "[Filtered]",
},
id="allowlist-cannot-override-sensitive",
),
pytest.param(
{"sessionid": "123", "csrftoken": "456", "foo": "bar"},
{},
{
"sessionid": "[Filtered]",
"csrftoken": "[Filtered]",
"foo": "bar",
},
id="cookies-omitted-defaults-to-denylist",
),
],
)
def test_data_collection_cookies(
sentry_init,
client,
capture_items,
cookies_to_set,
data_collection,
expected_cookies,
):
sentry_init(
integrations=[DjangoIntegration()],
_experiments={"data_collection": data_collection},
)
items = capture_items("event")
for name, value in cookies_to_set.items():
werkzeug_set_cookie(client, "localhost", name, value)
client.get(reverse("view_exc"))

(event,) = (item.payload for item in items if item.type == "event")
if expected_cookies is NO_COOKIES:
assert "cookies" not in event["request"]
else:
assert event["request"]["cookies"] == expected_cookies


@pytest.mark.forked
@pytest_mark_django_db_decorator()
def test_data_collection_cookies_precedence_over_send_default_pii(
sentry_init, client, capture_items
):
# ``data_collection`` is the single source of truth: even with
# ``send_default_pii=False``, the configured cookie behaviour still applies.
sentry_init(
integrations=[DjangoIntegration()],
send_default_pii=False,
_experiments={"data_collection": {"cookies": {"mode": "denylist"}}},
)
items = capture_items("event")
werkzeug_set_cookie(client, "localhost", "sessionid", "123")
werkzeug_set_cookie(client, "localhost", "csrftoken", "456")
werkzeug_set_cookie(client, "localhost", "foo", "bar")
client.get(reverse("view_exc"))

(event,) = (item.payload for item in items if item.type == "event")
assert event["request"]["cookies"] == {
"sessionid": "[Filtered]",
"csrftoken": "[Filtered]",
"foo": "bar",
}
Loading
Loading