go-ipld-prime json codec may panic if asked to encode bytes
Description
go-ipld-prime is an implementation of the InterPlanetary Linked Data (IPLD) spec interfaces, a batteries-included codec implementations of IPLD for CBOR and JSON, and tooling for basic operations on IPLD objects. Encoding data which contains a Bytes kind Node will pass a Bytes token to the JSON encoder which will panic as it doesn't expect to receive Bytes tokens. Such an encode should be treated as an error, as plain JSON should not be able to encode Bytes. This only impacts uses of the json codec. dag-json is not impacted. Use of json as a decoder is not impacted. This issue is fixed in v0.19.0. As a workaround, one may prefer the dag-json codec, which has the ability to encode bytes.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/ipld/go-ipld-primeGo | < 0.19.0 | 0.19.0 |
Affected products
1- Range: < 0.19.0
Patches
1146d1c852967fix: correct json codec links & bytes handling
3 files changed · +133 −42
codec/dagjson/marshal.go+36 −40 modified@@ -202,54 +202,50 @@ func Marshal(n datamodel.Node, sink shared.TokenSink, options EncodeOptions) err _, err = sink.Step(&tk) return err case datamodel.Kind_Bytes: + if !options.EncodeBytes { + return fmt.Errorf("cannot marshal IPLD bytes to this codec") + } v, err := n.AsBytes() if err != nil { return err } - if options.EncodeBytes { - // Precisely seven tokens to emit: - tk.Type = tok.TMapOpen - tk.Length = 1 - if _, err = sink.Step(&tk); err != nil { - return err - } - tk.Type = tok.TString - tk.Str = "/" - if _, err = sink.Step(&tk); err != nil { - return err - } - tk.Type = tok.TMapOpen - tk.Length = 1 - if _, err = sink.Step(&tk); err != nil { - return err - } - tk.Type = tok.TString - tk.Str = "bytes" - if _, err = sink.Step(&tk); err != nil { - return err - } - tk.Str = base64.RawStdEncoding.EncodeToString(v) - if _, err = sink.Step(&tk); err != nil { - return err - } - tk.Type = tok.TMapClose - if _, err = sink.Step(&tk); err != nil { - return err - } - tk.Type = tok.TMapClose - if _, err = sink.Step(&tk); err != nil { - return err - } - return nil - } else { - tk.Type = tok.TBytes - tk.Bytes = v - _, err = sink.Step(&tk) + // Precisely seven tokens to emit: + tk.Type = tok.TMapOpen + tk.Length = 1 + if _, err = sink.Step(&tk); err != nil { + return err + } + tk.Type = tok.TString + tk.Str = "/" + if _, err = sink.Step(&tk); err != nil { + return err + } + tk.Type = tok.TMapOpen + tk.Length = 1 + if _, err = sink.Step(&tk); err != nil { + return err + } + tk.Type = tok.TString + tk.Str = "bytes" + if _, err = sink.Step(&tk); err != nil { + return err + } + tk.Str = base64.RawStdEncoding.EncodeToString(v) + if _, err = sink.Step(&tk); err != nil { + return err + } + tk.Type = tok.TMapClose + if _, err = sink.Step(&tk); err != nil { + return err + } + tk.Type = tok.TMapClose + if _, err = sink.Step(&tk); err != nil { return err } + return nil case datamodel.Kind_Link: if !options.EncodeLinks { - return fmt.Errorf("cannot Marshal ipld links to JSON") + return fmt.Errorf("cannot marshal IPLD links to this codec") } v, err := n.AsLink() if err != nil {
codec/dagjson/marshal_test.go+46 −2 modified@@ -12,9 +12,9 @@ import ( "github.com/ipld/go-ipld-prime/node/basicnode" ) +var link = cid.MustParse("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi") + func TestMarshalUndefCid(t *testing.T) { - link, err := cid.Decode("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi") - qt.Assert(t, err, qt.IsNil) node, err := qp.BuildMap(basicnode.Prototype.Any, -1, func(ma datamodel.MapAssembler) { qp.MapEntry(ma, "UndefCid", qp.Link(cidlink.Link{Cid: cid.Undef})) qp.MapEntry(ma, "DefCid", qp.Link(cidlink.Link{Cid: link})) @@ -23,3 +23,47 @@ func TestMarshalUndefCid(t *testing.T) { _, err = ipld.Encode(node, Encode) qt.Assert(t, err, qt.ErrorMatches, "encoding undefined CIDs are not supported by this codec") } + +// mirrored in json but with errors +func TestMarshalLinks(t *testing.T) { + linkNode := basicnode.NewLink(cidlink.Link{Cid: link}) + mapNode, err := qp.BuildMap(basicnode.Prototype.Any, -1, func(ma datamodel.MapAssembler) { + qp.MapEntry(ma, "Lnk", qp.Node(linkNode)) + }) + qt.Assert(t, err, qt.IsNil) + + t.Run("link dag-json", func(t *testing.T) { + byts, err := ipld.Encode(linkNode, Encode) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, string(byts), qt.Equals, + `{"/":"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"}`) + }) + t.Run("nested link dag-json", func(t *testing.T) { + byts, err := ipld.Encode(mapNode, Encode) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, string(byts), qt.Equals, + `{"Lnk":{"/":"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"}}`) + }) +} + +// mirrored in json but with errors +func TestMarshalBytes(t *testing.T) { + bytsNode := basicnode.NewBytes([]byte("byte me")) + mapNode, err := qp.BuildMap(basicnode.Prototype.Any, -1, func(ma datamodel.MapAssembler) { + qp.MapEntry(ma, "Byts", qp.Node(bytsNode)) + }) + qt.Assert(t, err, qt.IsNil) + + t.Run("bytes dag-json", func(t *testing.T) { + byts, err := ipld.Encode(bytsNode, Encode) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, string(byts), qt.Equals, + `{"/":{"bytes":"Ynl0ZSBtZQ"}}`) + }) + t.Run("nested bytes dag-json", func(t *testing.T) { + byts, err := ipld.Encode(mapNode, Encode) + qt.Assert(t, err, qt.IsNil) + qt.Assert(t, string(byts), qt.Equals, + `{"Byts":{"/":{"bytes":"Ynl0ZSBtZQ"}}}`) + }) +}
codec/json/marshal_test.go+51 −0 added@@ -0,0 +1,51 @@ +package json + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/ipfs/go-cid" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/fluent/qp" + cidlink "github.com/ipld/go-ipld-prime/linking/cid" + "github.com/ipld/go-ipld-prime/node/basicnode" +) + +var link = cid.MustParse("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi") + +// mirrored in dag-json but without errors +func TestMarshalLinks(t *testing.T) { + linkNode := basicnode.NewLink(cidlink.Link{Cid: link}) + mapNode, err := qp.BuildMap(basicnode.Prototype.Any, -1, func(ma datamodel.MapAssembler) { + qp.MapEntry(ma, "Lnk", qp.Node(linkNode)) + }) + qt.Assert(t, err, qt.IsNil) + + t.Run("link json", func(t *testing.T) { + _, err := ipld.Encode(linkNode, Encode) + qt.Assert(t, err, qt.ErrorMatches, "cannot marshal IPLD links to this codec") + }) + t.Run("nested link json", func(t *testing.T) { + _, err := ipld.Encode(mapNode, Encode) + qt.Assert(t, err, qt.ErrorMatches, "cannot marshal IPLD links to this codec") + }) +} + +// mirrored in dag-json but without errors +func TestMarshalBytes(t *testing.T) { + bytsNode := basicnode.NewBytes([]byte("byte me")) + mapNode, err := qp.BuildMap(basicnode.Prototype.Any, -1, func(ma datamodel.MapAssembler) { + qp.MapEntry(ma, "Byts", qp.Node(bytsNode)) + }) + qt.Assert(t, err, qt.IsNil) + + t.Run("bytes json", func(t *testing.T) { + _, err := ipld.Encode(bytsNode, Encode) + qt.Assert(t, err, qt.ErrorMatches, "cannot marshal IPLD bytes to this codec") + }) + t.Run("nested bytes json", func(t *testing.T) { + _, err := ipld.Encode(mapNode, Encode) + qt.Assert(t, err, qt.ErrorMatches, "cannot marshal IPLD bytes to this codec") + }) +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-c653-6hhg-9x92ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-22460ghsaADVISORY
- github.com/ipld/go-ipld-prime/commit/146d1c8529676fe9ee0604f014656af2395505fcghsaWEB
- github.com/ipld/go-ipld-prime/pull/472ghsax_refsource_MISCWEB
- github.com/ipld/go-ipld-prime/releases/tag/v0.19.0ghsax_refsource_MISCWEB
- github.com/ipld/go-ipld-prime/security/advisories/GHSA-c653-6hhg-9x92ghsax_refsource_CONFIRMWEB
- pkg.go.dev/vuln/GO-2023-1269ghsaWEB
News mentions
0No linked articles in our index yet.