Skip to content

Commit fdb6dee

Browse files
committed
Skip the gRPC-reserved 'te' header in SanitizeMetadataHeaders
After [a-z0-9] key sanitization, "te" is the only grpc-reserved header name reachable (every other reserved header contains a ':' or '-' that sanitization strips). Handle it deterministically via an explicit skip set rather than relying on grpc's own handling of a reserved header.
1 parent b1751fa commit fdb6dee

3 files changed

Lines changed: 27 additions & 3 deletions

File tree

pkg/chipingress/header_provider.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,15 @@ func SanitizeMetadataValue(val string) string {
141141
// corresponding CE extension (differing only by the CloudEvents Kafka binding's "ce_" prefix
142142
// once on the wire). Values are sanitized via SanitizeMetadataValue, since grpc-go fails the
143143
// whole RPC on a non-printable value. Entries that sanitize to an empty key, or that collide
144-
// with a reserved extension name (see reservedExtensionNames), are skipped. Keys are applied
145-
// in sorted order so duplicate sanitized keys resolve deterministically (first in sorted order
146-
// wins), matching WithResourceAttributeExtensions' collision handling.
144+
// with a reserved extension name (see reservedExtensionNames) or a gRPC-reserved header name
145+
// (see reservedMetadataKeys), are skipped. Keys are applied in sorted order so duplicate
146+
// sanitized keys resolve deterministically (first in sorted order wins), matching
147+
// WithResourceAttributeExtensions' collision handling.
148+
//
149+
// Note: unlike the CloudEvents Kafka binding, gRPC metadata keys are NOT prefixed with "ce_" —
150+
// that prefix is a CloudEvents-binding concept, not a metadata one, and reusing it here would
151+
// collide with the CE binding's own "ce_<name>" Kafka header if the server ever forwards gRPC
152+
// metadata verbatim onto Kafka.
147153
func SanitizeMetadataHeaders(in map[string]string) map[string]string {
148154
keys := make([]string, 0, len(in))
149155
for k := range in {
@@ -160,6 +166,9 @@ func SanitizeMetadataHeaders(in map[string]string) map[string]string {
160166
if _, reserved := reservedExtensionNames[name]; reserved {
161167
continue
162168
}
169+
if _, reserved := reservedMetadataKeys[name]; reserved {
170+
continue
171+
}
163172
if _, exists := out[name]; exists {
164173
continue
165174
}

pkg/chipingress/header_provider_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,11 @@ func TestSanitizeMetadataHeaders(t *testing.T) {
326326
assert.Empty(t, got)
327327
})
328328

329+
t.Run("gRPC-reserved header 'te' is dropped", func(t *testing.T) {
330+
got := chipingress.SanitizeMetadataHeaders(map[string]string{"te": "trailers"})
331+
assert.Empty(t, got)
332+
})
333+
329334
t.Run("non-printable values are sanitized", func(t *testing.T) {
330335
got := chipingress.SanitizeMetadataHeaders(map[string]string{"chain_id": "1\n2"})
331336
assert.Equal(t, "1?2", got["chainid"])

pkg/chipingress/types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ var reservedExtensionNames = map[string]struct{}{
3232
"data": {},
3333
}
3434

35+
// reservedMetadataKeys holds gRPC-reserved header names that could otherwise be reached by
36+
// SanitizeExtensionName's [a-z0-9] sanitization. Verified against grpc-go v1.79.1's
37+
// isReservedHeader: every other reserved header (pseudo-headers, "content-type", "grpc-*")
38+
// contains a ':' or '-' that sanitization strips, so "te" is the only one actually reachable.
39+
// SanitizeMetadataHeaders consults this set so that edge case is handled deterministically
40+
// rather than relying on grpc's own (silent) handling of a reserved header.
41+
var reservedMetadataKeys = map[string]struct{}{
42+
"te": {},
43+
}
44+
3545
type (
3646
// Cloudevents types
3747
CloudEvent = ce.Event

0 commit comments

Comments
 (0)