diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index c4be3331fa..b3982471e7 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -29,6 +29,7 @@ from sentry_sdk.sessions import SessionFlusher from sentry_sdk.envelope import Envelope from sentry_sdk.profiler import has_profiling_enabled, setup_profiler +from sentry_sdk.scrubber import EventScrubber from sentry_sdk._types import TYPE_CHECKING @@ -111,6 +112,9 @@ def _get_options(*args, **kwargs): if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None: rv["traces_sample_rate"] = 1.0 + if rv["event_scrubber"] is None: + rv["event_scrubber"] = EventScrubber() + return rv @@ -249,6 +253,11 @@ def _prepare_event( self.options["project_root"], ) + if event is not None: + event_scrubber = self.options["event_scrubber"] + if event_scrubber and not self.options["send_default_pii"]: + event_scrubber.scrub_event(event) + # Postprocess the event here so that annotated types do # generally not surface in before_send if event is not None: diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 1a8fc99e5d..c217309bdf 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -133,6 +133,7 @@ def __init__( trace_propagation_targets=[ # noqa: B006 MATCH_ALL ], # type: Optional[Sequence[str]] + event_scrubber=None, # type: Optional[sentry_sdk.scrubber.EventScrubber] ): # type: (...) -> None pass diff --git a/sentry_sdk/scrubber.py b/sentry_sdk/scrubber.py new file mode 100644 index 0000000000..028d69cd4a --- /dev/null +++ b/sentry_sdk/scrubber.py @@ -0,0 +1,115 @@ +from sentry_sdk.utils import ( + capture_internal_exceptions, + AnnotatedValue, + iter_event_frames, +) +from sentry_sdk._compat import string_types +from sentry_sdk._types import TYPE_CHECKING + +if TYPE_CHECKING: + from sentry_sdk._types import Event + from typing import Any + from typing import Dict + from typing import List + from typing import Optional + + +DEFAULT_DENYLIST = [ + # stolen from relay + "password", + "passwd", + "secret", + "api_key", + "apikey", + "auth", + "credentials", + "mysql_pwd", + "privatekey", + "private_key", + "token", + "ip_address", + # django + "csrftoken", + "sessionid", + # wsgi + "remote_addr", + "http_x_csrftoken", + "http_x_forwarded_for", + "http_set_cookie", + "http_cookie", + "http_authorization", + "http_x_api_key", + "http_x_forwarded_for", + "http_x_real_ip", +] + + +class EventScrubber(object): + def __init__(self, denylist=None): + # type: (Optional[List[str]]) -> None + self.denylist = DEFAULT_DENYLIST if denylist is None else denylist + + def scrub_dict(self, d): + # type: (Dict[str, Any]) -> None + if not isinstance(d, dict): + return + + for k in d.keys(): + if isinstance(k, string_types) and k.lower() in self.denylist: + d[k] = AnnotatedValue.substituted_because_contains_sensitive_data() + + def scrub_request(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "request" in event: + if "headers" in event["request"]: + self.scrub_dict(event["request"]["headers"]) + if "cookies" in event["request"]: + self.scrub_dict(event["request"]["cookies"]) + if "data" in event["request"]: + self.scrub_dict(event["request"]["data"]) + + def scrub_extra(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "extra" in event: + self.scrub_dict(event["extra"]) + + def scrub_user(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "user" in event: + self.scrub_dict(event["user"]) + + def scrub_breadcrumbs(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "breadcrumbs" in event: + if "values" in event["breadcrumbs"]: + for value in event["breadcrumbs"]["values"]: + if "data" in value: + self.scrub_dict(value["data"]) + + def scrub_frames(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + for frame in iter_event_frames(event): + if "vars" in frame: + self.scrub_dict(frame["vars"]) + + def scrub_spans(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "spans" in event: + for span in event["spans"]: + if "data" in span: + self.scrub_dict(span["data"]) + + def scrub_event(self, event): + # type: (Event) -> None + self.scrub_request(event) + self.scrub_extra(event) + self.scrub_user(event) + self.scrub_breadcrumbs(event) + self.scrub_frames(event) + self.scrub_spans(event)