Skip to content

TraceContextTextMapPropagator injects invalid traceparent header (all zeros) when SpanContext is invalid #5695

Description

@Dotify71

Describe your environment

  • OS: Linux / macOS
  • Python version: 3.10+
  • SDK version: main (1.37.0.dev0)
  • API version: main (1.37.0.dev0)

What happened?

In TraceContextTextMapPropagator.inject, the propagator guards against injecting invalid contexts by checking:

if span_context == trace.INVALID_SPAN_CONTEXT:
    return

Because SpanContext is a tuple consisting of (trace_id, span_id, is_remote, trace_flags, trace_state, is_valid) and INVALID_SPAN_CONTEXT is statically defined with is_remote=False, trace_flags=0, and trace_state=DEFAULT_TRACE_STATE, any invalid SpanContext (i.e. where is_valid is False) that has:

  • is_remote=True
  • non-zero trace_flags (such as TraceFlags.SAMPLED)
  • or non-empty trace_state

evaluates span_context == trace.INVALID_SPAN_CONTEXT to False.

As a consequence, the guard fails to trigger and inject() proceeds to format and inject an illegal traceparent header containing all zeroes into outbound network requests:
traceparent: 00-00000000000000000000000000000000-0000000000000000-00

This violates:

  1. W3C Trace Context Specification: Section 3.2.2.3 forbids all-zero trace-id and parent-id, and Section 4.2 states an implementation MUST NOT forward an invalid traceparent.
  2. OpenTelemetry Specification (TraceContext Propagator): "If the SpanContext is invalid, the propagator MUST NOT inject anything into the carrier."

Strict reverse proxies, service meshes (Envoy), API gateways, and downstream microservices may reject HTTP requests with invalid W3C headers with 400 Bad Request or drop the context.

Steps to Reproduce

from opentelemetry import trace
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.trace.span import NonRecordingSpan, SpanContext

propagator = TraceContextTextMapPropagator()

# An invalid remote span context (e.g. from an upstream call)
invalid_span_context = SpanContext(trace_id=0, span_id=0, is_remote=True)
print("is_valid:", invalid_span_context.is_valid)  # False
print("== INVALID_SPAN_CONTEXT:", invalid_span_context == trace.INVALID_SPAN_CONTEXT)  # False

carrier = {}
ctx = trace.set_span_in_context(NonRecordingSpan(invalid_span_context))
propagator.inject(carrier, ctx)

print("Injected carrier:", carrier)

Expected Result

No traceparent or tracestate header should be injected when span_context.is_valid is False:
Injected carrier: {}

Actual Result

An illegal all-zero traceparent header is injected onto the carrier:
Injected carrier: {'traceparent': '00-00000000000000000000000000000000-0000000000000000-00'}

Additional context

The fix is straightforward. Replace the equality comparison against trace.INVALID_SPAN_CONTEXT with a check on the is_valid property:

diff --git a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py
--- a/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py
+++ b/opentelemetry-api/src/opentelemetry/trace/propagation/tracecontext.py
@@ -81,7 +81,7 @@ class TraceContextTextMapPropagator(textmap.TextMapPropagator):
         """
         span = trace.get_current_span(context)
         span_context = span.get_span_context()
-        if span_context == trace.INVALID_SPAN_CONTEXT:
+        if not span_context.is_valid:
             return
         traceparent_string = f"00-{format_trace_id(span_context.trace_id)}-{format_span_id(span_context.span_id)}-{span_context.trace_flags:02x}"
         setter.set(carrier, self._TRACEPARENT_HEADER_NAME, traceparent_string)

(Note: The same span_context == trace.INVALID_SPAN_CONTEXT pattern is also present in B3MultiFormat, B3SingleFormat, and JaegerPropagator.)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions