Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
05a5c11
Re-introducing max baggage length
XSAM Apr 17, 2026
128f3c1
Fix lint
XSAM Apr 17, 2026
c274c8e
Update CHANGELOG
XSAM Apr 17, 2026
5eb9558
Merge branch 'main' into fix/baggage-parsing
XSAM Apr 17, 2026
f3bcf1a
Fix CHANGELOG
XSAM Apr 17, 2026
1bf7210
Update CHANGELOG.md
XSAM Apr 21, 2026
6feee46
Apply suggestion
XSAM Apr 22, 2026
4030ecd
Remove hardcoded 8192 in test cases
XSAM Apr 22, 2026
f155676
Enforce maxParseErrors for extractMultiBaggage
XSAM Apr 22, 2026
64cf1f1
Merge branch 'main' into fix/baggage-parsing
XSAM Apr 22, 2026
9ea953d
Fix linter
XSAM Apr 22, 2026
98da4b3
Merge branch 'fix/baggage-parsing' of github.com:XSAM/opentelemetry-g…
XSAM Apr 22, 2026
55e5b4f
Fix markdown
XSAM Apr 22, 2026
285423e
Update baggage/baggage.go
XSAM Apr 28, 2026
35b72da
Merge branch 'main' of github.com:open-telemetry/opentelemetry-go int…
XSAM Apr 28, 2026
1c8caf8
Count comma separator between combined header values
XSAM Apr 28, 2026
832c87b
Fix linter
XSAM Apr 28, 2026
1424a1c
Update propagation/baggage_test.go
XSAM Apr 28, 2026
df9b633
Apply suggestions
XSAM Apr 29, 2026
0f14624
Merge branch 'fix/baggage-parsing' of github.com:XSAM/opentelemetry-g…
XSAM Apr 29, 2026
3bdee54
Merge branch 'main' into fix/baggage-parsing
XSAM Apr 29, 2026
36456d1
Apply suggestions
XSAM May 1, 2026
d9d8cd2
Merge branch 'main' into fix/baggage-parsing
XSAM May 1, 2026
965ba68
Fix CHANGELOG format
XSAM May 1, 2026
c782fa6
Merge branch 'main' into fix/baggage-parsing
pellared May 4, 2026
f8daa64
Update comment for Parse method
XSAM May 8, 2026
b11ec9b
Count one parse error per failing header in extractMultiBaggage
XSAM May 8, 2026
b7a41f7
Return empty when overall header size exceed the 8192 bytes
XSAM May 8, 2026
e6e11ed
Merge branch 'main' into fix/baggage-parsing
XSAM May 8, 2026
dbe8855
Update baggage_test.go
XSAM May 8, 2026
84bb8b4
Merge branch 'main' into fix/baggage-parsing
XSAM May 12, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Fix race condition in `FixedSizeReservoir` in `go.opentelemetry.io/otel/sdk/metric/exemplar` by reverting #7447. (#8249)
- Fix counting of spans and logs in self-observability metrics in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`, and `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8254)
- Drop conflicting scope attributes named `name`, `version`, or `schema_url` from metric labels in `go.opentelemetry.io/otel/exporters/prometheus`, preserving the dedicated `otel_scope_name`, `otel_scope_version`, and `otel_scope_schema_url` labels. (#8264)
- Enforce the 8192-byte baggage size limit during extraction/parsing, changing behavior when the limit is exceeded in `go.opentelemetry.io/otel/baggage` and `go.opentelemetry.io/otel/propagation`. (#8222)

<!-- Released section -->
<!-- Don't change this section unless doing release -->
Expand Down
30 changes: 26 additions & 4 deletions baggage/baggage.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import (
)

const (
maxParseErrors = 5

// W3C Baggage specification limits.
// https://www.w3.org/TR/baggage/#limits
maxMembers = 64
maxBytesPerBaggageString = 8192

Expand Down Expand Up @@ -493,9 +497,15 @@ func New(members ...Member) (Baggage, error) {
// from the W3C Baggage specification which allows duplicate list-members, but
// conforms to the OpenTelemetry Baggage specification.
//
// If the baggage-string exceeds the maximum allowed members (64) or bytes
// (8192), members are dropped until the limits are satisfied and an error is
// returned along with the partial result.
// If the raw baggage-string exceeds the maximum allowed bytes (8192), an
// empty Baggage and an error are returned.
//
// Otherwise, members are parsed left-to-right and accumulated until one of
// the following conditions is reached, at which point parsing stops and an
// error is returned alongside the partial result:
// - accepting the next member would cause the encoded baggage to exceed
// 8192 bytes, or
// - the baggage already contains 64 distinct keys.
//
// Invalid members are skipped and the error is returned along with the
// partial result containing the valid members.
Expand All @@ -504,9 +514,14 @@ func Parse(bStr string) (Baggage, error) {
return Baggage{}, nil
}

if n := len(bStr); n > maxBytesPerBaggageString {
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
}
Comment thread
XSAM marked this conversation as resolved.

b := make(baggage.List)
sizes := make(map[string]int) // Track per-key byte sizes
var totalBytes int
var parseErrors int
var truncateErr error
for memberStr := range strings.SplitSeq(bStr, listDelimiter) {
// Check member count limit.
Expand All @@ -517,7 +532,10 @@ func Parse(bStr string) (Baggage, error) {

m, err := parseMember(memberStr)
if err != nil {
truncateErr = errors.Join(truncateErr, err)
parseErrors++
if parseErrors <= maxParseErrors {
truncateErr = errors.Join(truncateErr, err)
}
Comment thread
XSAM marked this conversation as resolved.
continue // skip invalid member, keep processing
}

Expand Down Expand Up @@ -553,6 +571,10 @@ func Parse(bStr string) (Baggage, error) {
totalBytes = newTotalBytes
}

if dropped := parseErrors - maxParseErrors; dropped > 0 {
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more invalid member(s)", dropped))
}

if len(b) == 0 {
return Baggage{}, truncateErr
}
Expand Down
78 changes: 63 additions & 15 deletions baggage/baggage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -528,8 +528,7 @@ func TestBaggageParse(t *testing.T) {
{
name: "invalid baggage string: too large",
in: tooLarge,
// tooLarge is a single key without "=", so parseMember fails
err: errInvalidMember,
err: errBaggageBytes,
},
{
name: "baggage string with too many members keeps first 64",
Expand All @@ -544,31 +543,26 @@ func TestBaggageParse(t *testing.T) {
err: errMemberNumber,
},
{
name: "baggage string exceeds byte limit returns partial result",
name: "baggage string at max size is accepted",
in: func() string {
// Create members that collectively exceed maxBytesPerBaggageString.
// Each member: "kN=" + value. We use values large enough that
// a few members fit but the total exceeds 8192 bytes.
// Create a baggage string of exactly maxBytesPerBaggageString.
// 3 members: "k0=" + 2727v + "," + "k1=" + 2727v + "," + "k2=" + 2727v
// = 3*(3+2727) + 2 = 8192
val := strings.Repeat("v", 2727)
var parts []string
val := strings.Repeat("v", 2000)
for i := range 10 {
for i := range 3 {
parts = append(parts, fmt.Sprintf("k%d=%s", i, val))
}
return strings.Join(parts, ",")
}(),
want: func() baggage.List {
// Only members that fit within 8192 bytes should be kept.
// Each member is ~2003 bytes ("kN=" + 2000 "v"s), plus comma.
// 4 members = 4*2003 + 3 commas = 8015 bytes (fits).
// 5 members = 5*2003 + 4 commas = 10019 bytes (exceeds).
b := make(baggage.List)
val := strings.Repeat("v", 2000)
for i := range 4 {
val := strings.Repeat("v", 2727)
for i := range 3 {
b[fmt.Sprintf("k%d", i)] = baggage.Item{Value: val}
}
return b
}(),
err: errBaggageBytes,
},
{
name: "percent-encoded octet sequences do not match the UTF-8 encoding scheme",
Expand Down Expand Up @@ -1272,3 +1266,57 @@ func BenchmarkMemberString(b *testing.B) {
_ = member.String()
}
}

func BenchmarkParseOversized(b *testing.B) {
// 1MB oversized baggage string.
oversized := strings.Repeat("k=v,", 250000)

b.ReportAllocs()

for b.Loop() {
_, _ = Parse(oversized)
}
}

func TestParseErrorCap(t *testing.T) {
// Build a baggage string with many invalid members (no '=' delimiter).
// All within the 8192 byte limit.
var parts []string
for i := range 20 {
parts = append(parts, fmt.Sprintf("bad%d", i))
}
// Add one valid member so the baggage is not empty.
parts = append(parts, "good=val")
bStr := strings.Join(parts, ",")

b, err := Parse(bStr)
assert.ErrorIs(t, err, errInvalidMember)
assert.Equal(t, 1, b.Len(), "should return the valid member")

// Count the number of joined errors.
errs := err.Error()
invalidCount := strings.Count(errs, "invalid baggage list-member")
assert.Equal(t, maxParseErrors, invalidCount,
"should cap individual parse errors at maxParseErrors")
assert.Contains(t, errs, "and 15 more invalid member(s)")
}

func TestParseErrorCapAllInvalid(t *testing.T) {
// All members invalid, no valid members. Exercises the len(b)==0
// return path with a capped error message.
var parts []string
for i := range 20 {
parts = append(parts, fmt.Sprintf("bad%d", i))
}
bStr := strings.Join(parts, ",")

b, err := Parse(bStr)
assert.ErrorIs(t, err, errInvalidMember)
assert.Equal(t, 0, b.Len(), "should return empty baggage")

errs := err.Error()
invalidCount := strings.Count(errs, "invalid baggage list-member")
assert.Equal(t, maxParseErrors, invalidCount,
"should cap individual parse errors at maxParseErrors")
assert.Contains(t, errs, "and 15 more invalid member(s)")
}
46 changes: 42 additions & 4 deletions propagation/baggage.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package propagation // import "go.opentelemetry.io/otel/propagation"

import (
"context"
"errors"
"fmt"

"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/internal/errorhandler"
Expand All @@ -13,9 +15,12 @@ import (
const (
baggageHeader = "baggage"

maxParseErrors = 5

// W3C Baggage specification limits.
// https://www.w3.org/TR/baggage/#limits
maxMembers = 64
maxMembers = 64
maxBytesPerBaggageString = 8192
)

// Baggage is a propagator that supports the W3C Baggage format.
Expand Down Expand Up @@ -72,10 +77,35 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
}

var members []baggage.Member
for _, bStr := range bVals {
var totalBytes int
var parseErrors int
var truncateErr error
for i, bStr := range bVals {
if i > 0 {
totalBytes++ // comma separator between combined header values
}
totalBytes += len(bStr)
Comment thread
pellared marked this conversation as resolved.
if totalBytes > maxBytesPerBaggageString {
parseErrors++
if parseErrors <= maxParseErrors {
Comment thread
XSAM marked this conversation as resolved.
Outdated
truncateErr = errors.Join(
truncateErr,
fmt.Errorf(
"baggage: aggregate header size %d exceeds %d byte limit",
totalBytes,
maxBytesPerBaggageString,
),
)
}
break
}
Comment thread
XSAM marked this conversation as resolved.

currBag, err := baggage.Parse(bStr)
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
parseErrors++
if parseErrors <= maxParseErrors {
truncateErr = errors.Join(truncateErr, err)
}
Comment thread
XSAM marked this conversation as resolved.
}
if currBag.Len() == 0 {
continue
Expand All @@ -86,10 +116,18 @@ func extractMultiBaggage(parent context.Context, carrier ValuesGetter) context.C
}
Comment thread
XSAM marked this conversation as resolved.
}

if dropped := parseErrors - maxParseErrors; dropped > 0 {
truncateErr = errors.Join(truncateErr, fmt.Errorf("and %d more error(s)", dropped))
}

b, err := baggage.New(members...)
if err != nil {
errorhandler.GetErrorHandler().Handle(err)
truncateErr = errors.Join(truncateErr, err)
Comment thread
pellared marked this conversation as resolved.
}
if truncateErr != nil {
errorhandler.GetErrorHandler().Handle(truncateErr)
}

if b.Len() == 0 {
return parent
}
Expand Down
Loading
Loading