You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
4.3 KiB

  1. """ Custom class of raven.core.processors taken of https://git.io/JITko
  2. This is a custom class of processor to filter and sanitize
  3. passwords and keys from request data, it does not exist in
  4. sentry-sdk.
  5. """
  6. from __future__ import absolute_import
  7. import re
  8. from sentry_sdk._compat import text_type
  9. from .generalutils import string_types, varmap
  10. class SanitizeKeysProcessor(object):
  11. """ Class from raven for sanitize keys, cookies, etc
  12. Asterisk out things that correspond to a configurable set of keys. """
  13. MASK = '*' * 8
  14. def process(self, data, **kwargs):
  15. if 'exception' in data:
  16. if 'values' in data['exception']:
  17. for value in data['exception'].get('values', []):
  18. if 'stacktrace' in value:
  19. self.filter_stacktrace(value['stacktrace'])
  20. if 'request' in data:
  21. self.filter_http(data['request'])
  22. if 'extra' in data:
  23. data['extra'] = self.filter_extra(data['extra'])
  24. if 'level' in data:
  25. data['level'] = self.filter_level(data['level'])
  26. return data
  27. @property
  28. def sanitize_keys(self):
  29. pass
  30. def sanitize(self, item, value):
  31. if value is None:
  32. return
  33. if not item: # key can be a NoneType
  34. return value
  35. # Just in case we have bytes here, we want to make them into text
  36. # properly without failing so we can perform our check.
  37. if isinstance(item, bytes):
  38. item = item.decode('utf-8', 'replace')
  39. else:
  40. item = text_type(item)
  41. item = item.lower()
  42. for key in self.sanitize_keys:
  43. if key in item:
  44. # store mask as a fixed length for security
  45. return self.MASK
  46. return value
  47. def filter_stacktrace(self, data):
  48. for frame in data.get('frames', []):
  49. if 'vars' not in frame:
  50. continue
  51. frame['vars'] = varmap(self.sanitize, frame['vars'])
  52. def filter_http(self, data):
  53. for n in ('data', 'cookies', 'headers', 'env', 'query_string'):
  54. if n not in data:
  55. continue
  56. # data could be provided as bytes and if it's python3
  57. if isinstance(data[n], bytes):
  58. data[n] = data[n].decode('utf-8', 'replace')
  59. if isinstance(data[n], string_types()) and '=' in data[n]:
  60. # at this point we've assumed it's a standard HTTP query
  61. # or cookie
  62. if n == 'cookies':
  63. delimiter = ';'
  64. else:
  65. delimiter = '&'
  66. data[n] = self._sanitize_keyvals(data[n], delimiter)
  67. else:
  68. data[n] = varmap(self.sanitize, data[n])
  69. if n == 'headers' and 'Cookie' in data[n]:
  70. data[n]['Cookie'] = self._sanitize_keyvals(
  71. data[n]['Cookie'], ';'
  72. )
  73. def filter_extra(self, data):
  74. return varmap(self.sanitize, data)
  75. def filter_level(self, data):
  76. return re.sub(r'\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))', '', data)
  77. def _sanitize_keyvals(self, keyvals, delimiter):
  78. sanitized_keyvals = []
  79. for keyval in keyvals.split(delimiter):
  80. keyval = keyval.split('=')
  81. if len(keyval) == 2:
  82. sanitized_keyvals.append((keyval[0], self.sanitize(*keyval)))
  83. else:
  84. sanitized_keyvals.append(keyval)
  85. return delimiter.join('='.join(keyval) for keyval in sanitized_keyvals)
  86. class SanitizePasswordsProcessor(SanitizeKeysProcessor):
  87. """ Asterisk out things that look like passwords, credit card numbers,
  88. and API keys in frames, http, and basic extra data. """
  89. KEYS = frozenset([
  90. 'password',
  91. 'secret',
  92. 'passwd',
  93. 'authorization',
  94. 'api_key',
  95. 'apikey',
  96. 'sentry_dsn',
  97. 'access_token',
  98. ])
  99. VALUES_RE = re.compile(r'^(?:\d[ -]*?){13,16}$')
  100. @property
  101. def sanitize_keys(self):
  102. return self.KEYS
  103. def sanitize(self, item, value):
  104. value = super(SanitizePasswordsProcessor, self).sanitize(item, value)
  105. if isinstance(value, string_types()) and self.VALUES_RE.match(value):
  106. return self.MASK
  107. return value