From abd587757429730ca8f8ce0e2a858b3bb587d29c Mon Sep 17 00:00:00 2001 From: Maxence Maireaux Date: Fri, 27 Mar 2026 14:39:28 +0100 Subject: [PATCH] fix: close base64 encoder in DelegatedState.EncodeAsUrlParam to prevent data loss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The base64.NewEncoder requires Close() to flush any remaining buffered bytes with proper padding. Without it, the final 1-2 bytes of the JSON payload could be silently dropped, producing truncated base64 that fails to decode — breaking the encode/decode round-trip. Found via fuzz testing with FuzzDecodeDelegatedState. Co-Authored-By: Claude Opus 4.6 --- pkg/delegatedauth/state.go | 6 ++- pkg/delegatedauth/state_fuzz_test.go | 41 +++++++++++++++++++ .../FuzzDecodeDelegatedState/c71785f735fef5ee | 2 + 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 pkg/delegatedauth/state_fuzz_test.go create mode 100644 pkg/delegatedauth/testdata/fuzz/FuzzDecodeDelegatedState/c71785f735fef5ee diff --git a/pkg/delegatedauth/state.go b/pkg/delegatedauth/state.go index 0a45e10..8362eb9 100644 --- a/pkg/delegatedauth/state.go +++ b/pkg/delegatedauth/state.go @@ -12,7 +12,11 @@ type DelegatedState struct { func (s DelegatedState) EncodeAsUrlParam() string { buf := bytes.NewBufferString("") - if err := json.NewEncoder(base64.NewEncoder(base64.URLEncoding, buf)).Encode(s); err != nil { + encoder := base64.NewEncoder(base64.URLEncoding, buf) + if err := json.NewEncoder(encoder).Encode(s); err != nil { + panic(err) + } + if err := encoder.Close(); err != nil { panic(err) } return buf.String() diff --git a/pkg/delegatedauth/state_fuzz_test.go b/pkg/delegatedauth/state_fuzz_test.go new file mode 100644 index 0000000..29d5669 --- /dev/null +++ b/pkg/delegatedauth/state_fuzz_test.go @@ -0,0 +1,41 @@ +package delegatedauth + +import ( + "testing" +) + +func FuzzDecodeDelegatedState(f *testing.F) { + // Valid base64-encoded JSON seeds + valid := DelegatedState{AuthRequestID: "test-123"} + f.Add(valid.EncodeAsUrlParam()) + + empty := DelegatedState{AuthRequestID: ""} + f.Add(empty.EncodeAsUrlParam()) + + // Edge cases: raw strings that are not valid base64/JSON + f.Add("") + f.Add("not-base64") + f.Add("====") + f.Add("{}") + f.Add("e30=") // base64 of "{}" + f.Add("bnVsbA==") // base64 of "null" + f.Add(string([]byte{0, 1, 2, 3, 4, 5})) + f.Add("eyJhdXRoUmVxdWVzdElEIjoiIn0=") // base64 of {"authRequestID":""} + + f.Fuzz(func(t *testing.T, input string) { + result, err := DecodeDelegatedState(input) + if err != nil { + return + } + + // Round-trip: encode then decode should produce the same result + encoded := result.EncodeAsUrlParam() + result2, err := DecodeDelegatedState(encoded) + if err != nil { + t.Fatalf("round-trip decode failed: %v", err) + } + if result.AuthRequestID != result2.AuthRequestID { + t.Fatalf("round-trip mismatch: %q != %q", result.AuthRequestID, result2.AuthRequestID) + } + }) +} diff --git a/pkg/delegatedauth/testdata/fuzz/FuzzDecodeDelegatedState/c71785f735fef5ee b/pkg/delegatedauth/testdata/fuzz/FuzzDecodeDelegatedState/c71785f735fef5ee new file mode 100644 index 0000000..79e41ca --- /dev/null +++ b/pkg/delegatedauth/testdata/fuzz/FuzzDecodeDelegatedState/c71785f735fef5ee @@ -0,0 +1,2 @@ +go test fuzz v1 +string("eyJBdXRocmVxdWVzdElEIjoi08000800In00")