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
137 lines
4.3 KiB
""" Custom class of raven.core.processors taken of https://git.io/JITko
|
|
This is a custom class of processor to filter and sanitize
|
|
passwords and keys from request data, it does not exist in
|
|
sentry-sdk.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
|
|
import re
|
|
|
|
from sentry_sdk._compat import text_type
|
|
from .generalutils import string_types, varmap
|
|
|
|
|
|
class SanitizeKeysProcessor(object):
|
|
""" Class from raven for sanitize keys, cookies, etc
|
|
Asterisk out things that correspond to a configurable set of keys. """
|
|
|
|
MASK = '*' * 8
|
|
|
|
def process(self, data, **kwargs):
|
|
if 'exception' in data:
|
|
if 'values' in data['exception']:
|
|
for value in data['exception'].get('values', []):
|
|
if 'stacktrace' in value:
|
|
self.filter_stacktrace(value['stacktrace'])
|
|
|
|
if 'request' in data:
|
|
self.filter_http(data['request'])
|
|
|
|
if 'extra' in data:
|
|
data['extra'] = self.filter_extra(data['extra'])
|
|
|
|
if 'level' in data:
|
|
data['level'] = self.filter_level(data['level'])
|
|
|
|
return data
|
|
|
|
@property
|
|
def sanitize_keys(self):
|
|
pass
|
|
|
|
def sanitize(self, item, value):
|
|
if value is None:
|
|
return
|
|
|
|
if not item: # key can be a NoneType
|
|
return value
|
|
|
|
# Just in case we have bytes here, we want to make them into text
|
|
# properly without failing so we can perform our check.
|
|
if isinstance(item, bytes):
|
|
item = item.decode('utf-8', 'replace')
|
|
else:
|
|
item = text_type(item)
|
|
|
|
item = item.lower()
|
|
for key in self.sanitize_keys:
|
|
if key in item:
|
|
# store mask as a fixed length for security
|
|
return self.MASK
|
|
return value
|
|
|
|
def filter_stacktrace(self, data):
|
|
for frame in data.get('frames', []):
|
|
if 'vars' not in frame:
|
|
continue
|
|
frame['vars'] = varmap(self.sanitize, frame['vars'])
|
|
|
|
def filter_http(self, data):
|
|
for n in ('data', 'cookies', 'headers', 'env', 'query_string'):
|
|
if n not in data:
|
|
continue
|
|
|
|
# data could be provided as bytes and if it's python3
|
|
if isinstance(data[n], bytes):
|
|
data[n] = data[n].decode('utf-8', 'replace')
|
|
|
|
if isinstance(data[n], string_types()) and '=' in data[n]:
|
|
# at this point we've assumed it's a standard HTTP query
|
|
# or cookie
|
|
if n == 'cookies':
|
|
delimiter = ';'
|
|
else:
|
|
delimiter = '&'
|
|
|
|
data[n] = self._sanitize_keyvals(data[n], delimiter)
|
|
else:
|
|
data[n] = varmap(self.sanitize, data[n])
|
|
if n == 'headers' and 'Cookie' in data[n]:
|
|
data[n]['Cookie'] = self._sanitize_keyvals(
|
|
data[n]['Cookie'], ';'
|
|
)
|
|
|
|
def filter_extra(self, data):
|
|
return varmap(self.sanitize, data)
|
|
|
|
def filter_level(self, data):
|
|
return re.sub(r'\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))', '', data)
|
|
|
|
def _sanitize_keyvals(self, keyvals, delimiter):
|
|
sanitized_keyvals = []
|
|
for keyval in keyvals.split(delimiter):
|
|
keyval = keyval.split('=')
|
|
if len(keyval) == 2:
|
|
sanitized_keyvals.append((keyval[0], self.sanitize(*keyval)))
|
|
else:
|
|
sanitized_keyvals.append(keyval)
|
|
|
|
return delimiter.join('='.join(keyval) for keyval in sanitized_keyvals)
|
|
|
|
|
|
class SanitizePasswordsProcessor(SanitizeKeysProcessor):
|
|
""" Asterisk out things that look like passwords, credit card numbers,
|
|
and API keys in frames, http, and basic extra data. """
|
|
|
|
KEYS = frozenset([
|
|
'password',
|
|
'secret',
|
|
'passwd',
|
|
'authorization',
|
|
'api_key',
|
|
'apikey',
|
|
'sentry_dsn',
|
|
'access_token',
|
|
])
|
|
VALUES_RE = re.compile(r'^(?:\d[ -]*?){13,16}$')
|
|
|
|
@property
|
|
def sanitize_keys(self):
|
|
return self.KEYS
|
|
|
|
def sanitize(self, item, value):
|
|
value = super(SanitizePasswordsProcessor, self).sanitize(item, value)
|
|
if isinstance(value, string_types()) and self.VALUES_RE.match(value):
|
|
return self.MASK
|
|
return value
|