From eb55f901be47547588682ba61c462f06c1627633 Mon Sep 17 00:00:00 2001 From: Matheus Alcantara Date: Thu, 23 Dec 2021 10:57:30 -0300 Subject: [PATCH] npmaudit:chore - improve tests and code cleaning This commit add some new asserts on successful parsing npm results to verify that all fields of Vulnerability was filled. Note that the test cases from `TestParseOutputNpm` was moved to `TestNpmAuditParseOutput` to centralize all tests as it is done in the other tests of the other formatters. Some code organization was also made, and the entities packages was removed and the npm schema output was moved to npmaudit package. Updates #718 Signed-off-by: Matheus Alcantara --- .../javascript/npmaudit/entities/finding.go | 19 -- .../javascript/npmaudit/entities/metadata.go | 19 -- .../javascript/npmaudit/entities/output.go | 20 -- .../npmaudit/entities/vulnerabilities.go | 23 -- .../javascript/npmaudit/formatter.go | 51 ++-- .../javascript/npmaudit/formatter_test.go | 218 ++++++++++-------- .../npmaudit/{entities/issue.go => output.go} | 43 +++- .../issue_test.go => output_test.go} | 32 +-- 8 files changed, 190 insertions(+), 235 deletions(-) delete mode 100644 internal/services/formatters/javascript/npmaudit/entities/finding.go delete mode 100644 internal/services/formatters/javascript/npmaudit/entities/metadata.go delete mode 100644 internal/services/formatters/javascript/npmaudit/entities/output.go delete mode 100644 internal/services/formatters/javascript/npmaudit/entities/vulnerabilities.go rename internal/services/formatters/javascript/npmaudit/{entities/issue.go => output.go} (51%) rename internal/services/formatters/javascript/npmaudit/{entities/issue_test.go => output_test.go} (72%) diff --git a/internal/services/formatters/javascript/npmaudit/entities/finding.go b/internal/services/formatters/javascript/npmaudit/entities/finding.go deleted file mode 100644 index f9c3d6610..000000000 --- a/internal/services/formatters/javascript/npmaudit/entities/finding.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package entities - -type Finding struct { - Version string `json:"version"` -} diff --git a/internal/services/formatters/javascript/npmaudit/entities/metadata.go b/internal/services/formatters/javascript/npmaudit/entities/metadata.go deleted file mode 100644 index 4d637abe8..000000000 --- a/internal/services/formatters/javascript/npmaudit/entities/metadata.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package entities - -type Metadata struct { - Vulnerabilities Vulnerabilities `json:"vulnerabilities"` -} diff --git a/internal/services/formatters/javascript/npmaudit/entities/output.go b/internal/services/formatters/javascript/npmaudit/entities/output.go deleted file mode 100644 index f6fe949d7..000000000 --- a/internal/services/formatters/javascript/npmaudit/entities/output.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package entities - -type Output struct { - Advisories map[string]Issue `json:"advisories"` - Metadata Metadata `json:"metadata"` -} diff --git a/internal/services/formatters/javascript/npmaudit/entities/vulnerabilities.go b/internal/services/formatters/javascript/npmaudit/entities/vulnerabilities.go deleted file mode 100644 index 7fc6ea94a..000000000 --- a/internal/services/formatters/javascript/npmaudit/entities/vulnerabilities.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020 ZUP IT SERVICOS EM TECNOLOGIA E INOVACAO SA -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package entities - -type Vulnerabilities struct { - Info int `json:"info"` - Low int `json:"low"` - Moderate int `json:"moderate"` - High int `json:"high"` - Critical int `json:"critical"` -} diff --git a/internal/services/formatters/javascript/npmaudit/formatter.go b/internal/services/formatters/javascript/npmaudit/formatter.go index cbe1a4615..4b0a30762 100644 --- a/internal/services/formatters/javascript/npmaudit/formatter.go +++ b/internal/services/formatters/javascript/npmaudit/formatter.go @@ -29,11 +29,10 @@ import ( "github.com/ZupIT/horusec-devkit/pkg/enums/tools" "github.com/ZupIT/horusec-devkit/pkg/utils/logger" - dockerEntities "github.com/ZupIT/horusec/internal/entities/docker" + "github.com/ZupIT/horusec/internal/entities/docker" "github.com/ZupIT/horusec/internal/enums/images" "github.com/ZupIT/horusec/internal/helpers/messages" "github.com/ZupIT/horusec/internal/services/formatters" - "github.com/ZupIT/horusec/internal/services/formatters/javascript/npmaudit/entities" vulnhash "github.com/ZupIT/horusec/internal/utils/vuln_hash" ) @@ -69,8 +68,8 @@ func (f *Formatter) startNpmAudit(projectSubPath string) (string, error) { return output, f.parseOutput(output, projectSubPath) } -func (f *Formatter) getDockerConfig(projectSubPath string) *dockerEntities.AnalysisData { - analysisData := &dockerEntities.AnalysisData{ +func (f *Formatter) getDockerConfig(projectSubPath string) *docker.AnalysisData { + analysisData := &docker.AnalysisData{ CMD: f.GetConfigCMDByFileExtension(projectSubPath, CMD, "package-lock.json", tools.NpmAudit), Language: languages.Javascript, } @@ -100,56 +99,50 @@ func (f *Formatter) IsNotFoundError(containerOutput string) error { return nil } -func (f *Formatter) newContainerOutputFromString(containerOutput string) (output *entities.Output, err error) { +func (f *Formatter) newContainerOutputFromString(containerOutput string) (output *npmOutput, err error) { if containerOutput == "" { logger.LogDebugWithLevel(messages.MsgDebugOutputEmpty, map[string]interface{}{"tool": tools.NpmAudit.ToString()}) - return &entities.Output{}, nil + return &npmOutput{}, nil } return output, json.Unmarshal([]byte(containerOutput), &output) } -func (f *Formatter) processOutput(output *entities.Output, projectSubPath string) { +func (f *Formatter) processOutput(output *npmOutput, projectSubPath string) { for _, advisory := range output.Advisories { advisoryPointer := advisory - f.AddNewVulnerabilityIntoAnalysis(f.setVulnerabilitySeverityData(&advisoryPointer, projectSubPath)) + f.AddNewVulnerabilityIntoAnalysis(f.newVulnerability(&advisoryPointer, projectSubPath)) } } -func (f *Formatter) setVulnerabilitySeverityData( - issue *entities.Issue, projectSubPath string) (data *vulnerability.Vulnerability) { - data = f.getDefaultVulnerabilitySeverity(projectSubPath) - data.Severity = issue.GetSeverity() - data.Details = issue.Overview - data.Code = issue.ModuleName - data.Line = f.getVulnerabilityLineByName(f.getVersionText(issue.GetVersion()), data.Code, data.File) - data = vulnhash.Bind(data) - return f.SetCommitAuthor(data) -} - -func (f *Formatter) getDefaultVulnerabilitySeverity(projectSubPath string) *vulnerability.Vulnerability { - vulnerabilitySeverity := &vulnerability.Vulnerability{} - vulnerabilitySeverity.File = f.GetFilepathFromFilename("package-lock.json", projectSubPath) - vulnerabilitySeverity.SecurityTool = tools.NpmAudit - vulnerabilitySeverity.Language = languages.Javascript - return vulnerabilitySeverity +func (f *Formatter) newVulnerability(issue *npmIssue, projectSubPath string) *vulnerability.Vulnerability { + vuln := &vulnerability.Vulnerability{ + File: f.GetFilepathFromFilename("package-lock.json", projectSubPath), + SecurityTool: tools.NpmAudit, + Language: languages.Javascript, + Severity: issue.getSeverity(), + Details: issue.Overview, + Code: issue.ModuleName, + } + vuln.Line = f.getVulnerabilityLineByName(f.getVersionText(issue.getVersion()), vuln.Code, vuln.File) + return f.SetCommitAuthor(vulnhash.Bind(vuln)) } func (f *Formatter) getVersionText(version string) string { return fmt.Sprintf(`"version": %q`, version) } -func (f *Formatter) getVulnerabilityLineByName(version, module, file string) string { - fileExisting, err := os.Open(filepath.Join(f.GetConfigProjectPath(), file)) +func (f *Formatter) getVulnerabilityLineByName(version, module, filename string) string { + file, err := os.Open(filepath.Join(f.GetConfigProjectPath(), filename)) if err != nil { return "" } defer func() { - logger.LogErrorWithLevel(messages.MsgErrorDeferFileClose, fileExisting.Close()) + logger.LogErrorWithLevel(messages.MsgErrorDeferFileClose, file.Close()) }() - return f.getLine(version, module, bufio.NewScanner(fileExisting)) + return f.getLine(version, module, bufio.NewScanner(file)) } func (f *Formatter) getLine(version, module string, scanner *bufio.Scanner) string { diff --git a/internal/services/formatters/javascript/npmaudit/formatter_test.go b/internal/services/formatters/javascript/npmaudit/formatter_test.go index b504abe58..b4d55c64f 100644 --- a/internal/services/formatters/javascript/npmaudit/formatter_test.go +++ b/internal/services/formatters/javascript/npmaudit/formatter_test.go @@ -18,116 +18,105 @@ import ( "errors" "testing" - entitiesAnalysis "github.com/ZupIT/horusec-devkit/pkg/entities/analysis" + "github.com/ZupIT/horusec-devkit/pkg/entities/analysis" + "github.com/ZupIT/horusec-devkit/pkg/enums/languages" "github.com/ZupIT/horusec-devkit/pkg/enums/tools" "github.com/stretchr/testify/assert" - cliConfig "github.com/ZupIT/horusec/config" + "github.com/ZupIT/horusec/config" "github.com/ZupIT/horusec/internal/entities/toolsconfig" - "github.com/ZupIT/horusec/internal/entities/workdir" "github.com/ZupIT/horusec/internal/services/formatters" "github.com/ZupIT/horusec/internal/utils/testutil" ) -func TestStartNpmAudit(t *testing.T) { - t.Run("Should parse output with no errors", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} - dockerAPIControllerMock := testutil.NewDockerMock() - dockerAPIControllerMock.On("SetAnalysisID") - - config := &cliConfig.Config{} - config.WorkDir = &workdir.WorkDir{} - - output := "{\"advisories\":{\"1469\":{\"findings\":[{\"version\":\"0.6.6\",\"paths\":[\"express>qs\"]}],\"id\":1469,\"created\":\"2020-02-10T19:09:50.604Z\",\"updated\":\"2020-02-14T22:24:16.925Z\",\"deleted\":null,\"title\":\"Prototype Pollution Protection Bypass\",\"found_by\":{\"link\":\"\",\"name\":\"Unknown\",\"email\":\"\"},\"reported_by\":{\"link\":\"\",\"name\":\"Unknown\",\"email\":\"\"},\"module_name\":\"qs\",\"cves\":[\"CVE-2017-1000048\"],\"vulnerable_versions\":\"<6.0.4 || >=6.1.0 <6.1.2 || >=6.2.0 <6.2.3 || >=6.3.0 <6.3.2\",\"patched_versions\":\">=6.0.4 <6.1.0 || >=6.1.2 <6.2.0 || >=6.2.3 <6.3.0 || >=6.3.2\",\"overview\":\"Affected version of `qs` are vulnerable to Prototype Pollution because it is possible to bypass the protection. The `qs.parse` function fails to properly prevent an object's prototype to be altered when parsing arbitrary input. Input containing `[` or `]` may bypass the prototype pollution protection and alter the Object prototype. This allows attackers to override properties that will exist in all objects, which may lead to Denial of Service or Remote Code Execution in specific circumstances.\",\"recommendation\":\"Upgrade to 6.0.4, 6.1.2, 6.2.3, 6.3.2 or later.\",\"references\":\"- [GitHub Issue](https://github.com/ljharb/qs/issues/200)\\n- [Snyk Report](https://snyk.io/vuln/npm:qs:20170213)\",\"access\":\"public\",\"severity\":\"high\",\"cwe\":\"CWE-471\",\"metadata\":{\"module_type\":\"\",\"exploitability\":4,\"affected_components\":\"\"},\"url\":\"https://npmjs.com/advisories/1469\"}},\"metadata\":{\"vulnerabilities\":{\"info\":0,\"low\":8,\"moderate\":6,\"high\":7,\"critical\":0},\"dependencies\":23,\"devDependencies\":0,\"optionalDependencies\":0,\"totalDependencies\":23},\"runId\":\"7c3c5266-3f9d-4924-a8b7-93fad66e64e0\"}" - - dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) - - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) - - formatter := NewFormatter(service) +func TestNpmAuditParseOutput(t *testing.T) { + t.Run("should add 1 vulnerabilities on analysis with no errors", func(t *testing.T) { + analysis := new(analysis.Analysis) - formatter.StartAnalysis("") - assert.Equal(t, 1, len(analysis.AnalysisVulnerabilities)) - }) - t.Run("Should parse output with no errors", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} dockerAPIControllerMock := testutil.NewDockerMock() dockerAPIControllerMock.On("SetAnalysisID") - - config := &cliConfig.Config{} - config.WorkDir = &workdir.WorkDir{} - - output := "{\"advisories\":{\"1469\":{\"findings\":[{\"version\":\"0.6.6\",\"paths\":[\"express>qs\"]}],\"id\":1469,\"created\":\"2020-02-10T19:09:50.604Z\",\"updated\":\"2020-02-14T22:24:16.925Z\",\"deleted\":null,\"title\":\"Prototype Pollution Protection Bypass\",\"found_by\":{\"link\":\"\",\"name\":\"Unknown\",\"email\":\"\"},\"reported_by\":{\"link\":\"\",\"name\":\"Unknown\",\"email\":\"\"},\"module_name\":\"qs\",\"cves\":[\"CVE-2017-1000048\"],\"vulnerable_versions\":\"<6.0.4 || >=6.1.0 <6.1.2 || >=6.2.0 <6.2.3 || >=6.3.0 <6.3.2\",\"patched_versions\":\">=6.0.4 <6.1.0 || >=6.1.2 <6.2.0 || >=6.2.3 <6.3.0 || >=6.3.2\",\"overview\":\"Affected version of `qs` are vulnerable to Prototype Pollution because it is possible to bypass the protection. The `qs.parse` function fails to properly prevent an object's prototype to be altered when parsing arbitrary input. Input containing `[` or `]` may bypass the prototype pollution protection and alter the Object prototype. This allows attackers to override properties that will exist in all objects, which may lead to Denial of Service or Remote Code Execution in specific circumstances.\",\"recommendation\":\"Upgrade to 6.0.4, 6.1.2, 6.2.3, 6.3.2 or later.\",\"references\":\"- [GitHub Issue](https://github.com/ljharb/qs/issues/200)\\n- [Snyk Report](https://snyk.io/vuln/npm:qs:20170213)\",\"access\":\"public\",\"severity\":\"high\",\"cwe\":\"CWE-471\",\"metadata\":{\"module_type\":\"\",\"exploitability\":4,\"affected_components\":\"\"},\"url\":\"https://npmjs.com/advisories/1469\"}},\"metadata\":{\"vulnerabilities\":{\"info\":0,\"low\":8,\"moderate\":6,\"high\":7,\"critical\":0},\"dependencies\":23,\"devDependencies\":0,\"optionalDependencies\":0,\"totalDependencies\":23},\"runId\":\"7c3c5266-3f9d-4924-a8b7-93fad66e64e0\"}" - dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) - + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, newTestConfig(t, analysis)) formatter := NewFormatter(service) - formatter.StartAnalysis("") + assert.Equal(t, 1, len(analysis.AnalysisVulnerabilities)) + for _, v := range analysis.AnalysisVulnerabilities { + vuln := v.Vulnerability + + assert.Equal(t, tools.NpmAudit, vuln.SecurityTool) + assert.Equal(t, languages.Javascript, vuln.Language) + assert.NotEmpty(t, vuln.Details, "Expected not empty details") + assert.NotEmpty(t, vuln.Code, "Expected not empty code") + assert.NotEmpty(t, vuln.File, "Expected not empty file name") + assert.NotEmpty(t, vuln.Line, "Expected not empty line") + assert.NotEmpty(t, vuln.Severity, "Expected not empty severity") + } }) t.Run("Should parse output empty with no errors", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} + analysis := new(analysis.Analysis) + dockerAPIControllerMock := testutil.NewDockerMock() dockerAPIControllerMock.On("SetAnalysisID") + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("", nil) - config := &cliConfig.Config{} - config.WorkDir = &workdir.WorkDir{} - - output := "" - - dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) - - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) - + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, newTestConfig(t, analysis)) formatter := NewFormatter(service) - formatter.StartAnalysis("") + assert.Equal(t, 0, len(analysis.AnalysisVulnerabilities)) + assert.False(t, analysis.HasErrors(), "Expected no errors on analysis") }) - t.Run("Should parse output with not found error", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} + t.Run("Should add error on analysos when parse output with not found error", func(t *testing.T) { + analysis := new(analysis.Analysis) + dockerAPIControllerMock := testutil.NewDockerMock() dockerAPIControllerMock.On("SetAnalysisID") + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("ERROR_PACKAGE_LOCK_NOT_FOUND", nil) - config := &cliConfig.Config{} - config.WorkDir = &workdir.WorkDir{} + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, newTestConfig(t, analysis)) + formatter := NewFormatter(service) + formatter.StartAnalysis("") - output := "ERROR_PACKAGE_LOCK_NOT_FOUND" + assert.Equal(t, 0, len(analysis.AnalysisVulnerabilities)) + assert.True(t, analysis.HasErrors(), "Expected errors on analysis") + }) - dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return(output, nil) + t.Run("Should add error on analysis when parse invalid output", func(t *testing.T) { + analysis := new(analysis.Analysis) - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) + dockerAPIControllerMock := testutil.NewDockerMock() + dockerAPIControllerMock.On("SetAnalysisID") + dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("invalid", nil) + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, newTestConfig(t, analysis)) formatter := NewFormatter(service) - formatter.StartAnalysis("") - assert.Equal(t, 0, len(analysis.AnalysisVulnerabilities)) + + assert.True(t, analysis.HasErrors(), "Expected no errors on analysis") }) - t.Run("Should return error when executing container", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} + t.Run("should add error of executing container on analysis", func(t *testing.T) { + analysis := new(analysis.Analysis) + dockerAPIControllerMock := testutil.NewDockerMock() dockerAPIControllerMock.On("SetAnalysisID") dockerAPIControllerMock.On("CreateLanguageAnalysisContainer").Return("", errors.New("test")) - config := &cliConfig.Config{} - config.WorkDir = &workdir.WorkDir{} - - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) - + service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, newTestConfig(t, analysis)) formatter := NewFormatter(service) - formatter.StartAnalysis("") - assert.Equal(t, 0, len(analysis.AnalysisVulnerabilities)) + + assert.True(t, analysis.HasErrors(), "Expected errors on analysis") }) t.Run("Should not execute tool because it's ignored", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} + analysis := new(analysis.Analysis) + dockerAPIControllerMock := testutil.NewDockerMock() - config := &cliConfig.Config{} + + config := config.New() config.ToolsConfig = toolsconfig.ToolsConfig{ tools.NpmAudit: toolsconfig.Config{ IsToIgnore: true, @@ -135,43 +124,76 @@ func TestStartNpmAudit(t *testing.T) { } service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) formatter := NewFormatter(service) - formatter.StartAnalysis("") }) } -func TestParseOutputNpm(t *testing.T) { - t.Run("Should return error when invalid output", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} - dockerAPIControllerMock := testutil.NewDockerMock() - dockerAPIControllerMock.On("SetAnalysisID") - - config := &cliConfig.Config{} - config.WorkDir = &workdir.WorkDir{} - - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) - - formatter := Formatter{ - service, - } - - err := formatter.parseOutput("invalid output", "") - assert.Error(t, err) - assert.Equal(t, 0, len(analysis.AnalysisVulnerabilities)) - }) - t.Run("Check if get version contains double quote using %q", func(t *testing.T) { - analysis := &entitiesAnalysis.Analysis{} - dockerAPIControllerMock := testutil.NewDockerMock() - config := &cliConfig.Config{} - config.WorkDir = &workdir.WorkDir{} - service := formatters.NewFormatterService(analysis, dockerAPIControllerMock, config) - - formatter := Formatter{ - service, - } - - extractedVersion := formatter.getVersionText("v1.0.0") +func newTestConfig(t *testing.T, analysiss *analysis.Analysis) *config.Config { + cfg := config.New() + cfg.ProjectPath = testutil.CreateHorusecAnalysisDirectory(t, analysiss, testutil.JavaScriptExample1) + return cfg +} - assert.Equal(t, `"version": "v1.0.0"`, extractedVersion) - }) +const output = ` +{ + "advisories": { + "1469": { + "findings": [ + { + "version": "0.6.6", + "paths": [ + "express>qs" + ] + } + ], + "id": 1469, + "created": "2020-02-10T19:09:50.604Z", + "updated": "2020-02-14T22:24:16.925Z", + "deleted": null, + "title": "Prototype Pollution Protection Bypass", + "found_by": { + "link": "", + "name": "Unknown", + "email": "" + }, + "reported_by": { + "link": "", + "name": "Unknown", + "email": "" + }, + "module_name": "qs", + "cves": [ + "CVE-2017-1000048" + ], + "vulnerable_versions": "<6.0.4 || >=6.1.0 <6.1.2 || >=6.2.0 <6.2.3 || >=6.3.0 <6.3.2", + "patched_versions": ">=6.0.4 <6.1.0 || >=6.1.2 <6.2.0 || >=6.2.3 <6.3.0 || >=6.3.2", + "overview": "Affected version of qs are vulnerable to Prototype Pollution because it is possible to bypass the protection. The qs.parse function fails to properly prevent an object's prototype to be altered when parsing arbitrary input. Input containing [ or ] may bypass the prototype pollution protection and alter the Object prototype. This allows attackers to override properties that will exist in all objects, which may lead to Denial of Service or Remote Code Execution in specific circumstances.", + "recommendation": "Upgrade to 6.0.4, 6.1.2, 6.2.3, 6.3.2 or later.", + "references": "- [GitHub Issue](https://github.com/ljharb/qs/issues/200)\n- [Snyk Report](https://snyk.io/vuln/npm:qs:20170213)", + "access": "public", + "severity": "high", + "cwe": "CWE-471", + "metadata": { + "module_type": "", + "exploitability": 4, + "affected_components": "" + }, + "url": "https://npmjs.com/advisories/1469" + } + }, + "metadata": { + "vulnerabilities": { + "info": 0, + "low": 8, + "moderate": 6, + "high": 7, + "critical": 0 + }, + "dependencies": 23, + "devDependencies": 0, + "optionalDependencies": 0, + "totalDependencies": 23 + }, + "runId": "7c3c5266-3f9d-4924-a8b7-93fad66e64e0" } +` diff --git a/internal/services/formatters/javascript/npmaudit/entities/issue.go b/internal/services/formatters/javascript/npmaudit/output.go similarity index 51% rename from internal/services/formatters/javascript/npmaudit/entities/issue.go rename to internal/services/formatters/javascript/npmaudit/output.go index 94be74dd9..4aec0b788 100644 --- a/internal/services/formatters/javascript/npmaudit/entities/issue.go +++ b/internal/services/formatters/javascript/npmaudit/output.go @@ -12,24 +12,45 @@ // See the License for the specific language governing permissions and // limitations under the License. -package entities +package npmaudit import "github.com/ZupIT/horusec-devkit/pkg/enums/severities" -type Issue struct { - Findings []Finding `json:"findings"` - ID int `json:"id"` - ModuleName string `json:"module_name"` - VulnerableVersions string `json:"vulnerable_versions"` - Severity string `json:"severity"` - Overview string `json:"overview"` +type npmOutput struct { + Advisories map[string]npmIssue `json:"advisories"` + Metadata npmMetadata `json:"metadata"` } -func (i *Issue) GetSeverity() severities.Severity { +type npmMetadata struct { + Vulnerabilities npmVulnerabilities `json:"vulnerabilities"` +} + +type npmVulnerabilities struct { + Info int `json:"info"` + Low int `json:"low"` + Moderate int `json:"moderate"` + High int `json:"high"` + Critical int `json:"critical"` +} + +type npmFinding struct { + Version string `json:"version"` +} + +type npmIssue struct { + Findings []npmFinding `json:"findings"` + ID int `json:"id"` + ModuleName string `json:"module_name"` + VulnerableVersions string `json:"vulnerable_versions"` + Severity string `json:"severity"` + Overview string `json:"overview"` +} + +func (i *npmIssue) getSeverity() severities.Severity { return i.mapSeverities()[i.Severity] } -func (i *Issue) mapSeverities() map[string]severities.Severity { +func (i *npmIssue) mapSeverities() map[string]severities.Severity { return map[string]severities.Severity{ "critical": severities.Critical, "high": severities.High, @@ -40,7 +61,7 @@ func (i *Issue) mapSeverities() map[string]severities.Severity { } } -func (i *Issue) GetVersion() string { +func (i *npmIssue) getVersion() string { if len(i.Findings) > 0 { return i.Findings[0].Version } diff --git a/internal/services/formatters/javascript/npmaudit/entities/issue_test.go b/internal/services/formatters/javascript/npmaudit/output_test.go similarity index 72% rename from internal/services/formatters/javascript/npmaudit/entities/issue_test.go rename to internal/services/formatters/javascript/npmaudit/output_test.go index 2a38df6bb..8b9f4d0eb 100644 --- a/internal/services/formatters/javascript/npmaudit/entities/issue_test.go +++ b/internal/services/formatters/javascript/npmaudit/output_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package entities +package npmaudit import ( "testing" @@ -23,61 +23,61 @@ import ( func TestGetVersion(t *testing.T) { t.Run("should return finding version", func(t *testing.T) { - issue := Issue{ - Findings: []Finding{ + issue := npmIssue{ + Findings: []npmFinding{ { Version: "test", }, }, } - assert.Equal(t, "test", issue.GetVersion()) + assert.Equal(t, "test", issue.getVersion()) }) t.Run("should return no version", func(t *testing.T) { - issue := Issue{} - assert.Empty(t, issue.GetVersion()) + issue := npmIssue{} + assert.Empty(t, issue.getVersion()) }) } func TestGetSeverity(t *testing.T) { t.Run("should return a low severity", func(t *testing.T) { - issue := Issue{ + issue := npmIssue{ Severity: "low", } - assert.Equal(t, severities.Low, issue.GetSeverity()) + assert.Equal(t, severities.Low, issue.getSeverity()) }) t.Run("should return a medium severity", func(t *testing.T) { - issue := Issue{ + issue := npmIssue{ Severity: "moderate", } - assert.Equal(t, severities.Medium, issue.GetSeverity()) + assert.Equal(t, severities.Medium, issue.getSeverity()) }) t.Run("should return a critical severity", func(t *testing.T) { - issue := Issue{ + issue := npmIssue{ Severity: "critical", } - assert.Equal(t, severities.Critical, issue.GetSeverity()) + assert.Equal(t, severities.Critical, issue.getSeverity()) }) t.Run("should return a info severity", func(t *testing.T) { - issue := Issue{ + issue := npmIssue{ Severity: "info", } - assert.Equal(t, severities.Info, issue.GetSeverity()) + assert.Equal(t, severities.Info, issue.getSeverity()) }) t.Run("should return a unknown severity", func(t *testing.T) { - issue := Issue{ + issue := npmIssue{ Severity: "", } - assert.Equal(t, severities.Unknown, issue.GetSeverity()) + assert.Equal(t, severities.Unknown, issue.getSeverity()) }) }