Skip to content

Commit ac500e7

Browse files
authored
ref(gcp)!: Drop send_default_pii support in request data collection (#7822)
Always route the query string and request body through `data_collection` instead of falling back to `send_default_pii` when `data_collection` is not configured. Update tests to drive behaviour through `data_collection` only. Fixes PY-2820 Fixes #7591
1 parent 576ba3f commit ac500e7

2 files changed

Lines changed: 30 additions & 157 deletions

File tree

‎sentry_sdk/integrations/gcp.py‎

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@
1111
from sentry_sdk.integrations import Integration
1212
from sentry_sdk.integrations._wsgi_common import _filter_headers
1313
from sentry_sdk.integrations.cloud_resource_context import CLOUD_PROVIDER
14-
from sentry_sdk.scope import Scope, should_send_default_pii
14+
from sentry_sdk.scope import Scope
1515
from sentry_sdk.traces import SegmentNameSource
1616
from sentry_sdk.utils import (
1717
AnnotatedValue,
1818
TimeoutThread,
1919
capture_internal_exceptions,
2020
event_from_exception,
21-
has_data_collection_enabled,
2221
logger,
2322
reraise,
2423
)
@@ -98,17 +97,12 @@ def sentry_func(
9897
if hasattr(gcp_event, "query_string"):
9998
query_string = gcp_event.query_string.decode("utf-8", errors="replace")
10099
if query_string:
101-
if has_data_collection_enabled(client.options):
102-
filtered_qs = _apply_data_collection_filtering_to_query_string(
103-
query_string=query_string,
104-
behaviour=client.options["data_collection"][
105-
"url_query_params"
106-
],
107-
)
108-
if filtered_qs:
109-
additional_attributes["url.query"] = filtered_qs
110-
elif should_send_default_pii():
111-
additional_attributes["url.query"] = query_string
100+
filtered_qs = _apply_data_collection_filtering_to_query_string(
101+
query_string=query_string,
102+
behaviour=client.options["data_collection"]["url_query_params"],
103+
)
104+
if filtered_qs:
105+
additional_attributes["url.query"] = filtered_qs
112106

113107
sampling_context = {
114108
"gcp_env": {
@@ -229,28 +223,19 @@ def event_processor(event: "Event", hint: "Hint") -> "Optional[Event]":
229223

230224
if hasattr(gcp_event, "query_string"):
231225
query_string = gcp_event.query_string.decode("utf-8", errors="replace")
232-
if has_data_collection_enabled(client_options):
233-
if query_string:
234-
filtered_qs = _apply_data_collection_filtering_to_query_string(
235-
query_string=query_string,
236-
behaviour=client_options["data_collection"]["url_query_params"],
237-
)
238-
if filtered_qs:
239-
request["query_string"] = filtered_qs
240-
else:
241-
request["query_string"] = query_string
226+
if query_string:
227+
filtered_qs = _apply_data_collection_filtering_to_query_string(
228+
query_string=query_string,
229+
behaviour=client_options["data_collection"]["url_query_params"],
230+
)
231+
if filtered_qs:
232+
request["query_string"] = filtered_qs
242233

243234
if hasattr(gcp_event, "headers"):
244235
request["headers"] = _filter_headers(gcp_event.headers)
245236

246237
if hasattr(gcp_event, "data"):
247-
if has_data_collection_enabled(client_options):
248-
if (
249-
"incoming_request"
250-
in client_options["data_collection"]["http_bodies"]
251-
):
252-
request["data"] = gcp_event.data
253-
elif should_send_default_pii():
238+
if "incoming_request" in client_options["data_collection"]["http_bodies"]:
254239
request["data"] = gcp_event.data
255240
else:
256241
# Unfortunately couldn't find a way to get structured body from GCP

‎tests/integrations/gcp/test_gcp.py‎

Lines changed: 15 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ def cloud_function(functionhandler, event):
613613
+ FUNCTIONS_PRELUDE
614614
+ dedent(
615615
"""
616-
init_sdk(traces_sample_rate=1.0, send_default_pii=True)
616+
init_sdk(traces_sample_rate=1.0, data_collection={})
617617
gcp_functions.worker_v1.FunctionHandler.invoke_user_function(functionhandler, event)
618618
"""
619619
)
@@ -633,131 +633,53 @@ def cloud_function(functionhandler, event):
633633
assert attrs["faas.entry_point"] == "cloud_function"
634634

635635

636-
def test_no_query_string_without_pii(run_cloud_function):
637-
_, _, span_items = run_cloud_function(
638-
dedent(
639-
"""
640-
functionhandler = None
641-
642-
from collections import namedtuple
643-
GCPEvent = namedtuple("GCPEvent", ["headers", "method", "query_string"])
644-
event = GCPEvent(
645-
headers={},
646-
method="GET",
647-
query_string=b"secret=hunter2",
648-
)
649-
650-
def cloud_function(functionhandler, event):
651-
return "ok"
652-
"""
653-
)
654-
+ FUNCTIONS_PRELUDE
655-
+ dedent(
656-
"""
657-
init_sdk(traces_sample_rate=1.0, send_default_pii=False)
658-
gcp_functions.worker_v1.FunctionHandler.invoke_user_function(functionhandler, event)
659-
"""
660-
)
661-
)
662-
663-
assert len(span_items) == 1
664-
segment_span = span_items[0]
665-
attrs = segment_span["attributes"]
666-
667-
assert "url.query" not in attrs
668-
assert attrs["http.request.method"] == "GET"
669-
assert attrs["faas.name"] == "Google Cloud function"
670-
assert attrs["gcp.project.id"] == "serverless_project"
671-
assert attrs["faas.identity"] == "func_ID"
672-
assert attrs["faas.entry_point"] == "cloud_function"
673-
674-
675-
# Each case is (send_default_pii, data_collection, expected_event, expected_span).
676-
# ``send_default_pii`` / ``data_collection`` of None means the option is omitted.
677-
# The two paths only diverge for the legacy (no ``data_collection``) rows: the
678-
# event processor always records the raw query string, while the span-streaming
679-
# path gates the ``url.query`` attribute on ``send_default_pii``.
636+
# Each case is (data_collection, expected_span). ``data_collection`` of None
637+
# means the option is omitted.
680638
_QUERY_STRING_DATA_COLLECTION_CASES = [
681639
pytest.param(
682-
True,
683-
None,
684-
"toy=tennisball&color=red&auth=secret",
685-
"toy=tennisball&color=red&auth=secret",
686-
id="send_default_pii_true",
687-
),
688-
pytest.param(
689-
False,
690-
None,
691-
"toy=tennisball&color=red&auth=secret",
692-
None,
693-
id="send_default_pii_false",
694-
),
695-
pytest.param(
696-
None,
697-
None,
698-
"toy=tennisball&color=red&auth=secret",
699-
None,
700-
id="defaults",
701-
),
702-
# data_collection configured: query string is routed through filtering.
703-
# Spec defaults -> denylist: only the sensitive ``auth`` is redacted.
704-
pytest.param(
705-
None,
706640
{},
707641
"toy=tennisball&color=red&auth=%5BFiltered%5D",
708-
"toy=tennisball&color=red&auth=%5BFiltered%5D",
709642
id="data_collection_denylist_default",
710643
),
711644
pytest.param(
712-
None,
713645
{"url_query_params": {"mode": "denylist", "terms": ["toy"]}},
714646
"toy=%5BFiltered%5D&color=red&auth=%5BFiltered%5D",
715-
"toy=%5BFiltered%5D&color=red&auth=%5BFiltered%5D",
716647
id="data_collection_denylist_custom_terms",
717648
),
718649
# allowlist with only ``toy`` allowed: ``color`` is redacted even though it
719650
# is not sensitive, proving the redaction comes from the allowlist.
720651
pytest.param(
721-
None,
722652
{"url_query_params": {"mode": "allowlist", "terms": ["toy"]}},
723653
"toy=tennisball&color=%5BFiltered%5D&auth=%5BFiltered%5D",
724-
"toy=tennisball&color=%5BFiltered%5D&auth=%5BFiltered%5D",
725654
id="data_collection_allowlist",
726655
),
727656
pytest.param(
728-
None,
729657
{"url_query_params": {"mode": "off"}},
730658
None,
731-
None,
732659
id="data_collection_off",
733660
),
734661
]
735662

736663

737-
def _build_init_kwargs(send_default_pii, data_collection):
664+
def _build_init_kwargs(data_collection):
738665
"""Render the keyword-argument string passed to ``init_sdk`` in the
739666
subprocess."""
740-
kwargs = []
741-
if send_default_pii is not None:
742-
kwargs.append("send_default_pii=%r" % send_default_pii)
743-
if data_collection is not None:
744-
kwargs.append("data_collection=%r" % (data_collection,))
667+
if data_collection is None:
668+
return ""
745669

746-
return ", ".join(kwargs)
670+
return "data_collection=%r" % (data_collection,)
747671

748672

749673
@pytest.mark.parametrize(
750-
"send_default_pii, data_collection, expected_event, expected_span",
674+
"data_collection, expected_span",
751675
_QUERY_STRING_DATA_COLLECTION_CASES,
752676
)
753677
def test_query_string_data_collection(
754678
run_cloud_function,
755-
send_default_pii,
756679
data_collection,
757-
expected_event,
758680
expected_span,
759681
):
760-
init_kwargs = _build_init_kwargs(send_default_pii, data_collection)
682+
init_kwargs = _build_init_kwargs(data_collection)
761683
_, _, span_items = run_cloud_function(
762684
dedent(
763685
"""
@@ -793,71 +715,37 @@ def cloud_function(functionhandler, event):
793715
assert attrs["url.query"] == expected_span
794716

795717

796-
# Each case is (send_default_pii, data_collection, expected_data, expected_meta).
797-
# ``send_default_pii`` / ``data_collection`` of None means the option is omitted;
798-
# ``expected_data`` of None means the body is not attached to the event at all.
718+
# Each case is (data_collection, expected_data, expected_meta).
799719
@pytest.mark.parametrize(
800-
"send_default_pii, data_collection, expected_data, expected_meta",
720+
"data_collection, expected_data, expected_meta",
801721
[
802722
pytest.param(
803-
True,
804-
None,
805-
'{"toy": "tennisball"}',
806-
None,
807-
id="send_default_pii_true_attaches_body",
808-
),
809-
pytest.param(
810-
False,
811-
None,
812-
"",
813-
{"": {"rem": [["!raw", "x"]]}},
814-
id="send_default_pii_false_annotates_body_as_raw",
815-
),
816-
pytest.param(
817-
None,
818723
{},
819724
'{"toy": "tennisball"}',
820725
None,
821726
id="http_bodies_default_attaches_body",
822727
),
823728
pytest.param(
824-
None,
825729
{"http_bodies": ["incoming_request"]},
826730
'{"toy": "tennisball"}',
827731
None,
828732
id="http_bodies_incoming_request_attaches_body",
829733
),
830734
pytest.param(
831-
None,
832-
{"http_bodies": []},
833-
None,
834-
None,
835-
id="http_bodies_empty_omits_body",
836-
),
837-
pytest.param(
838-
False,
839-
{"http_bodies": ["incoming_request"]},
840-
'{"toy": "tennisball"}',
841-
None,
842-
id="http_bodies_incoming_request_attaches_body_despite_send_default_pii_false",
843-
),
844-
pytest.param(
845-
True,
846735
{"http_bodies": []},
847-
None,
848-
None,
849-
id="http_bodies_empty_omits_body_despite_send_default_pii_true",
736+
"",
737+
{"": {"rem": [["!raw", "x"]]}},
738+
id="http_bodies_empty_annotates_body_as_raw",
850739
),
851740
],
852741
)
853742
def test_request_body_data_collection_event_processor(
854743
run_cloud_function,
855-
send_default_pii,
856744
data_collection,
857745
expected_data,
858746
expected_meta,
859747
):
860-
init_kwargs = _build_init_kwargs(send_default_pii, data_collection)
748+
init_kwargs = _build_init_kwargs(data_collection)
861749
envelope_items, _, _ = run_cloud_function(
862750
dedent(
863751
"""

0 commit comments

Comments
 (0)