Skip to content

Commit 6ff62ad

Browse files
committed
Implement strict_trace_continuation
Fixes a security hole where incoming traces from other orgs can cause a DOS-like attack on another org by injecting Sentry propagation headers. Spec: https://develop.sentry.dev/sdk/telemetry/traces/#stricttracecontinuation
1 parent 254f618 commit 6ff62ad

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

sentry_sdk/consts.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,7 @@ def __init__(
10241024
enable_metrics=True, # type: bool
10251025
before_send_metric=None, # type: Optional[Callable[[Metric, Hint], Optional[Metric]]]
10261026
org_id=None, # type: Optional[str]
1027+
strict_trace_continuation=False, # type: bool
10271028
):
10281029
# type: (...) -> None
10291030
"""Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`.
@@ -1427,6 +1428,13 @@ def __init__(
14271428
If `trace_ignore_status_codes` is not provided, requests with any status code
14281429
may be traced.
14291430
1431+
:param strict_trace_continuation: If set to `True`, the SDK will only continue a trace if the `org_id` of the incoming trace found in the
1432+
`baggage` header matches the `org_id` of the current Sentry client.
1433+
1434+
The client's organization ID is extracted from the DSN or can be set with the `org_id` option.
1435+
If the organization IDs do not match, the SDK will start a new trace instead of continuing the incoming one.
1436+
This is useful to prevent traces of unknown third-party services from being continued in your application.
1437+
14301438
:param org_id: An optional organization ID. The SDK will try to extract if from the DSN in most cases
14311439
but you can provide it explicitly for self-hosted and Relay setups. This value is used for
14321440
trace propagation and for features like `strict_trace_continuation`.

sentry_sdk/tracing_utils.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from sentry_sdk.utils import (
1616
capture_internal_exceptions,
1717
filename_for_module,
18-
Dsn,
1918
logger,
2019
match_regex_list,
2120
qualname_from_function,
@@ -453,15 +452,23 @@ def from_incoming_data(cls, incoming_data):
453452

454453
sentry_trace_header = normalized_data.get(SENTRY_TRACE_HEADER_NAME)
455454
sentrytrace_data = extract_sentrytrace_data(sentry_trace_header)
455+
456+
# nothing to propagate if no sentry-trace
456457
if sentrytrace_data is None:
457458
return None
458459

460+
baggage_header = normalized_data.get(BAGGAGE_HEADER_NAME)
461+
baggage = (
462+
Baggage.from_incoming_header(baggage_header) if baggage_header else None
463+
)
464+
465+
if not _should_continue_trace(baggage):
466+
return None
467+
459468
propagation_context = PropagationContext()
460469
propagation_context.update(sentrytrace_data)
461-
462-
baggage_header = normalized_data.get(BAGGAGE_HEADER_NAME)
463-
if baggage_header:
464-
propagation_context.baggage = Baggage.from_incoming_header(baggage_header)
470+
if baggage:
471+
propagation_context.baggage = baggage
465472

466473
propagation_context._fill_sample_rand()
467474

@@ -1230,6 +1237,41 @@ def _set_output_attributes(span, template, send_pii, result):
12301237
span.update_data(_get_output_attributes(template, send_pii, result) or {})
12311238

12321239

1240+
def _should_continue_trace(baggage):
1241+
# type: (Optional[Baggage]) -> bool
1242+
"""
1243+
Check if we should continue the incoming trace according to the strict_trace_continuation spec.
1244+
https://develop.sentry.dev/sdk/telemetry/traces/#stricttracecontinuation
1245+
"""
1246+
1247+
client = sentry_sdk.get_client()
1248+
parsed_dsn = client.parsed_dsn
1249+
client_org_id = parsed_dsn.org_id if parsed_dsn else None
1250+
baggage_org_id = baggage.sentry_items.get("org_id") if baggage else None
1251+
1252+
if (
1253+
client_org_id is not None
1254+
and baggage_org_id is not None
1255+
and client_org_id != baggage_org_id
1256+
):
1257+
logger.debug(
1258+
f"Starting a new trace because org IDs don't match (incoming baggage org_id: {baggage_org_id}, SDK org_id: {client_org_id})"
1259+
)
1260+
return False
1261+
1262+
strict_trace_continuation = client.options.get("strict_trace_continuation", False) # type: bool
1263+
if strict_trace_continuation:
1264+
if (baggage_org_id is not None and client_org_id is None) or (
1265+
baggage_org_id is None and client_org_id is not None
1266+
):
1267+
logger.debug(
1268+
f"Starting a new trace because strict trace continuation is enabled and one org ID is missing (incoming baggage org_id: {baggage_org_id}, SDK org_id: {client_org_id})"
1269+
)
1270+
return False
1271+
1272+
return True
1273+
1274+
12331275
# Circular imports
12341276
from sentry_sdk.tracing import (
12351277
BAGGAGE_HEADER_NAME,

0 commit comments

Comments
 (0)