Skip to content

Commit 443b7b9

Browse files
authored
Work with a copy of request, vars in the event (#2125)
* Work with a copy of request, vars in the event In some cases we were attaching parts of the original request to the event with live references on them and ending up modifying the underlying headers or request data when we scrubbed the event. Now we make sure to only attach a copy of the request to the event. We also do the same for frame vars.
1 parent 8c24d33 commit 443b7b9

File tree

11 files changed

+122
-13
lines changed

11 files changed

+122
-13
lines changed

sentry_sdk/integrations/_wsgi_common.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from copy import deepcopy
23

34
from sentry_sdk.hub import Hub, _should_send_default_pii
45
from sentry_sdk.utils import AnnotatedValue
@@ -77,7 +78,7 @@ def extract_into_event(self, event):
7778
if data is not None:
7879
request_info["data"] = data
7980

80-
event["request"] = request_info
81+
event["request"] = deepcopy(request_info)
8182

8283
def content_length(self):
8384
# type: () -> int

sentry_sdk/integrations/asgi.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import asyncio
88
import inspect
99
import urllib
10+
from copy import deepcopy
1011

1112
from sentry_sdk._functools import partial
1213
from sentry_sdk._types import TYPE_CHECKING
@@ -211,7 +212,7 @@ def event_processor(self, event, hint, asgi_scope):
211212

212213
self._set_transaction_name_and_source(event, self.transaction_style, asgi_scope)
213214

214-
event["request"] = request_info
215+
event["request"] = deepcopy(request_info)
215216

216217
return event
217218

sentry_sdk/integrations/aws_lambda.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import sys
2+
from copy import deepcopy
13
from datetime import datetime, timedelta
24
from os import environ
3-
import sys
4-
from sentry_sdk.consts import OP
55

6+
from sentry_sdk.consts import OP
67
from sentry_sdk.hub import Hub, _should_send_default_pii
78
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, Transaction
89
from sentry_sdk._compat import reraise
@@ -380,7 +381,7 @@ def event_processor(sentry_event, hint, start_time=start_time):
380381
# event. Meaning every body is unstructured to us.
381382
request["data"] = AnnotatedValue.removed_because_raw_data()
382383

383-
sentry_event["request"] = request
384+
sentry_event["request"] = deepcopy(request)
384385

385386
return sentry_event
386387

sentry_sdk/integrations/fastapi.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from copy import deepcopy
23

34
from sentry_sdk._types import TYPE_CHECKING
45
from sentry_sdk.hub import Hub, _should_send_default_pii
@@ -116,7 +117,7 @@ def event_processor(event, hint):
116117
request_info["cookies"] = info["cookies"]
117118
if "data" in info:
118119
request_info["data"] = info["data"]
119-
event["request"] = request_info
120+
event["request"] = deepcopy(request_info)
120121

121122
return event
122123

sentry_sdk/integrations/gcp.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
import sys
2+
from copy import deepcopy
13
from datetime import datetime, timedelta
24
from os import environ
3-
import sys
4-
from sentry_sdk.consts import OP
55

6+
from sentry_sdk.consts import OP
67
from sentry_sdk.hub import Hub, _should_send_default_pii
78
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, Transaction
89
from sentry_sdk._compat import reraise
@@ -193,7 +194,7 @@ def event_processor(event, hint):
193194
# event. Meaning every body is unstructured to us.
194195
request["data"] = AnnotatedValue.removed_because_raw_data()
195196

196-
event["request"] = request
197+
event["request"] = deepcopy(request)
197198

198199
return event
199200

sentry_sdk/integrations/starlette.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import functools
5+
from copy import deepcopy
56

67
from sentry_sdk._compat import iteritems
78
from sentry_sdk._types import TYPE_CHECKING
@@ -389,7 +390,7 @@ def event_processor(event, hint):
389390
request_info["cookies"] = info["cookies"]
390391
if "data" in info:
391392
request_info["data"] = info["data"]
392-
event["request"] = request_info
393+
event["request"] = deepcopy(request_info)
393394

394395
return event
395396

@@ -435,7 +436,7 @@ def event_processor(event, hint):
435436
if cookies:
436437
request_info["cookies"] = cookies
437438

438-
event["request"] = request_info
439+
event["request"] = deepcopy(request_info)
439440

440441
return event
441442

sentry_sdk/utils.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import threading
1111
import time
1212
from collections import namedtuple
13+
from copy import copy
1314
from decimal import Decimal
1415
from numbers import Real
1516

@@ -627,7 +628,7 @@ def serialize_frame(
627628
)
628629

629630
if include_local_variables:
630-
rv["vars"] = frame.f_locals
631+
rv["vars"] = copy(frame.f_locals)
631632

632633
return rv
633634

tests/integrations/fastapi/test_fastapi.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import json
2+
import logging
23
import threading
34

45
import pytest
56
from sentry_sdk.integrations.fastapi import FastApiIntegration
67

78
fastapi = pytest.importorskip("fastapi")
89

9-
from fastapi import FastAPI
10+
from fastapi import FastAPI, Request
1011
from fastapi.testclient import TestClient
1112
from sentry_sdk import capture_message
1213
from sentry_sdk.integrations.starlette import StarletteIntegration
@@ -187,3 +188,33 @@ def test_active_thread_id(sentry_init, capture_envelopes, teardown_profiling, en
187188
transactions = profile.payload.json["transactions"]
188189
assert len(transactions) == 1
189190
assert str(data["active"]) == transactions[0]["active_thread_id"]
191+
192+
193+
@pytest.mark.asyncio
194+
async def test_original_request_not_scrubbed(sentry_init, capture_events):
195+
sentry_init(
196+
integrations=[StarletteIntegration(), FastApiIntegration()],
197+
traces_sample_rate=1.0,
198+
debug=True,
199+
)
200+
201+
app = FastAPI()
202+
203+
@app.post("/error")
204+
async def _error(request: Request):
205+
logging.critical("Oh no!")
206+
assert request.headers["Authorization"] == "Bearer ohno"
207+
assert await request.json() == {"password": "secret"}
208+
209+
return {"error": "Oh no!"}
210+
211+
events = capture_events()
212+
213+
client = TestClient(app)
214+
client.post(
215+
"/error", json={"password": "secret"}, headers={"Authorization": "Bearer ohno"}
216+
)
217+
218+
event = events[0]
219+
assert event["request"]["data"] == {"password": "[Filtered]"}
220+
assert event["request"]["headers"]["authorization"] == "[Filtered]"

tests/integrations/flask/test_flask.py

+23
Original file line numberDiff line numberDiff line change
@@ -816,3 +816,26 @@ def index():
816816
response = client.get("/")
817817
assert response.status_code == 200
818818
assert response.data == b"hi"
819+
820+
821+
def test_request_not_modified_by_reference(sentry_init, capture_events, app):
822+
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
823+
824+
@app.route("/", methods=["POST"])
825+
def index():
826+
logging.critical("oops")
827+
assert request.get_json() == {"password": "ohno"}
828+
assert request.headers["Authorization"] == "Bearer ohno"
829+
return "ok"
830+
831+
events = capture_events()
832+
833+
client = app.test_client()
834+
client.post(
835+
"/", json={"password": "ohno"}, headers={"Authorization": "Bearer ohno"}
836+
)
837+
838+
(event,) = events
839+
840+
assert event["request"]["data"]["password"] == "[Filtered]"
841+
assert event["request"]["headers"]["Authorization"] == "[Filtered]"

tests/integrations/starlette/test_starlette.py

+30
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import base64
33
import functools
44
import json
5+
import logging
56
import os
67
import threading
78

@@ -873,3 +874,32 @@ def test_active_thread_id(sentry_init, capture_envelopes, teardown_profiling, en
873874
transactions = profile.payload.json["transactions"]
874875
assert len(transactions) == 1
875876
assert str(data["active"]) == transactions[0]["active_thread_id"]
877+
878+
879+
def test_original_request_not_scrubbed(sentry_init, capture_events):
880+
sentry_init(integrations=[StarletteIntegration()])
881+
882+
events = capture_events()
883+
884+
async def _error(request):
885+
logging.critical("Oh no!")
886+
assert request.headers["Authorization"] == "Bearer ohno"
887+
assert await request.json() == {"password": "ohno"}
888+
return starlette.responses.JSONResponse({"status": "Oh no!"})
889+
890+
app = starlette.applications.Starlette(
891+
routes=[
892+
starlette.routing.Route("/error", _error, methods=["POST"]),
893+
],
894+
)
895+
896+
client = TestClient(app)
897+
client.post(
898+
"/error",
899+
json={"password": "ohno"},
900+
headers={"Authorization": "Bearer ohno"},
901+
)
902+
903+
event = events[0]
904+
assert event["request"]["data"] == {"password": "[Filtered]"}
905+
assert event["request"]["headers"]["authorization"] == "[Filtered]"

tests/test_scrubber.py

+18
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,21 @@ def test_custom_denylist(sentry_init, capture_events):
153153
assert meta == {
154154
"my_sensitive_var": {"": {"rem": [["!config", "s"]]}},
155155
}
156+
157+
158+
def test_scrubbing_doesnt_affect_local_vars(sentry_init, capture_events):
159+
sentry_init()
160+
events = capture_events()
161+
162+
try:
163+
password = "cat123"
164+
1 / 0
165+
except ZeroDivisionError:
166+
capture_exception()
167+
168+
(event,) = events
169+
170+
frames = event["exception"]["values"][0]["stacktrace"]["frames"]
171+
(frame,) = frames
172+
assert frame["vars"]["password"] == "[Filtered]"
173+
assert password == "cat123"

0 commit comments

Comments
 (0)