From 26e6a2288944ba1cdb4602b989eacfc11b233b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Su=C3=A1stegui?= Date: Fri, 8 May 2026 09:50:45 -0600 Subject: [PATCH 1/3] Add option to drop scrubbed user IP addresses --- sentry_sdk/scrubber.py | 12 +++++++++++- tests/test_scrubber.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py index f863092108..c91b6b3b89 100644 --- a/sentry_sdk/scrubber.py +++ b/sentry_sdk/scrubber.py @@ -66,6 +66,7 @@ def __init__( recursive: bool = False, send_default_pii: bool = False, pii_denylist: "Optional[List[str]]" = None, + remove_user_ip_address: bool = False, ) -> None: """ A scrubber that goes through the event payload and removes sensitive data configured through denylists. @@ -74,6 +75,7 @@ def __init__( :param recursive: Whether to scrub the event payload recursively, default False. :param send_default_pii: Whether pii is sending is on, pii fields are not scrubbed. :param pii_denylist: The denylist to use for scrubbing when pii is not sent, defaults to DEFAULT_PII_DENYLIST. + :param remove_user_ip_address: Whether to remove ``user.ip_address`` instead of replacing it with ``[Filtered]``. """ self.denylist = DEFAULT_DENYLIST.copy() if denylist is None else denylist @@ -85,6 +87,7 @@ def __init__( self.denylist = [x.lower() for x in self.denylist] self.recursive = recursive + self.remove_user_ip_address = remove_user_ip_address def scrub_list(self, lst: object) -> None: """ @@ -137,7 +140,14 @@ def scrub_extra(self, event: "Event") -> None: def scrub_user(self, event: "Event") -> None: with capture_internal_exceptions(): if "user" in event: - self.scrub_dict(event["user"]) + user = event["user"] + if ( + self.remove_user_ip_address + and "ip_address" in self.denylist + and isinstance(user, dict) + ): + user.pop("ip_address", None) + self.scrub_dict(user) def scrub_breadcrumbs(self, event: "Event") -> None: with capture_internal_exceptions(): diff --git a/tests/test_scrubber.py b/tests/test_scrubber.py index 2cc5f4139f..2cacefb148 100644 --- a/tests/test_scrubber.py +++ b/tests/test_scrubber.py @@ -89,6 +89,42 @@ def test_ip_address_not_scrubbed_when_pii_enabled(sentry_init, capture_events): } +def test_user_ip_address_scrubbed_when_pii_disabled(sentry_init, capture_events): + sentry_init() + events = capture_events() + + try: + 1 / 0 + except ZeroDivisionError: + ev, _hint = event_from_exception(sys.exc_info()) + ev["user"] = {"id": "42", "ip_address": "127.0.0.1"} + + capture_event(ev) + + (event,) = events + + assert event["user"] == {"id": "42", "ip_address": "[Filtered]"} + assert event["_meta"]["user"] == {"ip_address": {"": {"rem": [["!config", "s"]]}}} + + +def test_user_ip_address_removed_when_configured(sentry_init, capture_events): + sentry_init(event_scrubber=EventScrubber(remove_user_ip_address=True)) + events = capture_events() + + try: + 1 / 0 + except ZeroDivisionError: + ev, _hint = event_from_exception(sys.exc_info()) + ev["user"] = {"id": "42", "ip_address": "127.0.0.1"} + + capture_event(ev) + + (event,) = events + + assert event["user"] == {"id": "42"} + assert "user" not in event.get("_meta", {}) + + def test_stack_var_scrubbing(sentry_init, capture_events): sentry_init() events = capture_events() From cff08d07979792ce57e8f00b740994c040ff23dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Su=C3=A1stegui?= Date: Sun, 10 May 2026 03:53:59 -0600 Subject: [PATCH 2/3] ref: check IP denylist before user dict guard --- sentry_sdk/scrubber.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py index c91b6b3b89..bfdf415e18 100644 --- a/sentry_sdk/scrubber.py +++ b/sentry_sdk/scrubber.py @@ -142,8 +142,8 @@ def scrub_user(self, event: "Event") -> None: if "user" in event: user = event["user"] if ( - self.remove_user_ip_address - and "ip_address" in self.denylist + "ip_address" in self.denylist + and self.remove_user_ip_address and isinstance(user, dict) ): user.pop("ip_address", None) From 6c91d53df109b81b49a9cfd2c5d70568359bedd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20C=C3=A9sar=20Su=C3=A1stegui?= Date: Mon, 11 May 2026 10:34:56 -0600 Subject: [PATCH 3/3] Drop scrubbed user IP addresses --- sentry_sdk/scrubber.py | 9 +-------- .../test_new_scopes_compat_event.py | 4 ---- tests/test_scrubber.py | 12 ++++++------ 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py index bfdf415e18..b340b35ad9 100644 --- a/sentry_sdk/scrubber.py +++ b/sentry_sdk/scrubber.py @@ -66,7 +66,6 @@ def __init__( recursive: bool = False, send_default_pii: bool = False, pii_denylist: "Optional[List[str]]" = None, - remove_user_ip_address: bool = False, ) -> None: """ A scrubber that goes through the event payload and removes sensitive data configured through denylists. @@ -75,7 +74,6 @@ def __init__( :param recursive: Whether to scrub the event payload recursively, default False. :param send_default_pii: Whether pii is sending is on, pii fields are not scrubbed. :param pii_denylist: The denylist to use for scrubbing when pii is not sent, defaults to DEFAULT_PII_DENYLIST. - :param remove_user_ip_address: Whether to remove ``user.ip_address`` instead of replacing it with ``[Filtered]``. """ self.denylist = DEFAULT_DENYLIST.copy() if denylist is None else denylist @@ -87,7 +85,6 @@ def __init__( self.denylist = [x.lower() for x in self.denylist] self.recursive = recursive - self.remove_user_ip_address = remove_user_ip_address def scrub_list(self, lst: object) -> None: """ @@ -141,11 +138,7 @@ def scrub_user(self, event: "Event") -> None: with capture_internal_exceptions(): if "user" in event: user = event["user"] - if ( - "ip_address" in self.denylist - and self.remove_user_ip_address - and isinstance(user, dict) - ): + if "ip_address" in self.denylist and isinstance(user, dict): user.pop("ip_address", None) self.scrub_dict(user) diff --git a/tests/new_scopes_compat/test_new_scopes_compat_event.py b/tests/new_scopes_compat/test_new_scopes_compat_event.py index db1e5fec4b..79cdf1fc28 100644 --- a/tests/new_scopes_compat/test_new_scopes_compat_event.py +++ b/tests/new_scopes_compat/test_new_scopes_compat_event.py @@ -102,7 +102,6 @@ def create_expected_error_event(trx, span): "user": { "id": "123", "email": "jane.doe@example.com", - "ip_address": "[Filtered]", }, "transaction": "test_transaction", "transaction_info": {"source": "custom"}, @@ -137,7 +136,6 @@ def create_expected_error_event(trx, span): }, "platform": "python", "_meta": { - "user": {"ip_address": {"": {"rem": [["!config", "s"]]}}}, "extra": { "should_be_removed_by_event_scrubber": { "": {"rem": [["!config", "s"]]} @@ -207,7 +205,6 @@ def create_expected_transaction_event(trx, span): "user": { "id": "123", "email": "jane.doe@example.com", - "ip_address": "[Filtered]", }, "extra": { "extra1": "extra1_value", @@ -226,7 +223,6 @@ def create_expected_transaction_event(trx, span): }, "platform": "python", "_meta": { - "user": {"ip_address": {"": {"rem": [["!config", "s"]]}}}, "extra": { "should_be_removed_by_event_scrubber": { "": {"rem": [["!config", "s"]]} diff --git a/tests/test_scrubber.py b/tests/test_scrubber.py index 2cacefb148..01e26cf7a9 100644 --- a/tests/test_scrubber.py +++ b/tests/test_scrubber.py @@ -89,7 +89,7 @@ def test_ip_address_not_scrubbed_when_pii_enabled(sentry_init, capture_events): } -def test_user_ip_address_scrubbed_when_pii_disabled(sentry_init, capture_events): +def test_user_ip_address_removed_when_pii_disabled(sentry_init, capture_events): sentry_init() events = capture_events() @@ -103,12 +103,12 @@ def test_user_ip_address_scrubbed_when_pii_disabled(sentry_init, capture_events) (event,) = events - assert event["user"] == {"id": "42", "ip_address": "[Filtered]"} - assert event["_meta"]["user"] == {"ip_address": {"": {"rem": [["!config", "s"]]}}} + assert event["user"] == {"id": "42"} + assert "user" not in event.get("_meta", {}) -def test_user_ip_address_removed_when_configured(sentry_init, capture_events): - sentry_init(event_scrubber=EventScrubber(remove_user_ip_address=True)) +def test_user_ip_address_not_removed_when_pii_enabled(sentry_init, capture_events): + sentry_init(send_default_pii=True) events = capture_events() try: @@ -121,7 +121,7 @@ def test_user_ip_address_removed_when_configured(sentry_init, capture_events): (event,) = events - assert event["user"] == {"id": "42"} + assert event["user"] == {"id": "42", "ip_address": "127.0.0.1"} assert "user" not in event.get("_meta", {})