Skip to content
Open
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
4 changes: 2 additions & 2 deletions sentry_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ class DataCollectionUserOptions(TypedDict, total=False):
gen_ai: "GenAICollectionUserOptions"
database_query_data: bool
queues: bool
stack_frame_variables: bool
stack_frame_variables: "Union[bool, KeyValueCollectionBehaviour]"
frame_context_lines: int

class DataCollection(TypedDict):
Expand All @@ -202,7 +202,7 @@ class DataCollection(TypedDict):
gen_ai: "GenAICollectionBehaviour"
database_query_data: bool
queues: bool
stack_frame_variables: bool
stack_frame_variables: "Union[bool, KeyValueCollectionBehaviour]"
frame_context_lines: int

# "critical" is an alias of "fatal" recognized by Relay
Expand Down
21 changes: 13 additions & 8 deletions sentry_sdk/data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@
"""

import warnings
from typing import TYPE_CHECKING, List, Mapping, Optional, Union, cast
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Union, cast
from urllib.parse import parse_qs, urlencode

from sentry_sdk._types import SENSITIVE_DATA_SUBSTITUTE

if TYPE_CHECKING:
from typing import Any, Dict

from sentry_sdk._types import (
DataCollection,
GenAICollectionBehaviour,
Expand Down Expand Up @@ -190,7 +188,6 @@ def _map_from_send_default_pii(

def _resolve_explicit(
d: "dict[str, Any]",
include_local_variables: bool,
) -> "DataCollection":
"""
Build a fully-resolved ``DataCollection`` from a user-supplied
Expand All @@ -205,10 +202,19 @@ def _resolve_explicit(
frame_context_lines = _DEFAULT_FRAME_CONTEXT_LINES
elif isinstance(frame_context_lines, bool):
frame_context_lines = _DEFAULT_FRAME_CONTEXT_LINES if frame_context_lines else 0
else:
if not isinstance(frame_context_lines, int) or frame_context_lines < 0:
raise ValueError(
"Invalid `frame_context_lines` value: Must be 0 or greater."
)

raw_stack_frame_variables = d.get("stack_frame_variables", True)
stack_frame_variables: "Union[bool, KeyValueCollectionBehaviour]"

stack_frame_variables = d.get("stack_frame_variables")
if stack_frame_variables is None:
stack_frame_variables = include_local_variables
if isinstance(raw_stack_frame_variables, dict):
stack_frame_variables = _kvcb_from_value(raw_stack_frame_variables)
else:
stack_frame_variables = bool(raw_stack_frame_variables)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stack variable filters are never applied

High Severity

stack_frame_variables now accepts an allowlist or denylist dict, but frame serialization still only consults include_local_variables and never reads this setting. Allowlist, denylist, off, and False therefore do not change which locals are captured, so values users intended to exclude still ship.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dc5822c. Configure here.


# http_bodies: omitted means "all valid types"; [] is the explicit opt-out.
http_bodies = d.get("http_bodies")
Expand Down Expand Up @@ -319,7 +325,6 @@ def _resolve_data_collection(options: "Dict[str, Any]") -> "DataCollection":
)
return _resolve_explicit(
user_dc,
include_local_variables,
)

return _map_from_send_default_pii(
Expand Down
109 changes: 107 additions & 2 deletions tests/test_data_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@ def test_kvcb_invalid_mode():
sentry_sdk.init(_experiments={"data_collection": {"cookies": {"mode": "nope"}}}) # type: ignore Purposely ignoring to test invalid option


def test_stack_frame_variables_invalid_mode():
with pytest.raises(ValueError):
sentry_sdk.init(
_experiments={
"data_collection": {"stack_frame_variables": {"mode": "nope"}}
}
)


@pytest.mark.parametrize(
"value",
["3", -1, [1], 2.5],
ids=[
"frame_context_lines_string",
"frame_context_lines_negative",
"frame_context_lines_list",
"frame_context_lines_float",
],
)
def test_frame_context_lines_invalid_value(value):
with pytest.raises(ValueError):
sentry_sdk.init(
_experiments={"data_collection": {"frame_context_lines": value}}
)


def test_kvcb_from_dict_defaults_mode():
sentry_sdk.init(
_experiments={
Expand Down Expand Up @@ -147,8 +173,8 @@ def _get(dc, path):
"include_local_variables": False,
"include_source_context": False,
},
{"stack_frame_variables": False, "frame_context_lines": 5},
id="explicit_stack_frame_variables_falls_back_to_legacy_option",
{"stack_frame_variables": True, "frame_context_lines": 5},
id="explicit_data_collection_ignores_legacy_include_local_variables",
),
pytest.param(
{
Expand Down Expand Up @@ -248,6 +274,85 @@ def _get(dc, path):
{"frame_context_lines": 0},
id="frame_context_lines_bool_fallback_0",
),
pytest.param(
{"_experiments": {"data_collection": {"stack_frame_variables": True}}},
{"stack_frame_variables": True},
id="stack_frame_variables_explicit_true",
),
pytest.param(
{"_experiments": {"data_collection": {"stack_frame_variables": False}}},
{"stack_frame_variables": False},
id="stack_frame_variables_explicit_false",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"stack_frame_variables": {
"mode": "allowlist",
"terms": ["order_id"],
}
}
}
},
{
"stack_frame_variables": {
"mode": "allowlist",
"terms": ["order_id"],
}
},
id="stack_frame_variables_allowlist_dict",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"stack_frame_variables": {"terms": ["order_id"]}
}
}
},
{
"stack_frame_variables": {
"mode": "denylist",
"terms": ["order_id"],
}
},
id="stack_frame_variables_dict_defaults_mode_to_denylist",
),
pytest.param(
{
"_experiments": {
"data_collection": {"stack_frame_variables": {"mode": "off"}}
}
},
{"stack_frame_variables": {"mode": "off"}},
id="stack_frame_variables_off_dict_omits_terms",
),
pytest.param(
{"_experiments": {"data_collection": {"stack_frame_variables": "yes"}}},
{"stack_frame_variables": True},
id="stack_frame_variables_non_bool_truthy_coerces_to_true",
),
pytest.param(
{"_experiments": {"data_collection": {"stack_frame_variables": ""}}},
{"stack_frame_variables": False},
id="stack_frame_variables_non_bool_falsy_coerces_to_false",
),
pytest.param(
{"_experiments": {"data_collection": {"frame_context_lines": None}}},
{"frame_context_lines": 5},
id="frame_context_lines_none_falls_back_to_spec_default",
),
pytest.param(
{"include_local_variables": False, "include_source_context": False},
{"stack_frame_variables": False, "frame_context_lines": 0},
id="legacy_include_local_variables_off_disables_stack_frame_variables",
),
pytest.param(
{"include_local_variables": True, "include_source_context": True},
{"stack_frame_variables": True, "frame_context_lines": 5},
id="legacy_include_local_variables_on_enables_stack_frame_variables",
),
],
)
def test_initialize_client_data_collection(options, expected):
Expand Down
Loading