Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from importlib import import_module
from typing import TYPE_CHECKING, List, Dict, cast, overload
import warnings

import re
from sentry_sdk._compat import check_uwsgi_thread_support
from sentry_sdk._metrics_batcher import MetricsBatcher
from sentry_sdk._span_batcher import SpanBatcher
Expand Down Expand Up @@ -688,6 +688,7 @@
):
new_event = None
spans_before = len(cast(List[Dict[str, object]], event.get("spans", [])))
self._clean_span_descriptions(event)
with capture_internal_exceptions():
new_event = before_send_transaction(event, hint or {})
if new_event is None:
Expand All @@ -712,6 +713,12 @@

return event

def _clean_span_descriptions(self, event: "Event") -> None:
for s in event.get("spans", []):
if "description" in s:
cleaned_description = re.sub(r"\s+", " ", s["description"]).strip()

Check failure on line 719 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: find-bugs

TypeError crash when span description is None

The `_clean_span_descriptions` method checks `if "description" in s:` which only verifies the key exists, not that its value is non-None. When `s["description"]` is `None` (which is valid per the span serialization at `tracing.py:702`), calling `re.sub(r"\s+", " ", s["description"])` raises `TypeError: expected string or bytes-like object, got 'NoneType'`. Since this code runs outside `capture_internal_exceptions()`, the error will propagate and could crash transaction processing.
s["description"] = cleaned_description

Check warning on line 720 in sentry_sdk/client.py

View check run for this annotation

@sentry/warden / warden: code-review

Runtime error when span description is None

The `_clean_span_descriptions` method calls `re.sub()` on `s["description"]` after checking `if "description" in s`, but `description` can be `None` (spans are created via `to_json()` which always includes `"description": self.description`, even when the description is None). This will raise `TypeError: expected string or bytes-like object, got 'NoneType'`. Since the call to `_clean_span_descriptions` is outside the `capture_internal_exceptions()` context manager, this exception could cause event processing to fail.
Comment on lines +718 to +720
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime error when span description is None

The _clean_span_descriptions method calls re.sub() on s["description"] after checking if "description" in s, but description can be None (spans are created via to_json() which always includes "description": self.description, even when the description is None). This will raise TypeError: expected string or bytes-like object, got 'NoneType'. Since the call to _clean_span_descriptions is outside the capture_internal_exceptions() context manager, this exception could cause event processing to fail.

Verification

Verified by reading sentry_sdk/tracing.py lines 693-706 which shows to_json() always includes "description": self.description in the returned dict even when self.description is None. Also read sentry_sdk/tracing.py line 304 which shows description can be None when both name and description params are not provided. The new code at lines 716-720 only checks key existence, not value truthiness.

Identified by Warden code-review · CZ6-MTS


def _is_ignored_error(self, event: "Event", hint: "Hint") -> bool:
exc_info = hint.get("exc_info")
if exc_info is None:
Expand Down
79 changes: 79 additions & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,85 @@ def before_send_transaction(event, hint):
assert event["extra"] == {"before_send_transaction_called": True}


def test_before_send_transaction_span_description_contains_newlines(
sentry_init, capture_events
):
def before_send_transaction(event, hint):
for span in event.get("spans", []):
if (
span.get("description", "")
== "SELECT u.id, u.name, u.email FROM users;"
):
span["description"] = "filtered"
return event

sentry_init(
before_send_transaction=before_send_transaction,
traces_sample_rate=1.0,
)
events = capture_events()

description = "SELECT u.id,\n u.name,\n u.email\n FROM users;"

with start_transaction(name="test_transaction"):
with sentry_sdk.start_span(op="db", description=description):
pass

(event,) = events
assert event["transaction"] == "test_transaction"

spans = event["spans"]
assert len(spans) == 1
assert spans[0]["description"] == "filtered"
assert spans[0]["op"] == "db"


def test_before_send_transaction_span_description_contains_multiple_lines(
sentry_init, capture_events
):
def before_send_transaction(event, hint):
for span in event.get("spans", []):
if (
span.get("description", "")
== "SELECT u.id, u.name, u.email, p.title AS post_title, p.created_at AS post_date FROM users u JOIN posts p ON u.id = p.user_id WHERE u.active = true ORDER BY p.created_at DESC"
):
span["description"] = "no bueno"
return event

sentry_init(
before_send_transaction=before_send_transaction,
traces_sample_rate=1.0,
)
events = capture_events()

description = """SELECT
u.id,
u.name,
u.email,
p.title AS post_title,
p.created_at AS post_date
FROM
users u
JOIN
posts p ON u.id = p.user_id
WHERE
u.active = true
ORDER BY
p.created_at DESC"""

with start_transaction(name="test_transaction"):
with sentry_sdk.start_span(op="db", description=description):
pass

(event,) = events
assert event["transaction"] == "test_transaction"

spans = event["spans"]
assert len(spans) == 1
assert spans[0]["description"] == "no bueno"
assert spans[0]["op"] == "db"


def test_option_before_send_transaction_discard(sentry_init, capture_events):
def before_send_transaction_discard(event, hint):
return None
Expand Down
Loading