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.
Summary
OpenTelemetry::Baggage::Propagation::TextMapPropagatordeclares three private constants:at
api/lib/opentelemetry/baggage/propagation/text_map_propagator.rblines 15-17.The private
encodehelper 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:There is no
bytesize > MAX_TOTAL_LENGTHgate beforegsubwalks the full header, noentry.bytesize > MAX_ENTRY_LENGTHskip inside the loop, and nodecoded_count >= MAX_ENTRIESbreak. The result is that a round tripinject -> extractis 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:
maxMembers = 64andmaxBytes = 8192inpropagation/baggage.goExtract.MaxBaggageLength = 8192andMaxBaggageItems = 180inBaggagePropagator.cs.kMaxSize = 8192andkMaxKeyValuePairs = 180inbaggage.h::FromHeader.The Ruby propagator already has the same constants, just not wired into
extract.Suggested fix
Hoist the existing inject-side policy into
extractso the two paths share a single rule: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.