139 lines
4.3 KiB
Python
139 lines
4.3 KiB
Python
""" 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
|