internal: fix Unicode zero character handling (#841)

A fix was submitted to our dependency, tests added to validate the
problematic scenario.

We also added some fuzz tests to check for additional issues, none were
found.
This commit is contained in:
Maciej Kisiel 2026-03-12 21:54:23 +01:00 • committed by GitHub
commit 724dd47aa3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 114 additions and 3 deletions

2
go.mod
View file

@ -6,7 +6,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/go-cmp v0.7.0
github.com/google/jsonschema-go v0.4.2
github.com/segmentio/encoding v0.5.3
github.com/segmentio/encoding v0.5.4
github.com/yosida95/uritemplate/v3 v3.0.2
golang.org/x/oauth2 v0.35.0
golang.org/x/tools v0.42.0

4
go.sum
View file

@ -6,8 +6,8 @@ github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbc
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=

View file

@ -5,6 +5,9 @@
package json
import (
"bytes"
"encoding/json"
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
@ -61,3 +64,111 @@ func TestUnmarshalCaseSensitivity(t *testing.T) {
})
}
}
func TestUnmarshalNullCharacter(t *testing.T) {
type Target struct {
Field string `json:"field"`
}
tests := []struct {
name string
json string
want string
}{
{
name: "null char in middle",
json: `{"fi\u0000eld": "value"}`,
},
{
name: "null char at end",
json: `{"field\u0000": "value"}`,
},
{
name: "null char at start",
json: `{"\u0000field": "value"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got Target
if err := Unmarshal([]byte(tt.json), &got); err != nil {
t.Fatalf("Unmarshal failed: %v", err)
}
if got.Field != "" {
t.Errorf("Field was set: %s", tt.json)
}
})
}
}
func FuzzUnmarshalKeyComparison(f *testing.F) {
// Add seed corpus for expected valid key and the known problematic cases.
f.Add("field")
f.Add("FIELD")
f.Add("field\x00")
f.Fuzz(func(t *testing.T, key string) {
// Generate valid JSON with the fuzzed key.
// json.Encoder ensures the key is properly escaped (e.g. \x00 becomes \u0000).
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(key); err != nil {
return
}
encodedKey := buf.Bytes()
jsonData := fmt.Sprintf(`{%s: "value"}`, encodedKey)
type Target struct {
Field string `json:"field"`
}
var got Target
if err := Unmarshal([]byte(jsonData), &got); err != nil {
return
}
if got.Field == "value" && key != "field" {
t.Errorf("Unmarshal matched key %q (JSON: %s) to field 'field'", key, encodedKey)
}
})
}
func FuzzUnmarshalFieldIsolation(f *testing.F) {
// Add seed corpus.
f.Add("other", "value")
f.Add("safe", "overwrite")
f.Add("safe\x00", "attack")
f.Fuzz(func(t *testing.T, key, val string) {
// Generate valid JSON with the fuzzed key and value.
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(key); err != nil {
return
}
encodedKey := buf.Bytes()
buf.Reset()
if err := enc.Encode(val); err != nil {
return
}
encodedVal := buf.Bytes()
jsonData := fmt.Sprintf(`{"safe": "value", %s: %s}`, encodedKey, encodedVal)
type Target struct {
Safe string `json:"safe"`
}
var got Target
if err := Unmarshal([]byte(jsonData), &got); err != nil {
return
}
if got.Safe != "value" && key != "safe" {
t.Errorf("Field 'safe' improperly modified by key %q (JSON: %s). Got %q, want %q", key, encodedKey, got.Safe, "value")
}
})
}