From 3b3e0d01008846d43153791687dc9f38270b18d4 Mon Sep 17 00:00:00 2001 From: Andrew Nesbitt Date: Sat, 2 May 2026 15:29:49 +0100 Subject: [PATCH] Cap SPDX envelope unwrap depth to prevent quadratic re-parse --- spdx.go | 24 ++++++++++++++++-------- spdx_test.go | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/spdx.go b/spdx.go index 03462f0..15135a8 100644 --- a/spdx.go +++ b/spdx.go @@ -63,16 +63,24 @@ type spdxRelationship struct { RelatedSPDXElement string `json:"relatedSpdxElement"` } +const maxEnvelopeDepth = 3 + func parseSPDX(data []byte) (*SBOM, error) { var doc spdxDoc - if err := json.Unmarshal(data, &doc); err != nil { - return nil, wrapErr("spdx json", err) - } - if len(doc.SBOM) > 0 { - return parseSPDX(doc.SBOM) - } - if strings.Contains(doc.PredicateType, "spdx") && len(doc.Predicate) > 0 { - return parseSPDX(doc.Predicate) + for range maxEnvelopeDepth { + doc = spdxDoc{} + if err := json.Unmarshal(data, &doc); err != nil { + return nil, wrapErr("spdx json", err) + } + if len(doc.SBOM) > 0 { + data = doc.SBOM + continue + } + if strings.Contains(doc.PredicateType, "spdx") && len(doc.Predicate) > 0 { + data = doc.Predicate + continue + } + break } if doc.SPDXVersion == "" && doc.SPDXID == "" { return nil, ErrUnrecognized diff --git a/spdx_test.go b/spdx_test.go index cc04331..a5a8346 100644 --- a/spdx_test.go +++ b/spdx_test.go @@ -1,6 +1,10 @@ package sbom -import "testing" +import ( + "errors" + "strings" + "testing" +) func TestSPDXExternalRefPURL(t *testing.T) { in := `{ @@ -69,3 +73,30 @@ func TestSPDXInTotoEnvelope(t *testing.T) { t.Errorf("predicate unwrap failed: %+v", doc.Packages) } } + +func TestSPDXGitHubEnvelope(t *testing.T) { + inner := `{"spdxVersion":"SPDX-2.3","SPDXID":"SPDXRef-DOCUMENT", + "packages":[{"SPDXID":"SPDXRef-p","name":"x"}]}` + for _, depth := range []int{1, 2} { + in := inner + for range depth { + in = `{"sbom":` + in + `}` + } + doc, err := Parse([]byte(in)) + if err != nil { + t.Fatalf("depth %d: Parse: %v", depth, err) + } + if len(doc.Packages) != 1 || doc.Packages[0].Name != "x" { + t.Errorf("depth %d: sbom unwrap failed: %+v", depth, doc.Packages) + } + } +} + +func TestSPDXEnvelopeDepthLimit(t *testing.T) { + const depth = 100 + in := strings.Repeat(`{"sbom":`, depth) + `{}` + strings.Repeat(`}`, depth) + _, err := Parse([]byte(in)) + if !errors.Is(err, ErrUnrecognized) { + t.Fatalf("Parse = %v, want ErrUnrecognized", err) + } +}