From fc3837d655f3b4d08fcf4c61196fd4cfcfa501da Mon Sep 17 00:00:00 2001 From: koplas Date: Wed, 2 Jul 2025 17:06:25 +0200 Subject: [PATCH 1/5] Make json parsing more strict --- cmd/csaf_aggregator/interim.go | 4 ++-- cmd/csaf_aggregator/mirror.go | 5 ++--- cmd/csaf_checker/processor.go | 11 +++++----- cmd/csaf_downloader/downloader.go | 3 ++- cmd/csaf_uploader/processor.go | 7 +++---- cmd/csaf_validator/main.go | 4 ++-- csaf/advisory.go | 5 +++-- csaf/generate_cvss_enums.go | 5 +++-- csaf/models.go | 5 ++--- csaf/providermetaloader.go | 7 +++---- csaf/remotevalidation.go | 6 +++--- csaf/rolie.go | 8 ++++---- internal/misc/json.go | 34 +++++++++++++++++++++++++++++++ 13 files changed, 68 insertions(+), 36 deletions(-) create mode 100644 internal/misc/json.go diff --git a/cmd/csaf_aggregator/interim.go b/cmd/csaf_aggregator/interim.go index 94147bc..8805fdb 100644 --- a/cmd/csaf_aggregator/interim.go +++ b/cmd/csaf_aggregator/interim.go @@ -13,7 +13,6 @@ import ( "crypto/sha256" "crypto/sha512" "encoding/csv" - "encoding/json" "errors" "fmt" "io" @@ -25,6 +24,7 @@ import ( "time" "github.com/gocsaf/csaf/v3/csaf" + "github.com/gocsaf/csaf/v3/internal/misc" "github.com/gocsaf/csaf/v3/util" ) @@ -81,7 +81,7 @@ func (w *worker) checkInterims( if err := func() error { defer res.Body.Close() tee := io.TeeReader(res.Body, hasher) - return json.NewDecoder(tee).Decode(&doc) + return misc.StrictJSONParse(tee, &doc) }(); err != nil { return nil, err } diff --git a/cmd/csaf_aggregator/mirror.go b/cmd/csaf_aggregator/mirror.go index 1ef5881..f9ddcad 100644 --- a/cmd/csaf_aggregator/mirror.go +++ b/cmd/csaf_aggregator/mirror.go @@ -13,7 +13,6 @@ import ( "crypto/sha256" "crypto/sha512" "encoding/hex" - "encoding/json" "fmt" "io" "log/slog" @@ -31,6 +30,7 @@ import ( "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/gocsaf/csaf/v3/csaf" + "github.com/gocsaf/csaf/v3/internal/misc" "github.com/gocsaf/csaf/v3/util" ) @@ -538,7 +538,7 @@ func (w *worker) mirrorFiles(tlpLabel csaf.TLPLabel, files []csaf.AdvisoryFile) download := func(r io.Reader) error { tee := io.TeeReader(r, hasher) - return json.NewDecoder(tee).Decode(&advisory) + return misc.StrictJSONParse(tee, &advisory) } if err := downloadJSON(w.client, file.URL(), download); err != nil { @@ -627,7 +627,6 @@ func (w *worker) mirrorFiles(tlpLabel csaf.TLPLabel, files []csaf.AdvisoryFile) // If this fails it creates a signature itself with the configured key. func (w *worker) downloadSignatureOrSign(url, fname string, data []byte) error { sig, err := w.downloadSignature(url) - if err != nil { if err != errNotFound { w.log.Error("Could not find signature URL", "url", url, "err", err) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index def1960..08ec55e 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -15,10 +15,8 @@ import ( "crypto/sha512" "crypto/tls" "encoding/csv" - "encoding/json" "errors" "fmt" - "github.com/gocsaf/csaf/v3/internal/misc" "io" "log" "net/http" @@ -30,6 +28,8 @@ import ( "strings" "time" + "github.com/gocsaf/csaf/v3/internal/misc" + "github.com/ProtonMail/gopenpgp/v2/crypto" "golang.org/x/time/rate" @@ -518,7 +518,7 @@ func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) { return nil, nil, fmt.Errorf("%s: %v", feed, err) } var rolieDoc any - err = json.NewDecoder(bytes.NewReader(all)).Decode(&rolieDoc) + err = misc.StrictJSONParse(bytes.NewReader(all), &rolieDoc) return rfeed, rolieDoc, err }() if err != nil { @@ -702,7 +702,7 @@ func (p *processor) integrity( if err := func() error { defer res.Body.Close() tee := io.TeeReader(res.Body, hasher) - return json.NewDecoder(tee).Decode(&doc) + return misc.StrictJSONParse(tee, &doc) }(); err != nil { lg(ErrorType, "Reading %s failed: %v", u, err) continue @@ -1035,8 +1035,7 @@ func (p *processor) checkChanges(base string, mask whereType) error { } path := r[pathColumn] - times, files = - append(times, t), + times, files = append(times, t), append(files, csaf.DirectoryAdvisoryFile{Path: path}) p.timesChanges[path] = t } diff --git a/cmd/csaf_downloader/downloader.go b/cmd/csaf_downloader/downloader.go index 2b08544..4890593 100644 --- a/cmd/csaf_downloader/downloader.go +++ b/cmd/csaf_downloader/downloader.go @@ -35,6 +35,7 @@ import ( "golang.org/x/time/rate" "github.com/gocsaf/csaf/v3/csaf" + "github.com/gocsaf/csaf/v3/internal/misc" "github.com/gocsaf/csaf/v3/util" ) @@ -551,7 +552,7 @@ func (dc *downloadContext) downloadAdvisory( tee := io.TeeReader(resp.Body, hasher) - if err := json.NewDecoder(tee).Decode(&doc); err != nil { + if err := misc.StrictJSONParse(tee, &doc); err != nil { dc.stats.downloadFailed++ slog.Warn("Downloading failed", "url", file.URL(), diff --git a/cmd/csaf_uploader/processor.go b/cmd/csaf_uploader/processor.go index f655e02..104e1ef 100644 --- a/cmd/csaf_uploader/processor.go +++ b/cmd/csaf_uploader/processor.go @@ -11,7 +11,6 @@ package main import ( "bytes" "crypto/tls" - "encoding/json" "errors" "fmt" "io" @@ -91,7 +90,7 @@ func (p *processor) create() error { Errors []string `json:"errors"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := misc.StrictJSONParse(resp.Body, &result); err != nil { return err } @@ -115,7 +114,7 @@ func (p *processor) uploadRequest(filename string) (*http.Request, error) { if !p.cfg.NoSchemaCheck { var doc any - if err := json.NewDecoder(bytes.NewReader(data)).Decode(&doc); err != nil { + if err := misc.StrictJSONParse(bytes.NewReader(data), &doc); err != nil { return nil, err } errs, err := csaf.ValidateCSAF(doc) @@ -239,7 +238,7 @@ func (p *processor) process(filename string) error { Errors []string `json:"errors"` } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + if err := misc.StrictJSONParse(resp.Body, &result); err != nil { return err } diff --git a/cmd/csaf_validator/main.go b/cmd/csaf_validator/main.go index b3a0855..8cf6d9a 100644 --- a/cmd/csaf_validator/main.go +++ b/cmd/csaf_validator/main.go @@ -10,7 +10,6 @@ package main import ( - "encoding/json" "fmt" "log" "os" @@ -19,6 +18,7 @@ import ( "github.com/jessevdk/go-flags" "github.com/gocsaf/csaf/v3/csaf" + "github.com/gocsaf/csaf/v3/internal/misc" "github.com/gocsaf/csaf/v3/util" ) @@ -301,7 +301,7 @@ func loadJSONFromFile(fname string) (any, error) { } defer f.Close() var doc any - if err = json.NewDecoder(f).Decode(&doc); err != nil { + if err = misc.StrictJSONParse(f, &doc); err != nil { return nil, err } return doc, err diff --git a/csaf/advisory.go b/csaf/advisory.go index e81a28a..cc2516a 100644 --- a/csaf/advisory.go +++ b/csaf/advisory.go @@ -14,6 +14,8 @@ import ( "fmt" "io" "os" + + "github.com/gocsaf/csaf/v3/internal/misc" ) // Acknowledgement reflects the 'acknowledgement' object in the list of acknowledgements. @@ -383,7 +385,6 @@ type Relationship struct { FullProductName *FullProductName `json:"full_product_name"` // required ProductReference *ProductID `json:"product_reference"` // required RelatesToProductReference *ProductID `json:"relates_to_product_reference"` // required - } // Relationships is a list of Relationship. @@ -1391,7 +1392,7 @@ func LoadAdvisory(fname string) (*Advisory, error) { } defer f.Close() var advisory Advisory - if err := json.NewDecoder(f).Decode(&advisory); err != nil { + if err := misc.StrictJSONParse(f, &advisory); err != nil { return nil, err } if err := advisory.Validate(); err != nil { diff --git a/csaf/generate_cvss_enums.go b/csaf/generate_cvss_enums.go index c84ab15..2fa214b 100644 --- a/csaf/generate_cvss_enums.go +++ b/csaf/generate_cvss_enums.go @@ -12,7 +12,6 @@ package main import ( "bytes" - "encoding/json" "flag" "fmt" "go/format" @@ -22,6 +21,8 @@ import ( "sort" "strings" "text/template" + + "github.com/gocsaf/csaf/v3/internal/misc" ) // We from Intevation consider the source code parts in the following @@ -98,7 +99,7 @@ func loadSchema(filename string) (*schema, error) { } defer f.Close() var s schema - if err := json.NewDecoder(f).Decode(&s); err != nil { + if err := misc.StrictJSONParse(f, &s); err != nil { return nil, err } return &s, nil diff --git a/csaf/models.go b/csaf/models.go index c4b132d..983bf9c 100644 --- a/csaf/models.go +++ b/csaf/models.go @@ -17,6 +17,7 @@ import ( "strings" "time" + "github.com/gocsaf/csaf/v3/internal/misc" "github.com/gocsaf/csaf/v3/util" ) @@ -575,7 +576,6 @@ func (d *Distribution) Validate() error { // Validate checks if the provider metadata is valid. // Returns an error if the validation fails otherwise nil. func (pmd *ProviderMetadata) Validate() error { - switch { case pmd.CanonicalURL == nil: return errors.New("canonical_url is mandatory") @@ -695,8 +695,7 @@ func (pmd *ProviderMetadata) WriteTo(w io.Writer) (int64, error) { func LoadProviderMetadata(r io.Reader) (*ProviderMetadata, error) { var pmd ProviderMetadata - dec := json.NewDecoder(r) - if err := dec.Decode(&pmd); err != nil { + if err := misc.StrictJSONParse(r, &pmd); err != nil { return nil, err } diff --git a/csaf/providermetaloader.go b/csaf/providermetaloader.go index 72412b3..6f08eb7 100644 --- a/csaf/providermetaloader.go +++ b/csaf/providermetaloader.go @@ -11,13 +11,13 @@ package csaf import ( "bytes" "crypto/sha256" - "encoding/json" "fmt" "io" "log/slog" "net/http" "strings" + "github.com/gocsaf/csaf/v3/internal/misc" "github.com/gocsaf/csaf/v3/util" ) @@ -33,7 +33,7 @@ type ProviderMetadataLoader struct { type ProviderMetadataLoadMessageType int const ( - //JSONDecodingFailed indicates problems with JSON decoding + // JSONDecodingFailed indicates problems with JSON decoding JSONDecodingFailed ProviderMetadataLoadMessageType = iota // SchemaValidationFailed indicates a general problem with schema validation. SchemaValidationFailed @@ -149,7 +149,6 @@ func (pmdl *ProviderMetadataLoader) Enumerate(domain string) []*LoadedProviderMe } dnsURL := "https://csaf.data.security." + domain return []*LoadedProviderMetadata{pmdl.loadFromURL(dnsURL)} - } // Load loads one valid provider metadata for a given path. @@ -323,7 +322,7 @@ func (pmdl *ProviderMetadataLoader) loadFromURL(path string) *LoadedProviderMeta var doc any - if err := json.NewDecoder(tee).Decode(&doc); err != nil { + if err := misc.StrictJSONParse(tee, &doc); err != nil { result.Messages.Add( JSONDecodingFailed, fmt.Sprintf("JSON decoding failed: %v", err)) diff --git a/csaf/remotevalidation.go b/csaf/remotevalidation.go index 9e99b6f..97d612e 100644 --- a/csaf/remotevalidation.go +++ b/csaf/remotevalidation.go @@ -18,6 +18,7 @@ import ( "net/http" "sync" + "github.com/gocsaf/csaf/v3/internal/misc" bolt "go.etcd.io/bbolt" ) @@ -180,7 +181,6 @@ func prepareCache(config string) (cache, error) { return create() } return nil - }); err != nil { db.Close() return nil, err @@ -256,7 +256,7 @@ func deserialize(value []byte) (*RemoteValidationResult, error) { } defer r.Close() var rvr RemoteValidationResult - if err := json.NewDecoder(r).Decode(&rvr); err != nil { + if err := misc.StrictJSONParse(r, &rvr); err != nil { return nil, err } return &rvr, nil @@ -323,7 +323,7 @@ func (v *remoteValidator) Validate(doc any) (*RemoteValidationResult, error) { // no cache -> process directly. in = resp.Body } - return json.NewDecoder(in).Decode(&rvr) + return misc.StrictJSONParse(in, &rvr) }(); err != nil { return nil, err } diff --git a/csaf/rolie.go b/csaf/rolie.go index b94cfa3..d3a5ac7 100644 --- a/csaf/rolie.go +++ b/csaf/rolie.go @@ -14,6 +14,7 @@ import ( "sort" "time" + "github.com/gocsaf/csaf/v3/internal/misc" "github.com/gocsaf/csaf/v3/util" ) @@ -54,7 +55,7 @@ type ROLIEServiceDocument struct { // LoadROLIEServiceDocument loads a ROLIE service document from a reader. func LoadROLIEServiceDocument(r io.Reader) (*ROLIEServiceDocument, error) { var rsd ROLIEServiceDocument - if err := json.NewDecoder(r).Decode(&rsd); err != nil { + if err := misc.StrictJSONParse(r, &rsd); err != nil { return nil, err } return &rsd, nil @@ -122,7 +123,7 @@ func (rcd *ROLIECategoryDocument) Merge(categories ...string) bool { // LoadROLIECategoryDocument loads a ROLIE category document from a reader. func LoadROLIECategoryDocument(r io.Reader) (*ROLIECategoryDocument, error) { var rcd ROLIECategoryDocument - if err := json.NewDecoder(r).Decode(&rcd); err != nil { + if err := misc.StrictJSONParse(r, &rcd); err != nil { return nil, err } return &rcd, nil @@ -195,9 +196,8 @@ type ROLIEFeed struct { // LoadROLIEFeed loads a ROLIE feed from a reader. func LoadROLIEFeed(r io.Reader) (*ROLIEFeed, error) { - dec := json.NewDecoder(r) var rf ROLIEFeed - if err := dec.Decode(&rf); err != nil { + if err := misc.StrictJSONParse(r, &rf); err != nil { return nil, err } return &rf, nil diff --git a/internal/misc/json.go b/internal/misc/json.go new file mode 100644 index 0000000..0bb2ec0 --- /dev/null +++ b/internal/misc/json.go @@ -0,0 +1,34 @@ +// This file is Free Software under the Apache-2.0 License +// without warranty, see README.md and LICENSES/Apache-2.0.txt for details. +// +// SPDX-License-Identifier: Apache-2.0 +// +// SPDX-FileCopyrightText: 2023 German Federal Office for Information Security (BSI) +// Software-Engineering: 2023 Intevation GmbH + +package misc + +import ( + "encoding/json" + "fmt" + "io" +) + +// StrictJSONParse provides JSON parsing with stronger validation. +func StrictJSONParse(jsonData io.Reader, target interface{}) error { + decoder := json.NewDecoder(jsonData) + + decoder.DisallowUnknownFields() + + err := decoder.Decode(target) + if err != nil { + return fmt.Errorf("strictJSONParse: %w", err) + } + + token, err := decoder.Token() + if err != io.EOF { + return fmt.Errorf("strictJSONParse: unexpected trailing data after JSON: token: %v, err: %v", token, err) + } + + return nil +} From e7c08d05cd78ee31a2547acc6b8bfcd85d4aaf04 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 3 Jul 2025 10:58:32 +0200 Subject: [PATCH 2/5] Rewrite function from scratch --- internal/misc/json.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/internal/misc/json.go b/internal/misc/json.go index 0bb2ec0..c30323d 100644 --- a/internal/misc/json.go +++ b/internal/misc/json.go @@ -3,8 +3,8 @@ // // SPDX-License-Identifier: Apache-2.0 // -// SPDX-FileCopyrightText: 2023 German Federal Office for Information Security (BSI) -// Software-Engineering: 2023 Intevation GmbH +// SPDX-FileCopyrightText: 2025 German Federal Office for Information Security (BSI) +// Software-Engineering: 2025 Intevation GmbH package misc @@ -14,20 +14,23 @@ import ( "io" ) -// StrictJSONParse provides JSON parsing with stronger validation. +// StrictJSONParse creates a JSON decoder that decodes an interface +// while not allowing unknown fields nor trailing data func StrictJSONParse(jsonData io.Reader, target interface{}) error { decoder := json.NewDecoder(jsonData) - + // Don't allow unknown fields decoder.DisallowUnknownFields() - err := decoder.Decode(target) - if err != nil { - return fmt.Errorf("strictJSONParse: %w", err) + if err := decoder.Decode(target); err != nil { + return fmt.Errorf("JSON decoding error: %w", err) } - token, err := decoder.Token() - if err != io.EOF { - return fmt.Errorf("strictJSONParse: unexpected trailing data after JSON: token: %v, err: %v", token, err) + // Check for any trailing data after the main JSON structure + if _, err := decoder.Token(); err != io.EOF { + if err != nil { + return fmt.Errorf("error reading trailing data: %w", err) + } + return fmt.Errorf("unexpected trailing data after JSON object") } return nil From c81f55a752b33236d1b35f980baedaaaa04dea32 Mon Sep 17 00:00:00 2001 From: koplas Date: Fri, 4 Jul 2025 15:29:03 +0200 Subject: [PATCH 3/5] Add LoadAdvisory tests --- csaf/advisory_test.go | 50 +++++ internal/misc/json.go | 2 +- .../avendor-advisory-0004.json | 171 ++++++++++++++++++ .../unknown-fields/avendor-advisory-0004.json | 171 ++++++++++++++++++ .../valid/avendor-advisory-0004.json | 170 +++++++++++++++++ 5 files changed, 563 insertions(+), 1 deletion(-) create mode 100644 csaf/advisory_test.go create mode 100644 testdata/csaf-documents/trailing-garbage-data/avendor-advisory-0004.json create mode 100644 testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json create mode 100644 testdata/csaf-documents/valid/avendor-advisory-0004.json diff --git a/csaf/advisory_test.go b/csaf/advisory_test.go new file mode 100644 index 0000000..062a713 --- /dev/null +++ b/csaf/advisory_test.go @@ -0,0 +1,50 @@ +package csaf + +import ( + "os" + "path/filepath" + "testing" +) + +func TestLoadAdvisory(t *testing.T) { + type args struct { + jsonDir string + } + tests := []struct { + name string + args args + wantErr bool + }{{ + name: "Valid documents", + args: args{jsonDir: "csaf-documents/valid"}, + wantErr: false, + }, + { + name: "Unknown fields", + args: args{jsonDir: "csaf-documents/unknown-fields"}, + wantErr: true, + }, + { + name: "Garbage trailing data", + args: args{jsonDir: "csaf-documents/trailing-garbage-data"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := filepath.Walk("../testdata/"+tt.args.jsonDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode().IsRegular() && filepath.Ext(info.Name()) == ".json" { + if _, err := LoadAdvisory(path); (err != nil) != tt.wantErr { + t.Errorf("LoadAdvisory() error = %v, wantErr %v", err, tt.wantErr) + } + } + return nil + }); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/internal/misc/json.go b/internal/misc/json.go index c30323d..653c166 100644 --- a/internal/misc/json.go +++ b/internal/misc/json.go @@ -16,7 +16,7 @@ import ( // StrictJSONParse creates a JSON decoder that decodes an interface // while not allowing unknown fields nor trailing data -func StrictJSONParse(jsonData io.Reader, target interface{}) error { +func StrictJSONParse(jsonData io.Reader, target any) error { decoder := json.NewDecoder(jsonData) // Don't allow unknown fields decoder.DisallowUnknownFields() diff --git a/testdata/csaf-documents/trailing-garbage-data/avendor-advisory-0004.json b/testdata/csaf-documents/trailing-garbage-data/avendor-advisory-0004.json new file mode 100644 index 0000000..2131136 --- /dev/null +++ b/testdata/csaf-documents/trailing-garbage-data/avendor-advisory-0004.json @@ -0,0 +1,171 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "distribution": { + "tlp": { + "label": "WHITE", + "url": "https://www.first.org/tlp/v1/" + } + }, + "notes": [ + { + "category": "summary", + "title": "Test document summary", + "text": "Auto generated test CSAF document" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc.", + "namespace": "https://www.example.com" + }, + "title": "Test CSAF document", + "tracking": { + "current_release_date": "2020-01-01T00:00:00Z", + "generator": { + "date": "2020-01-01T00:00:00Z", + "engine": { + "name": "csaf-tool", + "version": "0.3.2" + } + }, + "id": "Avendor-advisory-0004", + "initial_release_date": "2020-01-01T00:00:00Z", + "revision_history": [ + { + "date": "2020-01-01T00:00:00Z", + "number": "1", + "summary": "Initial version" + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_1", + "branches": [ + { + "category": "product_version", + "name": "1.1", + "product": { + "name": "AVendor product_1 1.1", + "product_id": "CSAFPID_0001" + } + }, + { + "category": "product_version", + "name": "1.2", + "product": { + "name": "AVendor product_1 1.2", + "product_id": "CSAFPID_0002" + } + }, + { + "category": "product_version", + "name": "2.0", + "product": { + "name": "AVendor product_1 2.0", + "product_id": "CSAFPID_0003" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor1", + "branches": [ + { + "category": "product_name", + "name": "product_2", + "branches": [ + { + "category": "product_version", + "name": "1", + "product": { + "name": "AVendor1 product_2 1", + "product_id": "CSAFPID_0004" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_3", + "branches": [ + { + "category": "product_version", + "name": "2022H2", + "product": { + "name": "AVendor product_3 2022H2", + "product_id": "CSAFPID_0005" + } + } + ] + } + ] + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2020-1234", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-1234" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Customers should upgrade to the latest version of the product", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + }, + { + "cve": "CVE-2020-9876", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-9876" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Still under investigation", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + } + ] +} +invalid data diff --git a/testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json b/testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json new file mode 100644 index 0000000..17321ae --- /dev/null +++ b/testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json @@ -0,0 +1,171 @@ +{ + "document": { + "unknown-field": false, + "category": "csaf_vex", + "csaf_version": "2.0", + "distribution": { + "tlp": { + "label": "WHITE", + "url": "https://www.first.org/tlp/v1/" + } + }, + "notes": [ + { + "category": "summary", + "title": "Test document summary", + "text": "Auto generated test CSAF document" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc.", + "namespace": "https://www.example.com" + }, + "title": "Test CSAF document", + "tracking": { + "current_release_date": "2020-01-01T00:00:00Z", + "generator": { + "date": "2020-01-01T00:00:00Z", + "engine": { + "name": "csaf-tool", + "version": "0.3.2" + } + }, + "id": "Avendor-advisory-0004", + "initial_release_date": "2020-01-01T00:00:00Z", + "revision_history": [ + { + "date": "2020-01-01T00:00:00Z", + "number": "1", + "summary": "Initial version" + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_1", + "branches": [ + { + "category": "product_version", + "name": "1.1", + "product": { + "name": "AVendor product_1 1.1", + "product_id": "CSAFPID_0001" + } + }, + { + "category": "product_version", + "name": "1.2", + "product": { + "name": "AVendor product_1 1.2", + "product_id": "CSAFPID_0002" + } + }, + { + "category": "product_version", + "name": "2.0", + "product": { + "name": "AVendor product_1 2.0", + "product_id": "CSAFPID_0003" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor1", + "branches": [ + { + "category": "product_name", + "name": "product_2", + "branches": [ + { + "category": "product_version", + "name": "1", + "product": { + "name": "AVendor1 product_2 1", + "product_id": "CSAFPID_0004" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_3", + "branches": [ + { + "category": "product_version", + "name": "2022H2", + "product": { + "name": "AVendor product_3 2022H2", + "product_id": "CSAFPID_0005" + } + } + ] + } + ] + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2020-1234", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-1234" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Customers should upgrade to the latest version of the product", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + }, + { + "cve": "CVE-2020-9876", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-9876" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Still under investigation", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + } + ] +} diff --git a/testdata/csaf-documents/valid/avendor-advisory-0004.json b/testdata/csaf-documents/valid/avendor-advisory-0004.json new file mode 100644 index 0000000..0e194e9 --- /dev/null +++ b/testdata/csaf-documents/valid/avendor-advisory-0004.json @@ -0,0 +1,170 @@ +{ + "document": { + "category": "csaf_vex", + "csaf_version": "2.0", + "distribution": { + "tlp": { + "label": "WHITE", + "url": "https://www.first.org/tlp/v1/" + } + }, + "notes": [ + { + "category": "summary", + "title": "Test document summary", + "text": "Auto generated test CSAF document" + } + ], + "publisher": { + "category": "vendor", + "name": "ACME Inc.", + "namespace": "https://www.example.com" + }, + "title": "Test CSAF document", + "tracking": { + "current_release_date": "2020-01-01T00:00:00Z", + "generator": { + "date": "2020-01-01T00:00:00Z", + "engine": { + "name": "csaf-tool", + "version": "0.3.2" + } + }, + "id": "Avendor-advisory-0004", + "initial_release_date": "2020-01-01T00:00:00Z", + "revision_history": [ + { + "date": "2020-01-01T00:00:00Z", + "number": "1", + "summary": "Initial version" + } + ], + "status": "final", + "version": "1" + } + }, + "product_tree": { + "branches": [ + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_1", + "branches": [ + { + "category": "product_version", + "name": "1.1", + "product": { + "name": "AVendor product_1 1.1", + "product_id": "CSAFPID_0001" + } + }, + { + "category": "product_version", + "name": "1.2", + "product": { + "name": "AVendor product_1 1.2", + "product_id": "CSAFPID_0002" + } + }, + { + "category": "product_version", + "name": "2.0", + "product": { + "name": "AVendor product_1 2.0", + "product_id": "CSAFPID_0003" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor1", + "branches": [ + { + "category": "product_name", + "name": "product_2", + "branches": [ + { + "category": "product_version", + "name": "1", + "product": { + "name": "AVendor1 product_2 1", + "product_id": "CSAFPID_0004" + } + } + ] + } + ] + }, + { + "category": "vendor", + "name": "AVendor", + "branches": [ + { + "category": "product_name", + "name": "product_3", + "branches": [ + { + "category": "product_version", + "name": "2022H2", + "product": { + "name": "AVendor product_3 2022H2", + "product_id": "CSAFPID_0005" + } + } + ] + } + ] + } + ] + }, + "vulnerabilities": [ + { + "cve": "CVE-2020-1234", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-1234" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Customers should upgrade to the latest version of the product", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + }, + { + "cve": "CVE-2020-9876", + "notes": [ + { + "category": "description", + "title": "CVE description", + "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-9876" + } + ], + "product_status": { + "under_investigation": ["CSAFPID_0001"] + }, + "threats": [ + { + "category": "impact", + "details": "Still under investigation", + "date": "2020-01-01T00:00:00Z", + "product_ids": ["CSAFPID_0001"] + } + ] + } + ] +} From 7935818600ee70cbcb7784a67788a4f3bacaba01 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Mon, 7 Jul 2025 11:41:49 +0200 Subject: [PATCH 4/5] Fix: Allow unknown fields: They are not forbidden --- internal/misc/json.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/misc/json.go b/internal/misc/json.go index 653c166..4ecc6a5 100644 --- a/internal/misc/json.go +++ b/internal/misc/json.go @@ -18,8 +18,6 @@ import ( // while not allowing unknown fields nor trailing data func StrictJSONParse(jsonData io.Reader, target any) error { decoder := json.NewDecoder(jsonData) - // Don't allow unknown fields - decoder.DisallowUnknownFields() if err := decoder.Decode(target); err != nil { return fmt.Errorf("JSON decoding error: %w", err) From 4b4d6ed5943c5bf0e953e22454a4da55302b5a15 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Mon, 7 Jul 2025 11:45:36 +0200 Subject: [PATCH 5/5] Remove uknown field tests --- csaf/advisory_test.go | 5 - .../unknown-fields/avendor-advisory-0004.json | 171 ------------------ 2 files changed, 176 deletions(-) delete mode 100644 testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json diff --git a/csaf/advisory_test.go b/csaf/advisory_test.go index 062a713..9a82884 100644 --- a/csaf/advisory_test.go +++ b/csaf/advisory_test.go @@ -19,11 +19,6 @@ func TestLoadAdvisory(t *testing.T) { args: args{jsonDir: "csaf-documents/valid"}, wantErr: false, }, - { - name: "Unknown fields", - args: args{jsonDir: "csaf-documents/unknown-fields"}, - wantErr: true, - }, { name: "Garbage trailing data", args: args{jsonDir: "csaf-documents/trailing-garbage-data"}, diff --git a/testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json b/testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json deleted file mode 100644 index 17321ae..0000000 --- a/testdata/csaf-documents/unknown-fields/avendor-advisory-0004.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "document": { - "unknown-field": false, - "category": "csaf_vex", - "csaf_version": "2.0", - "distribution": { - "tlp": { - "label": "WHITE", - "url": "https://www.first.org/tlp/v1/" - } - }, - "notes": [ - { - "category": "summary", - "title": "Test document summary", - "text": "Auto generated test CSAF document" - } - ], - "publisher": { - "category": "vendor", - "name": "ACME Inc.", - "namespace": "https://www.example.com" - }, - "title": "Test CSAF document", - "tracking": { - "current_release_date": "2020-01-01T00:00:00Z", - "generator": { - "date": "2020-01-01T00:00:00Z", - "engine": { - "name": "csaf-tool", - "version": "0.3.2" - } - }, - "id": "Avendor-advisory-0004", - "initial_release_date": "2020-01-01T00:00:00Z", - "revision_history": [ - { - "date": "2020-01-01T00:00:00Z", - "number": "1", - "summary": "Initial version" - } - ], - "status": "final", - "version": "1" - } - }, - "product_tree": { - "branches": [ - { - "category": "vendor", - "name": "AVendor", - "branches": [ - { - "category": "product_name", - "name": "product_1", - "branches": [ - { - "category": "product_version", - "name": "1.1", - "product": { - "name": "AVendor product_1 1.1", - "product_id": "CSAFPID_0001" - } - }, - { - "category": "product_version", - "name": "1.2", - "product": { - "name": "AVendor product_1 1.2", - "product_id": "CSAFPID_0002" - } - }, - { - "category": "product_version", - "name": "2.0", - "product": { - "name": "AVendor product_1 2.0", - "product_id": "CSAFPID_0003" - } - } - ] - } - ] - }, - { - "category": "vendor", - "name": "AVendor1", - "branches": [ - { - "category": "product_name", - "name": "product_2", - "branches": [ - { - "category": "product_version", - "name": "1", - "product": { - "name": "AVendor1 product_2 1", - "product_id": "CSAFPID_0004" - } - } - ] - } - ] - }, - { - "category": "vendor", - "name": "AVendor", - "branches": [ - { - "category": "product_name", - "name": "product_3", - "branches": [ - { - "category": "product_version", - "name": "2022H2", - "product": { - "name": "AVendor product_3 2022H2", - "product_id": "CSAFPID_0005" - } - } - ] - } - ] - } - ] - }, - "vulnerabilities": [ - { - "cve": "CVE-2020-1234", - "notes": [ - { - "category": "description", - "title": "CVE description", - "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-1234" - } - ], - "product_status": { - "under_investigation": ["CSAFPID_0001"] - }, - "threats": [ - { - "category": "impact", - "details": "Customers should upgrade to the latest version of the product", - "date": "2020-01-01T00:00:00Z", - "product_ids": ["CSAFPID_0001"] - } - ] - }, - { - "cve": "CVE-2020-9876", - "notes": [ - { - "category": "description", - "title": "CVE description", - "text": "https://nvd.nist.gov/vuln/detail/CVE-2020-9876" - } - ], - "product_status": { - "under_investigation": ["CSAFPID_0001"] - }, - "threats": [ - { - "category": "impact", - "details": "Still under investigation", - "date": "2020-01-01T00:00:00Z", - "product_ids": ["CSAFPID_0001"] - } - ] - } - ] -}