aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/sentry_sdk/scrubber.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/sentry_sdk/scrubber.py')
-rw-r--r--.venv/lib/python3.12/site-packages/sentry_sdk/scrubber.py174
1 files changed, 174 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/sentry_sdk/scrubber.py b/.venv/lib/python3.12/site-packages/sentry_sdk/scrubber.py
new file mode 100644
index 00000000..1df55737
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sentry_sdk/scrubber.py
@@ -0,0 +1,174 @@
+from sentry_sdk.utils import (
+ capture_internal_exceptions,
+ AnnotatedValue,
+ iter_event_frames,
+)
+
+from typing import TYPE_CHECKING, cast, List, Dict
+
+if TYPE_CHECKING:
+ from sentry_sdk._types import Event
+ from typing import Optional
+
+
+DEFAULT_DENYLIST = [
+ # stolen from relay
+ "password",
+ "passwd",
+ "secret",
+ "api_key",
+ "apikey",
+ "auth",
+ "credentials",
+ "mysql_pwd",
+ "privatekey",
+ "private_key",
+ "token",
+ "session",
+ # django
+ "csrftoken",
+ "sessionid",
+ # wsgi
+ "x_csrftoken",
+ "x_forwarded_for",
+ "set_cookie",
+ "cookie",
+ "authorization",
+ "x_api_key",
+ # other common names used in the wild
+ "aiohttp_session", # aiohttp
+ "connect.sid", # Express
+ "csrf_token", # Pyramid
+ "csrf", # (this is a cookie name used in accepted answers on stack overflow)
+ "_csrf", # Express
+ "_csrf_token", # Bottle
+ "PHPSESSID", # PHP
+ "_session", # Sanic
+ "symfony", # Symfony
+ "user_session", # Vue
+ "_xsrf", # Tornado
+ "XSRF-TOKEN", # Angular, Laravel
+]
+
+DEFAULT_PII_DENYLIST = [
+ "x_forwarded_for",
+ "x_real_ip",
+ "ip_address",
+ "remote_addr",
+]
+
+
+class EventScrubber:
+ def __init__(
+ self, denylist=None, recursive=False, send_default_pii=False, pii_denylist=None
+ ):
+ # type: (Optional[List[str]], bool, bool, Optional[List[str]]) -> None
+ """
+ A scrubber that goes through the event payload and removes sensitive data configured through denylists.
+
+ :param denylist: A security denylist that is always scrubbed, defaults to DEFAULT_DENYLIST.
+ :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.
+ """
+ self.denylist = DEFAULT_DENYLIST.copy() if denylist is None else denylist
+
+ if not send_default_pii:
+ pii_denylist = (
+ DEFAULT_PII_DENYLIST.copy() if pii_denylist is None else pii_denylist
+ )
+ self.denylist += pii_denylist
+
+ self.denylist = [x.lower() for x in self.denylist]
+ self.recursive = recursive
+
+ def scrub_list(self, lst):
+ # type: (object) -> None
+ """
+ If a list is passed to this method, the method recursively searches the list and any
+ nested lists for any dictionaries. The method calls scrub_dict on all dictionaries
+ it finds.
+ If the parameter passed to this method is not a list, the method does nothing.
+ """
+ if not isinstance(lst, list):
+ return
+
+ for v in lst:
+ self.scrub_dict(v) # no-op unless v is a dict
+ self.scrub_list(v) # no-op unless v is a list
+
+ def scrub_dict(self, d):
+ # type: (object) -> None
+ """
+ If a dictionary is passed to this method, the method scrubs the dictionary of any
+ sensitive data. The method calls itself recursively on any nested dictionaries (
+ including dictionaries nested in lists) if self.recursive is True.
+ This method does nothing if the parameter passed to it is not a dictionary.
+ """
+ if not isinstance(d, dict):
+ return
+
+ for k, v in d.items():
+ # The cast is needed because mypy is not smart enough to figure out that k must be a
+ # string after the isinstance check.
+ if isinstance(k, str) and k.lower() in self.denylist:
+ d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
+ elif self.recursive:
+ self.scrub_dict(v) # no-op unless v is a dict
+ self.scrub_list(v) # no-op unless v is a list
+
+ 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 cast(List[Dict[str, object]], 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)