You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two of the four SDKs I measured implement the percent-encoding rule in HTTP Protocol Binding 3.1.3.2 and two do not, so a conformant sender and a non-conformant receiver silently exchange corrupted attribute values today.
This is the data nobody came back with on #777. In that thread @duglin asked:
I'd be curious to know if those decoding libraries would just ignore non-escaped chars and pass them along. Meaning... if people cheated and only % encoded some of what they should if things would just work anyway :-)
I'm concerned about compatibility with existing CloudEvent providers and consumers.
The answer is that it does not just work, and the incompatibility is silent in the common direction.
Measured, 2026-09-21
SDK
percent-encodes on send
percent-decodes on receive
sdk-csharp (main)
yes
yes
sdk-cpp (cloudevents-sdk-cpp, not an official SDK)
yes
yes
sdk-go v2.15.2
no
no
sdk-java (main)
no
no
sdk-go, run rather than read:
GO WRITES ce-subject = "a b" // for subject "a b"
GO WRITES ce-pct = "100%" // for extension value "100%"
GO READS "a%20b" -> "a%20b"
GO READS "100%25" -> "100%25"
sdk-csharp/src/CloudNative.CloudEvents/Http/HttpUtilities.cs encodes and decodes space, double quote, percent, non-printable ASCII and non-ASCII, which is 3.1.3.2 exactly. sdk-go/v2/protocol/http/write_request.go and sdk-java/http/basic/.../HttpMessageWriter.java apply no transformation in either direction.
What that costs
Conformant sender to non-conformant receiver, silent:
event
on the wire
receiver's application sees
subject = "a b"
ce-subject: a%20b
a%20b
subject = "100%"
ce-subject: 100%25
100%25
Non-conformant sender to conformant receiver:
event
on the wire
receiver produces
subject = "a b"
ce-subject: a b
a b, correct by luck: no escape present
subject = "100%"
ce-subject: 100%
rejected, malformed escape
subject = "100%41"
ce-subject: 100%41
100A, silent corruption
Only binary mode, and only for values containing space, double quote, percent or a byte outside printable ASCII. In practice that is subject and string extensions; id, type and time are unaffected in normal use, and source tends to agree because URI serialization escapes it anyway. Structured mode is unaffected.
Once this is agreed, we should also document conformance tests, but it's unclear at the moment where they should live.
As far as I can find, they still do not exist. The rule is testable only against another implementation, and the interop testing I have seen between SDKs compares JSON event documents rather than binding header mappings, which is exactly the layer where this lives. My own SDK had the same gap: the format layer was well covered and the binary-mode wire shape was not covered at all, which is why I did not notice either.
What I am asking
Not for a decision from me, and I have no standing to propose one. But this seems worth resolving in one place rather than four:
Is 3.1.3.2 still the intended rule? If yes, sdk-go and sdk-java are non-conformant and it is worth saying so plainly.
If yes, could the conformance examples from Clarify HTTP header encoding/decoding rules #777 land somewhere the SDKs can consume? A table of (attribute value, on-the-wire header value) would be enough, and is what @jskeet asked for originally.
If the rule is not going to be implemented broadly, the spec and the SDKs currently disagree in a way that corrupts data silently, which seems worse than either option on its own.
Happy to contribute the fixture table if that is useful, and to file the corresponding issues on sdk-go and sdk-java once there is a view here.
Context
Found while checking a C++ SDK for cross-SDK interoperability. That SDK stays conformant by default and offers an opt-in policy for talking to Go and Java, which is a workaround, not a fix.
Summary
Two of the four SDKs I measured implement the percent-encoding rule in HTTP Protocol Binding 3.1.3.2 and two do not, so a conformant sender and a non-conformant receiver silently exchange corrupted attribute values today.
This is the data nobody came back with on #777. In that thread @duglin asked:
and @jskeet replied:
The answer is that it does not just work, and the incompatibility is silent in the common direction.
Measured, 2026-09-21
cloudevents-sdk-cpp, not an official SDK)sdk-go, run rather than read:
sdk-csharp/src/CloudNative.CloudEvents/Http/HttpUtilities.csencodes and decodes space, double quote, percent, non-printable ASCII and non-ASCII, which is 3.1.3.2 exactly.sdk-go/v2/protocol/http/write_request.goandsdk-java/http/basic/.../HttpMessageWriter.javaapply no transformation in either direction.What that costs
Conformant sender to non-conformant receiver, silent:
subject = "a b"ce-subject: a%20ba%20bsubject = "100%"ce-subject: 100%25100%25Non-conformant sender to conformant receiver:
subject = "a b"ce-subject: a ba b, correct by luck: no escape presentsubject = "100%"ce-subject: 100%subject = "100%41"ce-subject: 100%41100A, silent corruptionOnly binary mode, and only for values containing space, double quote, percent or a byte outside printable ASCII. In practice that is
subjectand string extensions;id,typeandtimeare unaffected in normal use, andsourcetends to agree because URI serialization escapes it anyway. Structured mode is unaffected.Why I think it went unnoticed
#793 said:
As far as I can find, they still do not exist. The rule is testable only against another implementation, and the interop testing I have seen between SDKs compares JSON event documents rather than binding header mappings, which is exactly the layer where this lives. My own SDK had the same gap: the format layer was well covered and the binary-mode wire shape was not covered at all, which is why I did not notice either.
What I am asking
Not for a decision from me, and I have no standing to propose one. But this seems worth resolving in one place rather than four:
Happy to contribute the fixture table if that is useful, and to file the corresponding issues on sdk-go and sdk-java once there is a view here.
Context
Found while checking a C++ SDK for cross-SDK interoperability. That SDK stays conformant by default and offers an opt-in policy for talking to Go and Java, which is a workaround, not a fix.