Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always remove Django session related cookies. #1842

Merged
merged 15 commits into from Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions sentry_sdk/consts.py
Expand Up @@ -44,6 +44,8 @@
DEFAULT_QUEUE_SIZE = 100
DEFAULT_MAX_BREADCRUMBS = 100

SENSITIVE_DATA_SUBSTITUTE = "[Filtered]"


class INSTRUMENTER:
SENTRY = "sentry"
Expand Down
20 changes: 17 additions & 3 deletions sentry_sdk/integrations/django/__init__.py
Expand Up @@ -6,13 +6,14 @@
import weakref

from sentry_sdk._types import MYPY
from sentry_sdk.consts import OP
from sentry_sdk.consts import OP, SENSITIVE_DATA_SUBSTITUTE
from sentry_sdk.hub import Hub, _should_send_default_pii
from sentry_sdk.scope import add_global_event_processor
from sentry_sdk.serializer import add_global_repr_processor
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_URL
from sentry_sdk.tracing_utils import record_sql_queries
from sentry_sdk.utils import (
AnnotatedValue,
HAS_REAL_CONTEXTVARS,
CONTEXTVARS_ERROR_MESSAGE,
logger,
Expand All @@ -28,6 +29,7 @@

try:
from django import VERSION as DJANGO_VERSION
from django.conf import settings as django_settings
from django.core import signals

try:
Expand Down Expand Up @@ -476,8 +478,20 @@ def env(self):
return self.request.META

def cookies(self):
# type: () -> Dict[str, str]
return self.request.COOKIES
# type: () -> Dict[str, Union[str, AnnotatedValue]]
privacy_cookies = [
django_settings.CSRF_COOKIE_NAME,
django_settings.SESSION_COOKIE_NAME,
]

clean_cookies = {} # type: Dict[str, Union[str, AnnotatedValue]]
for (key, val) in self.request.COOKIES.items():
if key in privacy_cookies:
clean_cookies[key] = SENSITIVE_DATA_SUBSTITUTE
else:
clean_cookies[key] = val

return clean_cookies

def raw_data(self):
# type: () -> bytes
Expand Down
18 changes: 18 additions & 0 deletions sentry_sdk/utils.py
Expand Up @@ -370,6 +370,24 @@ def removed_because_over_size_limit(cls):
},
)

@classmethod
def substituted_because_contains_sensitive_data(cls):
# type: () -> AnnotatedValue
"""The actual value was removed because it contained sensitive information."""
from sentry_sdk.consts import SENSITIVE_DATA_SUBSTITUTE

return AnnotatedValue(
value=SENSITIVE_DATA_SUBSTITUTE,
metadata={
"rem": [ # Remark
[
"!config", # Because of SDK configuration (in this case the config is the hard coded removal of certain django cookies)
"s", # The fields original value was substituted
]
]
},
)


if MYPY:
from typing import TypeVar
Expand Down
103 changes: 103 additions & 0 deletions tests/integrations/django/test_data_scrubbing.py
@@ -0,0 +1,103 @@
from functools import partial
import pytest
import pytest_django

from werkzeug.test import Client

from sentry_sdk.integrations.django import DjangoIntegration

from tests.integrations.django.myapp.wsgi import application

try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse


# Hack to prevent from experimental feature introduced in version `4.3.0` in `pytest-django` that
# requires explicit database allow from failing the test
pytest_mark_django_db_decorator = partial(pytest.mark.django_db)
try:
pytest_version = tuple(map(int, pytest_django.__version__.split(".")))
if pytest_version > (4, 2, 0):
pytest_mark_django_db_decorator = partial(
pytest.mark.django_db, databases="__all__"
)
except ValueError:
if "dev" in pytest_django.__version__:
pytest_mark_django_db_decorator = partial(
pytest.mark.django_db, databases="__all__"
)
except AttributeError:
pass


@pytest.fixture
def client():
return Client(application)


@pytest.mark.forked
@pytest_mark_django_db_decorator()
def test_scrub_django_session_cookies_removed(
sentry_init,
client,
capture_events,
):
sentry_init(integrations=[DjangoIntegration()], send_default_pii=False)
events = capture_events()
client.set_cookie("localhost", "sessionid", "123")
client.set_cookie("localhost", "csrftoken", "456")
client.set_cookie("localhost", "foo", "bar")
client.get(reverse("view_exc"))

(event,) = events
assert "cookies" not in event["request"]


@pytest.mark.forked
@pytest_mark_django_db_decorator()
def test_scrub_django_session_cookies_filtered(
sentry_init,
client,
capture_events,
):
sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
events = capture_events()
client.set_cookie("localhost", "sessionid", "123")
client.set_cookie("localhost", "csrftoken", "456")
client.set_cookie("localhost", "foo", "bar")
client.get(reverse("view_exc"))

(event,) = events
assert event["request"]["cookies"] == {
"sessionid": "[Filtered]",
"csrftoken": "[Filtered]",
"foo": "bar",
}


@pytest.mark.forked
@pytest_mark_django_db_decorator()
def test_scrub_django_custom_session_cookies_filtered(
sentry_init,
client,
capture_events,
settings,
):
settings.SESSION_COOKIE_NAME = "my_sess"
settings.CSRF_COOKIE_NAME = "csrf_secret"

sentry_init(integrations=[DjangoIntegration()], send_default_pii=True)
events = capture_events()
client.set_cookie("localhost", "my_sess", "123")
client.set_cookie("localhost", "csrf_secret", "456")
client.set_cookie("localhost", "foo", "bar")
client.get(reverse("view_exc"))

(event,) = events
assert event["request"]["cookies"] == {
"my_sess": "[Filtered]",
"csrf_secret": "[Filtered]",
"foo": "bar",
}