From 5315f39aa82d3c9b49fa9b870f73c3f4856146c3 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 9 Sep 2024 12:37:58 +0600 Subject: [PATCH 1/6] fix(ModifiedFinding): add MarshalJSON function --- pkg/types/finding.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/pkg/types/finding.go b/pkg/types/finding.go index 9e194b8e6b74..90564f1300de 100644 --- a/pkg/types/finding.go +++ b/pkg/types/finding.go @@ -1,5 +1,11 @@ package types +import ( + "encoding/json" + + "golang.org/x/xerrors" +) + type FindingType string type FindingStatus string @@ -45,3 +51,44 @@ func NewModifiedFinding(f finding, status FindingStatus, statement, source strin Finding: f, } } + +// MarshalJSON correctly marshals ModifiedFinding.Finding given the type and `MarshalJSON` functions of struct fields +func (m *ModifiedFinding) MarshalJSON() ([]byte, error) { + var raw struct { + Type FindingType `json:"Type"` + Status FindingStatus `json:"Status"` + Statement string `json:"Statement"` + Source string `json:"Source"` + Finding json.RawMessage `json:"Finding"` + } + raw.Type = m.Type + raw.Status = m.Status + raw.Statement = m.Statement + raw.Source = m.Source + + // Define a `Finding` type and marshal as a struct of that type. + // This is necessary to run the `MarshalJSON` functions on the struct fields. + var err error + switch val := m.Finding.(type) { + case DetectedVulnerability: + if raw.Finding, err = json.Marshal(&val); err != nil { + return nil, xerrors.Errorf("unable to marshal `DetectedVulnerability` Findings: %w", err) + } + case DetectedMisconfiguration: + if raw.Finding, err = json.Marshal(&val); err != nil { + return nil, xerrors.Errorf("unable to marshal `DetectedMisconfiguration` Findings: %w", err) + } + case DetectedSecret: + if raw.Finding, err = json.Marshal(&val); err != nil { + return nil, xerrors.Errorf("unable to marshal `DetectedSecret` Findings: %w", err) + } + case DetectedLicense: + if raw.Finding, err = json.Marshal(&val); err != nil { + return nil, xerrors.Errorf("unable to marshal `DetectedLicense` Findings: %w", err) + } + default: + return nil, xerrors.Errorf("invalid Finding type: %T", val) + } + + return json.Marshal(&raw) +} From 5f2021f17de16e6e9066af52a809ed99312272e1 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 9 Sep 2024 13:13:17 +0600 Subject: [PATCH 2/6] fix(ModifiedFinding): add UnmarshalJSON function --- pkg/types/finding.go | 52 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/pkg/types/finding.go b/pkg/types/finding.go index 90564f1300de..50cd5354fddf 100644 --- a/pkg/types/finding.go +++ b/pkg/types/finding.go @@ -92,3 +92,55 @@ func (m *ModifiedFinding) MarshalJSON() ([]byte, error) { return json.Marshal(&raw) } + +// UnmarshalJSON unmarshals ModifiedFinding given the type and `UnmarshalJSON` functions of struct fields +func (m *ModifiedFinding) UnmarshalJSON(data []byte) error { + raw := struct { + Type FindingType `json:"Type"` + Status FindingStatus `json:"Status"` + Statement string `json:"Statement"` + Source string `json:"Source"` + Finding json.RawMessage `json:"Finding"` + }{} + + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + m.Type = raw.Type + m.Status = raw.Status + m.Statement = raw.Statement + m.Source = raw.Source + + // Select struct by m.Type to avoid errors with Unmarshal + switch m.Type { + case FindingTypeVulnerability: + rawFinding := DetectedVulnerability{} + if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) + } + m.Finding = rawFinding + case FindingTypeMisconfiguration: + rawFinding := DetectedMisconfiguration{} + if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) + } + m.Finding = rawFinding + case FindingTypeSecret: + rawFinding := DetectedSecret{} + if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) + } + m.Finding = rawFinding + case FindingTypeLicense: + rawFinding := DetectedLicense{} + if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) + } + m.Finding = rawFinding + default: + return xerrors.Errorf("invalid Finding type: %s", m.Type) + } + + return nil +} From 5fff7ac7db087a9d1e240b24b89b99fa2aebfce5 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 9 Sep 2024 13:54:22 +0600 Subject: [PATCH 3/6] test(integration): add test with suppressed vulnerability --- integration/convert_test.go | 26 ++- .../convert/npm-with-suppressed.json.golden | 195 ++++++++++++++++++ 2 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 integration/testdata/fixtures/convert/npm-with-suppressed.json.golden diff --git a/integration/convert_test.go b/integration/convert_test.go index 803ba538dcba..e7d2a5f5e6fe 100644 --- a/integration/convert_test.go +++ b/integration/convert_test.go @@ -11,9 +11,11 @@ import ( func TestConvert(t *testing.T) { type args struct { - input string - format string - scanners string + input string + format string + scanners string + showSuppressed bool + listAllPkgs bool } tests := []struct { name string @@ -37,6 +39,16 @@ func TestConvert(t *testing.T) { }, golden: "testdata/npm-cyclonedx.json.golden", }, + { + name: "npm with suppressed vulnerability", + args: args{ + input: "testdata/fixtures/convert/npm-with-suppressed.json.golden", + format: "json", + showSuppressed: true, + listAllPkgs: true, + }, + golden: "testdata/fixtures/convert/npm-with-suppressed.json.golden", + }, } for _, tt := range tests { @@ -50,6 +62,14 @@ func TestConvert(t *testing.T) { tt.args.format, } + if tt.args.showSuppressed { + osArgs = append(osArgs, "--show-suppressed") + } + + if tt.args.listAllPkgs { + osArgs = append(osArgs, "--list-all-pkgs") + } + // Set up the output file outputFile := filepath.Join(t.TempDir(), "output.json") if *update { diff --git a/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden b/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden new file mode 100644 index 000000000000..0ce14e33545b --- /dev/null +++ b/integration/testdata/fixtures/convert/npm-with-suppressed.json.golden @@ -0,0 +1,195 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2024-09-09T13:21:09.230231+06:00", + "ArtifactName": "package-lock.json", + "ArtifactType": "filesystem", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "package-lock.json", + "Class": "lang-pkgs", + "Type": "npm", + "Packages": [ + { + "ID": "debug@3.0.1", + "Name": "debug", + "Identifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "Version": "3.0.1", + "Relationship": "direct", + "DependsOn": [ + "ms@2.0.0" + ], + "Layer": {}, + "Locations": [ + { + "StartLine": 11, + "EndLine": 19 + } + ] + }, + { + "ID": "ms@2.0.0", + "Name": "ms", + "Identifier": { + "PURL": "pkg:npm/ms@2.0.0", + "UID": "f51af0181daf2ced" + }, + "Version": "2.0.0", + "Indirect": true, + "Relationship": "indirect", + "Layer": {}, + "Locations": [ + { + "StartLine": 20, + "EndLine": 25 + } + ] + } + ], + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2017-20165", + "PkgID": "debug@3.0.1", + "PkgName": "debug", + "PkgIdentifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "InstalledVersion": "3.0.1", + "FixedVersion": "3.1.0, 2.6.9", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2017-20165", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "A vulnerability classified as problematic has been found in debug-js d ...", + "Description": "A vulnerability classified as problematic has been found in debug-js debug up to 3.0.x. This affects the function useColors of the file src/node.js. The manipulation of the argument str leads to inefficient regular expression complexity. Upgrading to version 3.1.0 is able to address this issue. The identifier of the patch is c38a0166c266a679c8de012d4eaccec3f944e685. It is recommended to upgrade the affected component. The identifier VDB-217665 was assigned to this vulnerability.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-1333" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + }, + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "V3Score": 7.5 + } + }, + "References": [ + "https://github.com/debug-js/debug", + "https://github.com/debug-js/debug/commit/c38a0166c266a679c8de012d4eaccec3f944e685", + "https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a", + "https://github.com/debug-js/debug/pull/504", + "https://github.com/debug-js/debug/releases/tag/2.6.9", + "https://github.com/debug-js/debug/releases/tag/3.1.0", + "https://nvd.nist.gov/vuln/detail/CVE-2017-20165", + "https://vuldb.com/?ctiid.217665", + "https://vuldb.com/?id.217665" + ], + "PublishedDate": "2023-01-09T10:15:10.447Z", + "LastModifiedDate": "2024-05-17T01:17:24.28Z" + } + ], + "ExperimentalModifiedFindings": [ + { + "Type": "vulnerability", + "Status": "not_affected", + "Statement": "vulnerable_code_not_in_execute_path", + "Source": "./vex.json", + "Finding": { + "VulnerabilityID": "CVE-2017-16137", + "PkgID": "debug@3.0.1", + "PkgName": "debug", + "PkgIdentifier": { + "PURL": "pkg:npm/debug@3.0.1", + "UID": "45acc377fa09cc3" + }, + "InstalledVersion": "3.0.1", + "FixedVersion": "2.6.9, 3.1.0, 3.2.7, 4.3.1", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2017-16137", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory npm", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anpm" + }, + "Title": "nodejs-debug: Regular expression Denial of Service", + "Description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the o formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.", + "Severity": "LOW", + "CweIDs": [ + "CWE-400" + ], + "VendorSeverity": { + "ghsa": 1, + "nvd": 2, + "redhat": 2 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 3.7 + }, + "nvd": { + "V2Vector": "AV:N/AC:L/Au:N/C:N/I:N/A:P", + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V2Score": 5, + "V3Score": 5.3 + }, + "redhat": { + "V3Vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "V3Score": 5.3 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2017-16137", + "https://github.com/debug-js/debug/commit/4e2150207c568adb9ead8f4c4528016081c88020", + "https://github.com/debug-js/debug/commit/71169065b5262f9858ac78cc0b688c84a438f290", + "https://github.com/debug-js/debug/commit/b6d12fdbc63b483e5c969da33ea6adc09946b5ac", + "https://github.com/debug-js/debug/commit/f53962e944a87e6ca9bb622a2a12dffc22a9bb5a", + "https://github.com/debug-js/debug/issues/797", + "https://github.com/visionmedia/debug", + "https://github.com/visionmedia/debug/issues/501", + "https://github.com/visionmedia/debug/pull/504", + "https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3%40%3Ccommits.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/r8ba4c628fba7181af58817d452119481adce4ba92e889c643e4c7dd3@%3Ccommits.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63%40%3Cnotifications.netbeans.apache.org%3E", + "https://lists.apache.org/thread.html/rb5ac16fad337d1f3bb7079549f97d8166d0ef3082629417c39f12d63@%3Cnotifications.netbeans.apache.org%3E", + "https://nodesecurity.io/advisories/534", + "https://nvd.nist.gov/vuln/detail/CVE-2017-16137", + "https://www.cve.org/CVERecord?id=CVE-2017-16137" + ], + "PublishedDate": "2018-06-07T02:29:03.817Z", + "LastModifiedDate": "2023-11-07T02:40:28.13Z" + } + } + ] + } + ] +} From a2b229dce9a1d39b3d4316e3657e999943e8c8ce Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 10 Sep 2024 15:49:27 +0600 Subject: [PATCH 4/6] refactor: remove `MarshalJSON()` function --- pkg/types/finding.go | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/pkg/types/finding.go b/pkg/types/finding.go index 50cd5354fddf..68f80c920ea8 100644 --- a/pkg/types/finding.go +++ b/pkg/types/finding.go @@ -52,47 +52,6 @@ func NewModifiedFinding(f finding, status FindingStatus, statement, source strin } } -// MarshalJSON correctly marshals ModifiedFinding.Finding given the type and `MarshalJSON` functions of struct fields -func (m *ModifiedFinding) MarshalJSON() ([]byte, error) { - var raw struct { - Type FindingType `json:"Type"` - Status FindingStatus `json:"Status"` - Statement string `json:"Statement"` - Source string `json:"Source"` - Finding json.RawMessage `json:"Finding"` - } - raw.Type = m.Type - raw.Status = m.Status - raw.Statement = m.Statement - raw.Source = m.Source - - // Define a `Finding` type and marshal as a struct of that type. - // This is necessary to run the `MarshalJSON` functions on the struct fields. - var err error - switch val := m.Finding.(type) { - case DetectedVulnerability: - if raw.Finding, err = json.Marshal(&val); err != nil { - return nil, xerrors.Errorf("unable to marshal `DetectedVulnerability` Findings: %w", err) - } - case DetectedMisconfiguration: - if raw.Finding, err = json.Marshal(&val); err != nil { - return nil, xerrors.Errorf("unable to marshal `DetectedMisconfiguration` Findings: %w", err) - } - case DetectedSecret: - if raw.Finding, err = json.Marshal(&val); err != nil { - return nil, xerrors.Errorf("unable to marshal `DetectedSecret` Findings: %w", err) - } - case DetectedLicense: - if raw.Finding, err = json.Marshal(&val); err != nil { - return nil, xerrors.Errorf("unable to marshal `DetectedLicense` Findings: %w", err) - } - default: - return nil, xerrors.Errorf("invalid Finding type: %T", val) - } - - return json.Marshal(&raw) -} - // UnmarshalJSON unmarshals ModifiedFinding given the type and `UnmarshalJSON` functions of struct fields func (m *ModifiedFinding) UnmarshalJSON(data []byte) error { raw := struct { From 38b0d2bd83e623d837938ffad01c89dd3f7c7bb8 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 10 Sep 2024 15:49:43 +0600 Subject: [PATCH 5/6] refactor: update `UnmarshalJSON()` function --- pkg/types/finding.go | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/pkg/types/finding.go b/pkg/types/finding.go index 68f80c920ea8..a119be773caa 100644 --- a/pkg/types/finding.go +++ b/pkg/types/finding.go @@ -54,46 +54,41 @@ func NewModifiedFinding(f finding, status FindingStatus, statement, source strin // UnmarshalJSON unmarshals ModifiedFinding given the type and `UnmarshalJSON` functions of struct fields func (m *ModifiedFinding) UnmarshalJSON(data []byte) error { - raw := struct { - Type FindingType `json:"Type"` - Status FindingStatus `json:"Status"` - Statement string `json:"Statement"` - Source string `json:"Source"` - Finding json.RawMessage `json:"Finding"` - }{} + type Alias ModifiedFinding + aux := &struct { + Finding json.RawMessage `json:"Finding"` + *Alias + }{ + Alias: (*Alias)(m), + } - if err := json.Unmarshal(data, &raw); err != nil { + if err := json.Unmarshal(data, &aux); err != nil { return err } - m.Type = raw.Type - m.Status = raw.Status - m.Statement = raw.Statement - m.Source = raw.Source - // Select struct by m.Type to avoid errors with Unmarshal switch m.Type { case FindingTypeVulnerability: rawFinding := DetectedVulnerability{} - if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) } m.Finding = rawFinding case FindingTypeMisconfiguration: rawFinding := DetectedMisconfiguration{} - if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) } m.Finding = rawFinding case FindingTypeSecret: rawFinding := DetectedSecret{} - if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) } m.Finding = rawFinding case FindingTypeLicense: rawFinding := DetectedLicense{} - if err := json.Unmarshal(raw.Finding, &rawFinding); err != nil { + if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) } m.Finding = rawFinding From ae75daa3c0559592927e8ddf5dd8208df4394fcc Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Wed, 11 Sep 2024 09:59:15 +0400 Subject: [PATCH 6/6] refactor: do not repeat json.Unmarshal Signed-off-by: knqyf263 --- pkg/types/finding.go | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/pkg/types/finding.go b/pkg/types/finding.go index a119be773caa..65bc4b930fc9 100644 --- a/pkg/types/finding.go +++ b/pkg/types/finding.go @@ -67,34 +67,28 @@ func (m *ModifiedFinding) UnmarshalJSON(data []byte) error { } // Select struct by m.Type to avoid errors with Unmarshal + var err error switch m.Type { case FindingTypeVulnerability: - rawFinding := DetectedVulnerability{} - if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { - return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) - } - m.Finding = rawFinding + m.Finding, err = unmarshalFinding[DetectedVulnerability](aux.Finding) case FindingTypeMisconfiguration: - rawFinding := DetectedMisconfiguration{} - if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { - return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) - } - m.Finding = rawFinding + m.Finding, err = unmarshalFinding[DetectedMisconfiguration](aux.Finding) case FindingTypeSecret: - rawFinding := DetectedSecret{} - if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { - return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) - } - m.Finding = rawFinding + m.Finding, err = unmarshalFinding[DetectedSecret](aux.Finding) case FindingTypeLicense: - rawFinding := DetectedLicense{} - if err := json.Unmarshal(aux.Finding, &rawFinding); err != nil { - return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) - } - m.Finding = rawFinding + m.Finding, err = unmarshalFinding[DetectedLicense](aux.Finding) default: return xerrors.Errorf("invalid Finding type: %s", m.Type) } + if err != nil { + return xerrors.Errorf("unable to unmarshal %q type: %w", m.Type, err) + } return nil } + +func unmarshalFinding[T finding](data []byte) (T, error) { + var f T + err := json.Unmarshal(data, &f) + return f, err +}