Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

178 lignes
5.9 KiB

  1. from sentry_sdk.utils import (
  2. capture_internal_exceptions,
  3. AnnotatedValue,
  4. iter_event_frames,
  5. )
  6. from typing import TYPE_CHECKING, cast, List, Dict
  7. if TYPE_CHECKING:
  8. from sentry_sdk._types import Event
  9. from typing import Optional
  10. DEFAULT_DENYLIST = [
  11. # stolen from relay
  12. "password",
  13. "passwd",
  14. "secret",
  15. "api_key",
  16. "apikey",
  17. "auth",
  18. "credentials",
  19. "mysql_pwd",
  20. "privatekey",
  21. "private_key",
  22. "token",
  23. "session",
  24. # django
  25. "csrftoken",
  26. "sessionid",
  27. # wsgi
  28. "x_csrftoken",
  29. "x_forwarded_for",
  30. "set_cookie",
  31. "cookie",
  32. "authorization",
  33. "x_api_key",
  34. # other common names used in the wild
  35. "aiohttp_session", # aiohttp
  36. "connect.sid", # Express
  37. "csrf_token", # Pyramid
  38. "csrf", # (this is a cookie name used in accepted answers on stack overflow)
  39. "_csrf", # Express
  40. "_csrf_token", # Bottle
  41. "PHPSESSID", # PHP
  42. "_session", # Sanic
  43. "symfony", # Symfony
  44. "user_session", # Vue
  45. "_xsrf", # Tornado
  46. "XSRF-TOKEN", # Angular, Laravel
  47. ]
  48. DEFAULT_PII_DENYLIST = [
  49. "x_forwarded_for",
  50. "x_real_ip",
  51. "ip_address",
  52. "remote_addr",
  53. ]
  54. class EventScrubber:
  55. def __init__(
  56. self, denylist=None, recursive=False, send_default_pii=False, pii_denylist=None
  57. ):
  58. # type: (Optional[List[str]], bool, bool, Optional[List[str]]) -> None
  59. """
  60. A scrubber that goes through the event payload and removes sensitive data configured through denylists.
  61. :param denylist: A security denylist that is always scrubbed, defaults to DEFAULT_DENYLIST.
  62. :param recursive: Whether to scrub the event payload recursively, default False.
  63. :param send_default_pii: Whether pii is sending is on, pii fields are not scrubbed.
  64. :param pii_denylist: The denylist to use for scrubbing when pii is not sent, defaults to DEFAULT_PII_DENYLIST.
  65. """
  66. self.denylist = DEFAULT_DENYLIST.copy() if denylist is None else denylist
  67. if not send_default_pii:
  68. pii_denylist = (
  69. DEFAULT_PII_DENYLIST.copy() if pii_denylist is None else pii_denylist
  70. )
  71. self.denylist += pii_denylist
  72. self.denylist = [x.lower() for x in self.denylist]
  73. self.recursive = recursive
  74. def scrub_list(self, lst):
  75. # type: (object) -> None
  76. """
  77. If a list is passed to this method, the method recursively searches the list and any
  78. nested lists for any dictionaries. The method calls scrub_dict on all dictionaries
  79. it finds.
  80. If the parameter passed to this method is not a list, the method does nothing.
  81. """
  82. if not isinstance(lst, list):
  83. return
  84. for v in lst:
  85. self.scrub_dict(v) # no-op unless v is a dict
  86. self.scrub_list(v) # no-op unless v is a list
  87. def scrub_dict(self, d):
  88. # type: (object) -> None
  89. """
  90. If a dictionary is passed to this method, the method scrubs the dictionary of any
  91. sensitive data. The method calls itself recursively on any nested dictionaries (
  92. including dictionaries nested in lists) if self.recursive is True.
  93. This method does nothing if the parameter passed to it is not a dictionary.
  94. """
  95. if not isinstance(d, dict):
  96. return
  97. for k, v in d.items():
  98. # The cast is needed because mypy is not smart enough to figure out that k must be a
  99. # string after the isinstance check.
  100. if isinstance(k, str) and k.lower() in self.denylist:
  101. d[k] = AnnotatedValue.substituted_because_contains_sensitive_data()
  102. elif self.recursive:
  103. self.scrub_dict(v) # no-op unless v is a dict
  104. self.scrub_list(v) # no-op unless v is a list
  105. def scrub_request(self, event):
  106. # type: (Event) -> None
  107. with capture_internal_exceptions():
  108. if "request" in event:
  109. if "headers" in event["request"]:
  110. self.scrub_dict(event["request"]["headers"])
  111. if "cookies" in event["request"]:
  112. self.scrub_dict(event["request"]["cookies"])
  113. if "data" in event["request"]:
  114. self.scrub_dict(event["request"]["data"])
  115. def scrub_extra(self, event):
  116. # type: (Event) -> None
  117. with capture_internal_exceptions():
  118. if "extra" in event:
  119. self.scrub_dict(event["extra"])
  120. def scrub_user(self, event):
  121. # type: (Event) -> None
  122. with capture_internal_exceptions():
  123. if "user" in event:
  124. self.scrub_dict(event["user"])
  125. def scrub_breadcrumbs(self, event):
  126. # type: (Event) -> None
  127. with capture_internal_exceptions():
  128. if "breadcrumbs" in event:
  129. if (
  130. not isinstance(event["breadcrumbs"], AnnotatedValue)
  131. and "values" in event["breadcrumbs"]
  132. ):
  133. for value in event["breadcrumbs"]["values"]:
  134. if "data" in value:
  135. self.scrub_dict(value["data"])
  136. def scrub_frames(self, event):
  137. # type: (Event) -> None
  138. with capture_internal_exceptions():
  139. for frame in iter_event_frames(event):
  140. if "vars" in frame:
  141. self.scrub_dict(frame["vars"])
  142. def scrub_spans(self, event):
  143. # type: (Event) -> None
  144. with capture_internal_exceptions():
  145. if "spans" in event:
  146. for span in cast(List[Dict[str, object]], event["spans"]):
  147. if "data" in span:
  148. self.scrub_dict(span["data"])
  149. def scrub_event(self, event):
  150. # type: (Event) -> None
  151. self.scrub_request(event)
  152. self.scrub_extra(event)
  153. self.scrub_user(event)
  154. self.scrub_breadcrumbs(event)
  155. self.scrub_frames(event)
  156. self.scrub_spans(event)