Skip to content

W3C Baggage extract does not enforce the MAX_ENTRIES / MAX_ENTRY_LENGTH / MAX_TOTAL_LENGTH constants the inject path enforces #2163

@tonghuaroot

Description

@tonghuaroot

Summary

OpenTelemetry::Baggage::Propagation::TextMapPropagator declares three private constants:

MAX_ENTRIES      = 180
MAX_ENTRY_LENGTH = 4096
MAX_TOTAL_LENGTH = 8192

at api/lib/opentelemetry/baggage/propagation/text_map_propagator.rb lines 15-17.

The private encode helper used on the inject (outbound) path applies all three at lines 85-99 and a dedicated test exists for each cap (enforces max of 180 name-value pairs, enforces max entry length of 4096, enforces total length of 8192 chars).

The extract (inbound) path at lines 54-73 does not consult any of the three constants:

def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter)
  header = getter.get(carrier, BAGGAGE_KEY)
  return context if header.nil? || header.empty?

  entries = header.gsub(/\s/, '').split(',')

  OpenTelemetry::Baggage.build(context: context) do |builder|
    entries.each do |entry|
      kv, meta = entry.split(';', 2)
      k, v = kv.split('=').map!(&URI.method(:decode_uri_component))
      builder.set_value(k, v, metadata: meta)
    end
  end
rescue StandardError => e
  OpenTelemetry.logger.debug "Error extracting W3C baggage: #{e.message}"
  context
end

There is no bytesize > MAX_TOTAL_LENGTH gate before gsub walks the full header, no entry.bytesize > MAX_ENTRY_LENGTH skip inside the loop, and no decoded_count >= MAX_ENTRIES break. The result is that a round trip inject -> extract is not symmetric: anything the local process is unwilling to send out can still be parsed in.

Cross-implementation consistency

Sister OpenTelemetry implementations apply the W3C-spec caps on extract:

  • opentelemetry-go applies maxMembers = 64 and maxBytes = 8192 in propagation/baggage.go Extract.
  • opentelemetry-dotnet applies MaxBaggageLength = 8192 and MaxBaggageItems = 180 in BaggagePropagator.cs.
  • opentelemetry-cpp applies kMaxSize = 8192 and kMaxKeyValuePairs = 180 in baggage.h::FromHeader.
  • opentelemetry-java added equivalent byte / entry caps on the extract direction in SDK 1.62.0.

The Ruby propagator already has the same constants, just not wired into extract.

Suggested fix

Hoist the existing inject-side policy into extract so the two paths share a single rule:

def extract(carrier, context: Context.current, getter: Context::Propagation.text_map_getter)
  header = getter.get(carrier, BAGGAGE_KEY)
  return context if header.nil? || header.empty?
  return context if header.bytesize > MAX_TOTAL_LENGTH

  entries = header.gsub(/\s/, '').split(',')

  OpenTelemetry::Baggage.build(context: context) do |builder|
    decoded_count = 0
    entries.each do |entry|
      break if decoded_count >= MAX_ENTRIES
      next  if entry.bytesize > MAX_ENTRY_LENGTH

      kv, meta = entry.split(';', 2)
      k, v = kv.split('=').map!(&URI.method(:decode_uri_component))
      builder.set_value(k, v, metadata: meta)
      decoded_count += 1
    end
  end
rescue StandardError => e
  OpenTelemetry.logger.debug "Error extracting W3C baggage: #{e.message}"
  context
end

This is a code-consistency / hardening change to keep the inject and extract paths symmetric, not a security report. It was originally submitted via private advisory and the project responded that "this is not considered a security vulnerability" and asked for a standard issue + PR (closed advisory GHSA-g4r4-95p7-vp43).

I will open a PR shortly that includes this change plus three new tests that mirror the existing inject-side cap tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions