diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 85db85e0..4dc0f409 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,7 +65,7 @@ jobs: # Test and generate code coverage - name: Run tests - run: go test ${{ env.GO_COMMON_TEST_ARGS }} -cover -coverprofile=cover-unit-tests --test.unit + run: go test ${{ env.GO_COMMON_TEST_ARGS }} -covermode atomic -coverprofile=cover-unit-tests --test.unit - name: Archive Code Coverage Results uses: actions/upload-artifact@v4 @@ -246,7 +246,7 @@ jobs: pull-requests: write # write permission needed to comment on PR steps: - name: Generate Unit Tests Code Coverage Report - uses: fgrosse/go-coverage-report@v1.2.0 + uses: fgrosse/go-coverage-report@v1.1.0 with: coverage-artifact-name: unit-tests-code-coverage coverage-file-name: cover-unit-tests \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c74c7d0f..32b87497 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,12 +56,13 @@ go test -v github.com/jfrog/jfrog-cli-security [test-types] [flags] ### The available flags are: -| Flag | Equivalent Env vars | Description | -| ---------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `-jfrog.url` | `JFROG_SECURITY_CLI_TESTS_JFROG_URL` | [Default: http://localhost:8083] JFrog platform URL | -| `-jfrog.user` | `JFROG_SECURITY_CLI_TESTS_JFROG_USER` | [Default: admin] JFrog platform username | -| `-jfrog.password` | `JFROG_SECURITY_CLI_TESTS_JFROG_PASSWORD` | [Default: password] JFrog platform password | -| `-jfrog.adminToken` | `JFROG_SECURITY_CLI_TESTS_JFROG_ACCESS_TOKEN` | [Optional] JFrog platform admin token | +| Flag | Equivalent Env vars | Description | +| ---------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `-jfrog.url` | `JFROG_SECURITY_CLI_TESTS_JFROG_URL` | [Default: http://localhost:8083] JFrog platform URL | +| `-jfrog.user` | `JFROG_SECURITY_CLI_TESTS_JFROG_USER` | [Default: admin] JFrog platform username | +| `-jfrog.password` | `JFROG_SECURITY_CLI_TESTS_JFROG_PASSWORD` | [Default: password] JFrog platform password | +| `-jfrog.adminToken` | `JFROG_SECURITY_CLI_TESTS_JFROG_ACCESS_TOKEN` | [Optional] JFrog platform admin token | +| `-jfrog.projectKey` | `JFROG_SECURITY_CLI_TESTS_JFROG_PLATFORM_PROJECT_KEY` | [Optional] JFrog platform project key | | `-ci.runId` | - | [Optional] A unique identifier used as a suffix to create repositories and builds in the tests. | | `-jfrog.sshKeyPath` | - | [Optional] Path to the SSH key file. Use this flag only if the Artifactory URL format is `ssh://[domain]:port`. | | `-jfrog.sshPassphrase` | - | [Optional] Passphrase for the SSH key. | diff --git a/audit_test.go b/audit_test.go index a927acf4..8393b677 100644 --- a/audit_test.go +++ b/audit_test.go @@ -41,8 +41,8 @@ func TestXrayAuditNpmJson(t *testing.T) { integration.InitAuditJavaScriptTest(t, scangraph.GraphScanMinXrayVersion) output := testAuditNpm(t, string(format.Json), false) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - SecurityViolations: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Violations: 1}, + Violations: &validations.ViolationCount{ValidateType: &validations.ScaViolationCount{Security: 1}}, }) } @@ -50,9 +50,8 @@ func TestXrayAuditNpmSimpleJson(t *testing.T) { integration.InitAuditJavaScriptTest(t, scangraph.GraphScanMinXrayVersion) output := testAuditNpm(t, string(format.SimpleJson), true) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - SecurityViolations: 1, - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1, Violations: 1}, + Violations: &validations.ViolationCount{ValidateType: &validations.ScaViolationCount{Security: 1}}, }) } @@ -63,7 +62,7 @@ func testAuditNpm(t *testing.T, format string, withVuln bool) string { assert.NoError(t, exec.Command("npm", "install").Run()) // Add dummy descriptor file to check that we run only specific audit addDummyPackageDescriptor(t, true) - watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "audit-policy", "audit-watch", xrayUtils.High) + watchName, deleteWatch := securityTestUtils.CreateTestPolicyAndWatch(t, "audit-policy", "audit-watch", xrayUtils.High) defer deleteWatch() args := []string{"audit", "--npm", "--licenses", "--format=" + format, "--watches=" + watchName, "--fail=false"} if withVuln { @@ -76,8 +75,7 @@ func TestXrayAuditConanJson(t *testing.T) { integration.InitAuditCTest(t, scangraph.GraphScanMinXrayVersion) output := testAuditConan(t, string(format.Json), true) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 8, - Licenses: 2, + Total: &validations.TotalCount{Licenses: 2, Vulnerabilities: 8}, }) } @@ -85,8 +83,7 @@ func TestXrayAuditConanSimpleJson(t *testing.T) { integration.InitAuditCTest(t, scangraph.GraphScanMinXrayVersion) output := testAuditConan(t, string(format.SimpleJson), true) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 8, - Licenses: 2, + Total: &validations.TotalCount{Licenses: 2, Vulnerabilities: 8}, }) } @@ -95,7 +92,7 @@ func testAuditConan(t *testing.T, format string, withVuln bool) string { defer cleanUp() // Run conan install before executing jfrog audit assert.NoError(t, exec.Command("conan").Run()) - watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "audit-curation-policy", "audit-curation-watch", xrayUtils.High) + watchName, deleteWatch := securityTestUtils.CreateTestPolicyAndWatch(t, "audit-curation-policy", "audit-curation-watch", xrayUtils.High) defer deleteWatch() args := []string{"audit", "--licenses", "--format=" + format, "--watches=" + watchName, "--fail=false"} if withVuln { @@ -108,8 +105,7 @@ func TestXrayAuditPnpmJson(t *testing.T) { integration.InitAuditJavaScriptTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPnpm(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } @@ -117,8 +113,7 @@ func TestXrayAuditPnpmSimpleJson(t *testing.T) { integration.InitAuditJavaScriptTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPnpm(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } @@ -137,8 +132,7 @@ func TestXrayAuditYarnV2Json(t *testing.T) { testXrayAuditYarn(t, "yarn-v2", func() { output := runXrayAuditYarnWithOutput(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) }) } @@ -148,8 +142,7 @@ func TestXrayAuditYarnV2SimpleJson(t *testing.T) { testXrayAuditYarn(t, "yarn-v3", func() { output := runXrayAuditYarnWithOutput(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) }) } @@ -159,8 +152,7 @@ func TestXrayAuditYarnV1Json(t *testing.T) { testXrayAuditYarn(t, "yarn-v1", func() { output := runXrayAuditYarnWithOutput(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) }) } @@ -183,8 +175,7 @@ func TestXrayAuditYarnV1SimpleJson(t *testing.T) { testXrayAuditYarn(t, "yarn-v1", func() { output := runXrayAuditYarnWithOutput(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) }) } @@ -255,8 +246,7 @@ func TestXrayAuditNugetJson(t *testing.T) { func(t *testing.T) { output := testXrayAuditNuget(t, test.projectName, test.format, test.restoreTech) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: test.minVulnerabilities, - Licenses: test.minLicences, + Total: &validations.TotalCount{Licenses: test.minLicences, Vulnerabilities: test.minVulnerabilities}, }) }) } @@ -299,8 +289,7 @@ func TestXrayAuditNugetSimpleJson(t *testing.T) { func(t *testing.T) { output := testXrayAuditNuget(t, test.projectName, test.format, test.restoreTech) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: test.minVulnerabilities, - Licenses: test.minLicences, + Total: &validations.TotalCount{Licenses: test.minLicences, Vulnerabilities: test.minVulnerabilities}, }) }) } @@ -323,8 +312,7 @@ func TestXrayAuditGradleJson(t *testing.T) { integration.InitAuditJavaTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditGradle(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 3, + Total: &validations.TotalCount{Licenses: 3, Vulnerabilities: 3}, }) } @@ -332,8 +320,7 @@ func TestXrayAuditGradleSimpleJson(t *testing.T) { integration.InitAuditJavaTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditGradle(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 3, + Total: &validations.TotalCount{Licenses: 3, Vulnerabilities: 3}, }) } @@ -349,8 +336,7 @@ func TestXrayAuditMavenJson(t *testing.T) { integration.InitAuditJavaTest(t, scangraph.GraphScanMinXrayVersion) output := testAuditMaven(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } @@ -358,8 +344,7 @@ func TestXrayAuditMavenSimpleJson(t *testing.T) { integration.InitAuditJavaTest(t, scangraph.GraphScanMinXrayVersion) output := testAuditMaven(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } @@ -374,13 +359,18 @@ func testAuditMaven(t *testing.T, format string) string { func TestXrayAuditGoJson(t *testing.T) { integration.InitAuditGoTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditGo(t, false, string(format.Json), "simple-project") - validations.VerifyJsonResults(t, output, validations.ValidationParams{Licenses: 1, Vulnerabilities: 4}) + validations.VerifyJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 4}}) } func TestXrayAuditGoSimpleJson(t *testing.T) { integration.InitAuditGoTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditGo(t, true, string(format.SimpleJson), "simple-project") - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Licenses: 3, Vulnerabilities: 4, NotCovered: 1, NotApplicable: 3}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ + Total: &validations.TotalCount{Licenses: 3, Vulnerabilities: 4}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{NotCovered: 1, NotApplicable: 3}, + }, + }) } func testXrayAuditGo(t *testing.T, noCreds bool, format, project string) string { @@ -425,15 +415,11 @@ func TestXrayAuditMultiProjects(t *testing.T) { output := securityTests.PlatformCli.WithoutCredentials().RunCliCmdWithOutput(t, "audit", "--format="+string(format.SimpleJson), workingDirsFlag) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Sast: 1, - Iac: 9, - Secrets: 6, - - Vulnerabilities: 35, - Applicable: 3, - Undetermined: 0, - NotCovered: 22, - NotApplicable: 2, + Total: &validations.TotalCount{Vulnerabilities: 43}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 27, Sast: 1, Iac: 9, Secrets: 6}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 3, NotCovered: 22, NotApplicable: 2}, + }, }) } @@ -441,23 +427,20 @@ func TestXrayAuditPipJson(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPip(t, string(format.Json), "") validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 3}, }) } func TestXrayAuditCocoapods(t *testing.T) { integration.InitAuditCocoapodsTest(t, scangraph.CocoapodsScanMinXrayVersion) output := testXrayAuditCocoapods(t, string(format.Json)) - validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - }) + validations.VerifyJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Vulnerabilities: 1}}) } func TestXrayAuditSwift(t *testing.T) { output := testXrayAuditSwift(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, + Total: &validations.TotalCount{Vulnerabilities: 1}, }) } @@ -465,21 +448,20 @@ func TestXrayAuditPipSimpleJson(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPip(t, string(format.SimpleJson), "") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 3}, }) } func TestXrayAuditPipJsonWithRequirementsFile(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPip(t, string(format.Json), "requirements.txt") - validations.VerifyJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 2}) + validations.VerifyJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Vulnerabilities: 2}}) } func TestXrayAuditPipSimpleJsonWithRequirementsFile(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPip(t, string(format.SimpleJson), "requirements.txt") - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 2}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Vulnerabilities: 2}}) } func testXrayAuditPip(t *testing.T, format, requirementsFile string) string { @@ -515,8 +497,7 @@ func TestXrayAuditPipenvJson(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPipenv(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 3}, }) } @@ -524,8 +505,7 @@ func TestXrayAuditPipenvSimpleJson(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPipenv(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 3}, }) } @@ -541,8 +521,7 @@ func TestXrayAuditPoetryJson(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPoetry(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 3}, }) } @@ -550,8 +529,7 @@ func TestXrayAuditPoetrySimpleJson(t *testing.T) { integration.InitAuditPythonTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditPoetry(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 3, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 3}, }) } @@ -586,19 +564,19 @@ func TestXrayAuditSastCppFlagSimpleJson(t *testing.T) { name: "withFlag", withFlag: true, expectedResults: validations.ValidationParams{ - Vulnerabilities: 1, - Sast: 1, + Total: &validations.TotalCount{Vulnerabilities: 2}, + Vulnerabilities: &validations.VulnerabilityCount{ValidateScan: &validations.ScanCount{Sast: 2}}, }, }, { name: "withoutFlag", withFlag: false, - expectedResults: validations.ValidationParams{}, + expectedResults: validations.ValidationParams{ExactResultsMatch: true, Total: &validations.TotalCount{}}, }, } for _, tc := range testCase { t.Run(tc.name, func(t *testing.T) { - output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("package-managers", "c"), "3", false, tc.withFlag, false, "") + output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("package-managers", "c"), "3", false, tc.withFlag, false, "*out*") validations.VerifySimpleJsonResults(t, output, tc.expectedResults) }) } @@ -609,15 +587,17 @@ func TestXrayAuditSastCSharpFlagSimpleJson(t *testing.T) { integration.InitAuditJasTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("package-managers", "dotnet", "dotnet-single"), "3", false, false, true, "") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Sast: 1, + Total: &validations.TotalCount{Vulnerabilities: 1}, + Vulnerabilities: &validations.VulnerabilityCount{ValidateScan: &validations.ScanCount{Sast: 1}}, }) } func TestXrayAuditJasMissingContextSimpleJson(t *testing.T) { integration.InitAuditJasTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("package-managers", "maven", "missing-context"), "3", false, false, false, "") - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{MissingContext: 1}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ + Vulnerabilities: &validations.VulnerabilityCount{ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{MissingContext: 1}}, + }) } func TestXrayAuditNotEntitledForJas(t *testing.T) { @@ -625,7 +605,7 @@ func TestXrayAuditNotEntitledForJas(t *testing.T) { cliToRun, cleanUp := integration.InitTestWithMockCommandOrParams(t, false, getNoJasAuditMockCommand) defer cleanUp() output := testXrayAuditJas(t, cliToRun, filepath.Join("jas", "jas"), "3", false, false, false, "") - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 8}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Vulnerabilities: 8}}) } func getNoJasAuditMockCommand() components.Command { @@ -648,37 +628,34 @@ func TestXrayAuditJasSimpleJson(t *testing.T) { integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("jas", "jas"), "3", false, false, false, "") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Sast: 1, - Iac: 9, - Secrets: 6, - - Vulnerabilities: 8, - Applicable: 3, - Undetermined: 1, - NotCovered: 1, - NotApplicable: 2, + Total: &validations.TotalCount{Vulnerabilities: 23}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 7, Sast: 1, Iac: 9, Secrets: 6}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 3, Undetermined: 1, NotCovered: 1, NotApplicable: 2}, + }, }) } func TestXrayAuditJasSimpleJsonWithTokenValidation(t *testing.T) { integration.InitAuditGeneralTests(t, jasutils.DynamicTokenValidationMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("jas", "jas"), "3", true, false, false, "") - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 5, Inactive: 5}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Secrets: 5}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Inactive: 5}, + }, + }) } func TestXrayAuditJasSimpleJsonWithOneThread(t *testing.T) { integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("jas", "jas"), "1", false, false, false, "") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Sast: 1, - Iac: 9, - Secrets: 6, - - Vulnerabilities: 8, - Applicable: 3, - Undetermined: 1, - NotCovered: 1, - NotApplicable: 2, + Total: &validations.TotalCount{Vulnerabilities: 23}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 7, Sast: 1, Iac: 9, Secrets: 6}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 3, Undetermined: 1, NotCovered: 1, NotApplicable: 2}, + }, }) } @@ -686,20 +663,21 @@ func TestXrayAuditJasSimpleJsonWithConfig(t *testing.T) { integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("jas", "jas-config"), "3", false, false, false, "") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Secrets: 1, - - Vulnerabilities: 8, - Applicable: 3, - Undetermined: 1, - NotCovered: 1, - NotApplicable: 2, + Total: &validations.TotalCount{Vulnerabilities: 8}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 7, Secrets: 1}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 3, Undetermined: 1, NotCovered: 1, NotApplicable: 2}, + }, }) } func TestXrayAuditJasNoViolationsSimpleJson(t *testing.T) { integration.InitAuditGeneralTests(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("package-managers", "npm", "npm"), "3", false, false, false, "") - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 1, NotApplicable: 1}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ + Total: &validations.TotalCount{Vulnerabilities: 1}, + Vulnerabilities: &validations.VulnerabilityCount{ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{NotApplicable: 1}}, + }) } func testXrayAuditJas(t *testing.T, testCli *coreTests.JfrogCli, project string, threads string, validateSecrets bool, validateSastCpp bool, validateSastCSharp bool, customExclusion string) string { @@ -755,7 +733,7 @@ func TestXrayRecursiveScan(t *testing.T) { output := securityTests.PlatformCli.RunCliCmdWithOutput(t, "audit", "--format=json") // We anticipate the identification of five vulnerabilities: four originating from the .NET project and one from the NPM project. - validations.VerifyJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 4}) + validations.VerifyJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Vulnerabilities: 4}}) var results []services.ScanResponse err = json.Unmarshal([]byte(output), &results) @@ -785,7 +763,7 @@ func TestXrayAuditNotEntitledForJasWithXrayUrl(t *testing.T) { defer cleanUp() output := testXrayAuditJas(t, cliToRun, filepath.Join("jas", "jas"), "3", false, false, false, "") // Verify that scan results are printed - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Vulnerabilities: 8}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Vulnerabilities: 8}}) // Verify that JAS results are not printed validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{}) } @@ -795,15 +773,11 @@ func TestXrayAuditJasSimpleJsonWithXrayUrl(t *testing.T) { cliToRun := integration.GetTestCli(cli.GetJfrogCliSecurityApp(), true) output := testXrayAuditJas(t, cliToRun, filepath.Join("jas", "jas"), "3", false, false, false, "") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Sast: 1, - Iac: 9, - Secrets: 6, - - Vulnerabilities: 8, - Applicable: 3, - Undetermined: 1, - NotCovered: 1, - NotApplicable: 2, + Total: &validations.TotalCount{Vulnerabilities: 24}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 7, Sast: 1, Iac: 9, Secrets: 6}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 3, Undetermined: 1, NotCovered: 1, NotApplicable: 2}, + }, }) } @@ -813,14 +787,64 @@ func TestXrayAuditJasSimpleJsonWithCustomExclusions(t *testing.T) { integration.InitAuditJasTest(t, scangraph.GraphScanMinXrayVersion) output := testXrayAuditJas(t, securityTests.PlatformCli, filepath.Join("jas", "jas"), "3", false, false, false, "non_existing_folder") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Sast: 2, - Iac: 9, - Secrets: 6, - - Vulnerabilities: 8, - Applicable: 3, - Undetermined: 1, - NotCovered: 1, - NotApplicable: 2, + Total: &validations.TotalCount{Vulnerabilities: 24}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 7, Sast: 2, Iac: 9, Secrets: 6}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 3, Undetermined: 1, NotCovered: 1, NotApplicable: 2}, + }, }) } + +// test audit command parameters +type auditCommandTestParams struct { + // Will combined with "," if provided and be used as --working-dirs flag value + WorkingDirsToScan []string + // Will be combined with ";" if provided and be used as --exclusions flag value + CustomExclusion []string + // --format flag value if provided + Format string + // Will combined with "," if provided and be used as --watches flag value + Watches []string + // --project flag value if provided. + ProjectKey string + // --fail flag value if provided, must be provided with 'createWatchesFuncs' to create watches for the test + FailOnFailedBuildFlag bool + // -- vuln flag 'True' value must be provided with 'createWatchesFuncs' to create watches for the test + WithVuln bool + // --licenses flag value if provided + WithLicense bool +} + +// run audit command with different flags and params for integration tests +func testAuditCommand(t *testing.T, testCli *coreTests.JfrogCli, params auditCommandTestParams) string { + args := []string{"audit"} + if len(params.WorkingDirsToScan) > 0 { + args = append(args, "--working-dirs="+strings.Join(params.WorkingDirsToScan, ",")) + } + if len(params.CustomExclusion) > 0 { + args = append(args, "--exclusions="+strings.Join(params.CustomExclusion, ";")) + } + if params.Format != "" { + args = append(args, "--format="+params.Format) + } + if params.WithLicense { + args = append(args, "--licenses") + } + if params.ProjectKey != "" { + args = append(args, "--project="+params.ProjectKey) + } + if len(params.Watches) > 0 { + args = append(args, "--watches="+strings.Join(params.Watches, ",")) + } + if params.FailOnFailedBuildFlag { + if len(params.Watches) == 0 { + // Verify params consistency no fail flag + assert.False(t, params.FailOnFailedBuildFlag, "Fail flag provided without watches") + } + args = append(args, "--fail") + } + if params.WithVuln { + args = append(args, "--vuln") + } + return testCli.RunCliCmdWithOutput(t, args...) +} diff --git a/cli/scancommands.go b/cli/scancommands.go index 5b9c4ca1..8d608a5f 100644 --- a/cli/scancommands.go +++ b/cli/scancommands.go @@ -211,13 +211,15 @@ func ScanCmd(c *components.Context) error { return err } var specFile *spec.SpecFiles + repoPath := "" if c.IsFlagSet(flags.SpecFlag) && len(c.GetStringFlagValue(flags.SpecFlag)) > 0 { specFile, err = pluginsCommon.GetFileSystemSpec(c) if err != nil { return err } } else { - specFile = createDefaultScanSpec(c, addTrailingSlashToRepoPathIfNeeded(c)) + repoPath = addTrailingSlashToRepoPathIfNeeded(c) + specFile = createDefaultScanSpec(c, repoPath) } err = spec.ValidateSpec(specFile.Files, false, false) if err != nil { @@ -244,6 +246,7 @@ func ScanCmd(c *components.Context) error { SetSpec(specFile). SetOutputFormat(format). SetProject(getProject(c)). + SetBaseRepoPath(repoPath). SetIncludeVulnerabilities(c.GetBoolFlagValue(flags.Vuln) || shouldIncludeVulnerabilities(c)). SetIncludeLicenses(c.GetBoolFlagValue(flags.Licenses)). SetFail(c.GetBoolFlagValue(flags.Fail)). @@ -472,7 +475,7 @@ func CreateAuditCmd(c *components.Context) (string, string, *coreConfig.ServerDe auditCmd.SetTargetRepoPath(addTrailingSlashToRepoPathIfNeeded(c)). SetProject(getProject(c)). - SetIncludeVulnerabilities(c.GetBoolFlagValue(flags.Vuln) || shouldIncludeVulnerabilities(c)). + SetIncludeVulnerabilities(c.GetBoolFlagValue(flags.Vuln)). SetIncludeLicenses(c.GetBoolFlagValue(flags.Licenses)). SetFail(c.GetBoolFlagValue(flags.Fail)). SetPrintExtendedTable(c.GetBoolFlagValue(flags.ExtendedTable)). @@ -728,12 +731,12 @@ func DockerScan(c *components.Context, image string) error { return err } containerScanCommand.SetImageTag(image). - SetTargetRepoPath(addTrailingSlashToRepoPathIfNeeded(c)). SetServerDetails(serverDetails). SetXrayVersion(xrayVersion). SetXscVersion(xscVersion). SetOutputFormat(format). SetProject(getProject(c)). + SetBaseRepoPath(addTrailingSlashToRepoPathIfNeeded(c)). SetIncludeVulnerabilities(c.GetBoolFlagValue(flags.Vuln) || shouldIncludeVulnerabilities(c)). SetIncludeLicenses(c.GetBoolFlagValue(flags.Licenses)). SetFail(c.GetBoolFlagValue(flags.Fail)). diff --git a/commands/audit/audit.go b/commands/audit/audit.go index 5d347f5f..548e1e03 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -30,10 +30,12 @@ import ( "github.com/jfrog/jfrog-client-go/xray" "github.com/jfrog/jfrog-client-go/xray/services" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" + xscutils "github.com/jfrog/jfrog-client-go/xsc/services/utils" ) type AuditCommand struct { watches []string + gitRepoHttpsCloneUrl string projectKey string targetRepoPath string IncludeVulnerabilities bool @@ -53,6 +55,11 @@ func (auditCmd *AuditCommand) SetWatches(watches []string) *AuditCommand { return auditCmd } +func (auditCmd *AuditCommand) SetGitRepoHttpsCloneUrl(gitRepoHttpsCloneUrl string) *AuditCommand { + auditCmd.gitRepoHttpsCloneUrl = gitRepoHttpsCloneUrl + return auditCmd +} + func (auditCmd *AuditCommand) SetProject(project string) *AuditCommand { auditCmd.projectKey = project return auditCmd @@ -88,16 +95,49 @@ func (auditCmd *AuditCommand) SetThreads(threads int) *AuditCommand { return auditCmd } -func (auditCmd *AuditCommand) CreateCommonGraphScanParams() *scangraph.CommonGraphScanParams { - commonParams := &scangraph.CommonGraphScanParams{ - RepoPath: auditCmd.targetRepoPath, - Watches: auditCmd.watches, - ScanType: services.Dependency, +// Create a results context based on the provided parameters. resolves conflicts between the parameters based on the retrieved platform watches. +func CreateAuditResultsContext(serverDetails *config.ServerDetails, xrayVersion string, watches []string, artifactoryRepoPath, projectKey, gitRepoHttpsCloneUrl string, includeVulnerabilities, includeLicenses bool) (context results.ResultContext) { + context = results.ResultContext{ + RepoPath: artifactoryRepoPath, + Watches: watches, + ProjectKey: projectKey, + IncludeVulnerabilities: shouldIncludeVulnerabilities(includeVulnerabilities, watches, artifactoryRepoPath, projectKey, ""), + IncludeLicenses: includeLicenses, + } + if err := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, services.MinXrayVersionGitRepoKey); err != nil { + // Git repo key is not supported by the Xray version. + return + } + if gitRepoHttpsCloneUrl == "" { + // No git repo key was provided, no need to check anything else. + log.Debug("Git repo key was not provided, jas violations will not be checked for this resource.") + return + } + // Get the defined and active watches from the platform. + manager, err := xsc.CreateXscService(serverDetails) + if err != nil { + log.Warn(fmt.Sprintf("Failed to create Xray services manager: %s", err.Error())) + return + } + if context.PlatformWatches, err = manager.GetResourceWatches(xscutils.GetGitRepoUrlKey(gitRepoHttpsCloneUrl), projectKey); err != nil { + log.Warn(fmt.Sprintf("Failed to get active defined watches: %s", err.Error())) + return } - commonParams.ProjectKey = auditCmd.projectKey - commonParams.IncludeVulnerabilities = auditCmd.IncludeVulnerabilities - commonParams.IncludeLicenses = auditCmd.IncludeLicenses - return commonParams + // Set git repo key and check if it has any watches defined in the platform. + context.GitRepoHttpsCloneUrl = gitRepoHttpsCloneUrl + if len(context.PlatformWatches.GitRepositoryWatches) == 0 && len(watches) == 0 && projectKey == "" { + log.Debug(fmt.Sprintf("No watches were found in the platform for the given git repo key (%s), and no watches were given by the user (using watches or project flags). Calculating vulnerabilities...", context.GitRepoHttpsCloneUrl)) + context.GitRepoHttpsCloneUrl = "" + } + // We calculate again this time also taking into account the final git repo key value. + // (if there are no watches defined on the git repo and no other context was given, we should include vulnerabilities) + context.IncludeVulnerabilities = shouldIncludeVulnerabilities(includeVulnerabilities, watches, artifactoryRepoPath, projectKey, context.GitRepoHttpsCloneUrl) + return +} + +// If the user requested to include vulnerabilities, or if the user didn't provide any watches, project key, artifactory repo path or git repo key, we should include vulnerabilities. +func shouldIncludeVulnerabilities(includeVulnerabilities bool, watches []string, artifactoryRepoPath, projectKey, gitRepoHttpsCloneUrl string) bool { + return includeVulnerabilities || !(len(watches) > 0 || projectKey != "" || artifactoryRepoPath != "" || gitRepoHttpsCloneUrl != "") } func (auditCmd *AuditCommand) Run() (err error) { @@ -124,7 +164,16 @@ func (auditCmd *AuditCommand) Run() (err error) { SetMinSeverityFilter(auditCmd.minSeverityFilter). SetFixableOnly(auditCmd.fixableOnly). SetGraphBasicParams(auditCmd.AuditBasicParams). - SetCommonGraphScanParams(auditCmd.CreateCommonGraphScanParams()). + SetResultsContext(CreateAuditResultsContext( + serverDetails, + auditCmd.GetXrayVersion(), + auditCmd.watches, + auditCmd.targetRepoPath, + auditCmd.projectKey, + auditCmd.gitRepoHttpsCloneUrl, + auditCmd.IncludeVulnerabilities, + auditCmd.IncludeLicenses, + )). SetThirdPartyApplicabilityScan(auditCmd.thirdPartyApplicabilityScan). SetThreads(auditCmd.Threads). SetScansResultsOutputDir(auditCmd.scanResultsOutputDir).SetStartTime(startTime).SetMultiScanId(multiScanId) @@ -144,13 +193,10 @@ func (auditCmd *AuditCommand) Run() (err error) { messages = []string{coreutils.PrintTitle("The ‘jf audit’ command also supports JFrog Advanced Security features, such as 'Contextual Analysis', 'Secret Detection', 'IaC Scan' and ‘SAST’.\nThis feature isn't enabled on your system. Read more - ") + coreutils.PrintLink(utils.JasInfoURL)} } if err = output.NewResultsWriter(auditResults). - SetHasViolationContext(auditCmd.HasViolationContext()). - SetIncludeVulnerabilities(auditCmd.IncludeVulnerabilities). - SetIncludeLicenses(auditCmd.IncludeLicenses). SetOutputFormat(auditCmd.OutputFormat()). SetPrintExtendedTable(auditCmd.PrintExtendedTable). SetExtraMessages(messages). - SetSubScansPreformed(auditCmd.ScansToPerform()). + SetSubScansPerformed(auditCmd.ScansToPerform()). PrintScanResults(); err != nil { return errors.Join(err, auditResults.GetErrors()) } @@ -170,10 +216,6 @@ func (auditCmd *AuditCommand) CommandName() string { return "generic_audit" } -func (auditCmd *AuditCommand) HasViolationContext() bool { - return len(auditCmd.watches) > 0 || auditCmd.projectKey != "" || auditCmd.targetRepoPath != "" -} - // Runs an audit scan based on the provided auditParams. // Returns an audit Results object containing all the scan results. // If the current server is entitled for JAS, the advanced security results will be included in the scan results. @@ -192,14 +234,14 @@ func RunAudit(auditParams *AuditParams) (cmdResults *results.SecurityCommandResu var jasScanner *jas.JasScanner var generalJasScanErr error if jasScanner, generalJasScanErr = RunJasScans(auditParallelRunner, auditParams, cmdResults, jfrogAppsConfig); generalJasScanErr != nil { - cmdResults.AddGeneralError(fmt.Errorf("An error has occurred during JAS scan process. JAS scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalJasScanErr.Error()), auditParams.AllowPartialResults()) + cmdResults.AddGeneralError(fmt.Errorf("error has occurred during JAS scan process. JAS scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalJasScanErr.Error()), auditParams.AllowPartialResults()) } if auditParams.Progress() != nil { auditParams.Progress().SetHeadlineMsg("Scanning for issues") } // The sca scan doesn't require the analyzer manager, so it can run separately from the analyzer manager download routine. if generalScaScanError := buildDepTreeAndRunScaScan(auditParallelRunner, auditParams, cmdResults); generalScaScanError != nil { - cmdResults.AddGeneralError(fmt.Errorf("An error has occurred during SCA scan process. SCA scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalScaScanError.Error()), auditParams.AllowPartialResults()) + cmdResults.AddGeneralError(fmt.Errorf("error has occurred during SCA scan process. SCA scan is skipped for the following directories: %s\n%s", strings.Join(cmdResults.GetTargetsPaths(), ","), generalScaScanError.Error()), auditParams.AllowPartialResults()) } go func() { auditParallelRunner.ScaScansWg.Wait() @@ -234,7 +276,19 @@ func RunJasScans(auditParallelRunner *utils.SecurityParallelRunner, auditParams return } auditParallelRunner.ResultsMu.Lock() - jasScanner, err = jas.CreateJasScanner(serverDetails, scanResults.SecretValidation, auditParams.minSeverityFilter, jas.GetAnalyzerManagerXscEnvVars(auditParams.GetMultiScanId(), scanResults.GetTechnologies()...), auditParams.Exclusions()...) + jasScanner, err = jas.CreateJasScanner( + serverDetails, + scanResults.SecretValidation, + auditParams.minSeverityFilter, + jas.GetAnalyzerManagerXscEnvVars( + auditParams.GetMultiScanId(), + jas.GetGitRepoUrlKey(auditParams.resultsContext.GitRepoHttpsCloneUrl), + auditParams.resultsContext.ProjectKey, + auditParams.resultsContext.Watches, + scanResults.GetTechnologies()..., + ), + auditParams.Exclusions()..., + ) auditParallelRunner.ResultsMu.Unlock() if err != nil { generalError = fmt.Errorf("failed to create jas scanner: %s", err.Error()) @@ -276,7 +330,7 @@ func createJasScansTasks(auditParallelRunner *utils.SecurityParallelRunner, scan Scanner: scanner, Module: *module, ConfigProfile: auditParams.configProfile, - ScansToPreform: auditParams.ScansToPerform(), + ScansToPerform: auditParams.ScansToPerform(), SecretsScanType: secrets.SecretsScannerType, DirectDependencies: auditParams.DirectDependencies(), ThirdPartyApplicabilityScan: auditParams.thirdPartyApplicabilityScan, @@ -310,11 +364,13 @@ func initAuditCmdResults(params *AuditParams) (cmdResults *results.SecurityComma cmdResults.SetXscVersion(params.GetXscVersion()) cmdResults.SetMultiScanId(params.GetMultiScanId()) cmdResults.SetStartTime(params.StartTime()) - // Send entitlement requests + cmdResults.SetResultsContext(params.resultsContext) + xrayManager, err := xrayutils.CreateXrayServiceManager(serverDetails) if err != nil { return cmdResults.AddGeneralError(err, false) } + // Send entitlement requests entitledForJas, err := isEntitledForJas(xrayManager, params) if err != nil { return cmdResults.AddGeneralError(err, false) @@ -330,11 +386,11 @@ func initAuditCmdResults(params *AuditParams) (cmdResults *results.SecurityComma // No SCA targets were detected, add the root directory as a target for JAS scans. cmdResults.NewScanResults(results.ScanTarget{Target: params.workingDirs[0]}) } - scanInfo, err := coreutils.GetJsonIndent(cmdResults) + scanInfo, err := coreutils.GetJsonIndent(cmdResults.GetTargets()) if err != nil { return } - log.Info(fmt.Sprintf("Preforming scans on %d targets:\n%s", len(cmdResults.Targets), scanInfo)) + log.Info(fmt.Sprintf("Performing scans on %d targets:\n%s", len(cmdResults.Targets), scanInfo)) return } @@ -350,14 +406,14 @@ func detectScanTargets(cmdResults *results.SecurityCommandResults, params *Audit log.Warn("Couldn't detect technologies in", requestedDirectory, "directory.", err.Error()) continue } - // Create scans to preform + // Create scans to perform for tech, workingDirs := range techToWorkingDirs { if tech == techutils.Dotnet { // We detect Dotnet and Nuget the same way, if one detected so does the other. // We don't need to scan for both and get duplicate results. continue } - // No technology was detected, add scan without descriptors. (so no sca scan will be preformed and set at target level) + // No technology was detected, add scan without descriptors. (so no sca scan will be performed and set at target level) if len(workingDirs) == 0 { // Requested technology (from params) descriptors/indicators were not found or recursive scan with NoTech value, add scan without descriptors. cmdResults.NewScanResults(results.ScanTarget{Target: requestedDirectory, Technology: tech}) diff --git a/commands/audit/audit_test.go b/commands/audit/audit_test.go index 420bef09..442a5fde 100644 --- a/commands/audit/audit_test.go +++ b/commands/audit/audit_test.go @@ -2,17 +2,18 @@ package audit import ( "fmt" - commonCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" - "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - configTests "github.com/jfrog/jfrog-cli-security/tests" - securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" - clientTests "github.com/jfrog/jfrog-client-go/utils/tests" "net/http" "path/filepath" "sort" "strings" "testing" + commonCommands "github.com/jfrog/jfrog-cli-core/v2/common/commands" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + configTests "github.com/jfrog/jfrog-cli-security/tests" + securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" + clientTests "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/stretchr/testify/assert" "github.com/jfrog/jfrog-cli-security/utils" @@ -20,7 +21,6 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/results/conversion" "github.com/jfrog/jfrog-cli-security/utils/techutils" "github.com/jfrog/jfrog-cli-security/utils/validations" - "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" biutils "github.com/jfrog/build-info-go/utils" @@ -28,11 +28,12 @@ import ( coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" - scanservices "github.com/jfrog/jfrog-client-go/xray/services" + xrayServices "github.com/jfrog/jfrog-client-go/xray/services" + xrayApi "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/jfrog/jfrog-client-go/xsc/services" ) -func TestDetectScansToPreform(t *testing.T) { +func TestDetectScansToPerform(t *testing.T) { dir, cleanUp := createTestDir(t) @@ -56,14 +57,14 @@ func TestDetectScansToPreform(t *testing.T) { ScanTarget: results.ScanTarget{ Target: filepath.Join(dir, "Nuget"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, }, { ScanTarget: results.ScanTarget{ Technology: techutils.Go, Target: filepath.Join(dir, "dir", "go"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")}, }, @@ -73,7 +74,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Maven, Target: filepath.Join(dir, "dir", "maven"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{ filepath.Join(dir, "dir", "maven", "maven-sub", "pom.xml"), @@ -87,7 +88,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Npm, Target: filepath.Join(dir, "dir", "npm"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "dir", "npm", "package.json")}, }, @@ -97,7 +98,7 @@ func TestDetectScansToPreform(t *testing.T) { ScanTarget: results.ScanTarget{ Target: filepath.Join(dir, "yarn"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, }, }, }, @@ -115,7 +116,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Nuget, Target: filepath.Join(dir, "Nuget"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "Nuget", "Nuget-sub", "project.csproj"), filepath.Join(dir, "Nuget", "project.sln")}, }, @@ -125,7 +126,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Go, Target: filepath.Join(dir, "dir", "go"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")}, }, @@ -135,7 +136,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Maven, Target: filepath.Join(dir, "dir", "maven"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{ filepath.Join(dir, "dir", "maven", "maven-sub", "pom.xml"), @@ -149,7 +150,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Npm, Target: filepath.Join(dir, "dir", "npm"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "dir", "npm", "package.json")}, }, @@ -159,7 +160,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Yarn, Target: filepath.Join(dir, "yarn"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "yarn", "package.json")}, }, @@ -169,7 +170,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Pip, Target: filepath.Join(dir, "yarn", "Pip"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "yarn", "Pip", "requirements.txt")}, }, @@ -179,7 +180,7 @@ func TestDetectScansToPreform(t *testing.T) { Technology: techutils.Pipenv, Target: filepath.Join(dir, "yarn", "Pipenv"), }, - JasResults: &results.JasScansResults{}, + JasResults: &results.JasScansResults{JasVulnerabilities: results.JasScanResults{}, JasViolations: results.JasScanResults{}}, ScaResults: &results.ScaScanResults{ Descriptors: []string{filepath.Join(dir, "yarn", "Pipenv", "Pipfile")}, }, @@ -425,37 +426,30 @@ func TestAuditWithConfigProfile(t *testing.T) { SetMultiScanId(validations.TestMsi). SetGraphBasicParams(auditBasicParams). SetConfigProfile(&configProfile). - SetCommonGraphScanParams(&scangraph.CommonGraphScanParams{ - RepoPath: "", - ScanType: scanservices.Dependency, - IncludeVulnerabilities: true, - }) + SetResultsContext(results.ResultContext{IncludeVulnerabilities: true}) auditParams.SetWorkingDirs([]string{tempDirPath}).SetIsRecursiveScan(true) auditResults := RunAudit(auditParams) assert.NoError(t, auditResults.GetErrors()) - summary, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true}).ConvertToSummary(auditResults) + summary, err := conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{IncludeVulnerabilities: true}).ConvertToSummary(auditResults) assert.NoError(t, err) - var ScaResultsCount int + var scaResultsCount int // When checking Applicability results with ExactResultsMatch = true, the sum of all statuses should equal total Sca results amount. Else, we check the provided Sca issues amount if testcase.expectedCaApplicable > 0 || testcase.expectedCaNotApplicable > 0 || testcase.expectedCaNotCovered > 0 || testcase.expectedCaUndetermined > 0 { - ScaResultsCount = testcase.expectedCaApplicable + testcase.expectedCaNotApplicable + testcase.expectedCaNotCovered + testcase.expectedCaUndetermined + scaResultsCount = testcase.expectedCaApplicable + testcase.expectedCaNotApplicable + testcase.expectedCaNotCovered + testcase.expectedCaUndetermined } else { - ScaResultsCount = testcase.expectedScaIssues + scaResultsCount = testcase.expectedScaIssues } validations.ValidateCommandSummaryOutput(t, validations.ValidationParams{ Actual: summary, ExactResultsMatch: true, - Vulnerabilities: testcase.expectedSastIssues + testcase.expectedSecretsIssues + testcase.expectedIacIssues + ScaResultsCount, - Sast: testcase.expectedSastIssues, - Secrets: testcase.expectedSecretsIssues, - Iac: testcase.expectedIacIssues, - Applicable: testcase.expectedCaApplicable, - NotApplicable: testcase.expectedCaNotApplicable, - NotCovered: testcase.expectedCaNotCovered, - Undetermined: testcase.expectedCaUndetermined, + Total: &validations.TotalCount{Vulnerabilities: testcase.expectedSastIssues + testcase.expectedSecretsIssues + testcase.expectedIacIssues + scaResultsCount}, + Vulnerabilities: &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: scaResultsCount, Sast: testcase.expectedSastIssues, Secrets: testcase.expectedSecretsIssues, Iac: testcase.expectedIacIssues}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: testcase.expectedCaApplicable, NotApplicable: testcase.expectedCaNotApplicable, NotCovered: testcase.expectedCaNotCovered, Undetermined: testcase.expectedCaUndetermined}, + }, }) }) } @@ -484,10 +478,7 @@ func TestAuditWithScansOutputDir(t *testing.T) { SetWorkingDirs([]string{tempDirPath}). SetMultiScanId(validations.TestScaScanId). SetGraphBasicParams(auditBasicParams). - SetCommonGraphScanParams(&scangraph.CommonGraphScanParams{ - ScanType: scanservices.Dependency, - IncludeVulnerabilities: true, - }). + SetResultsContext(results.ResultContext{IncludeVulnerabilities: true}). SetScansResultsOutputDir(outputDirPath) auditParams.SetIsRecursiveScan(true) @@ -623,10 +614,7 @@ func TestAuditWithPartialResults(t *testing.T) { SetWorkingDirs([]string{tempDirPath}). SetMultiScanId(validations.TestScaScanId). SetGraphBasicParams(auditBasicParams). - SetCommonGraphScanParams(&scangraph.CommonGraphScanParams{ - ScanType: scanservices.Dependency, - IncludeVulnerabilities: true, - }) + SetResultsContext(results.ResultContext{IncludeVulnerabilities: true}) auditParams.SetIsRecursiveScan(true) auditResults := RunAudit(auditParams) @@ -638,3 +626,111 @@ func TestAuditWithPartialResults(t *testing.T) { }) } } + +func TestCreateResultsContext(t *testing.T) { + mockWatches := []string{"watch-1", "watch-2"} + mockProjectKey := "project" + mockArtifactoryRepoPath := "repo/path" + + tests := []struct { + name string + xrayVersion string + expectedPlatformWatches xrayApi.ResourcesWatchesBody + }{ + { + name: "Git Repo Url Supported", + xrayVersion: xrayServices.MinXrayVersionGitRepoKey, + expectedPlatformWatches: xrayApi.ResourcesWatchesBody{GitRepositoryWatches: mockWatches}, + }, + { + name: "Git Repo Url Not Supported (Backward Compatibility)", + xrayVersion: "1.0.0", + }, + } + for _, test := range tests { + testCaseExpectedGitRepoHttpsCloneUrl := "" + expectedIncludeVulnerabilitiesIfOnlyGitRepoUrlProvided := false + if len(test.expectedPlatformWatches.GitRepositoryWatches) > 0 { + // We should include the value of gitRepoUrl only if a watch is assigned to this git_repository + testCaseExpectedGitRepoHttpsCloneUrl = validations.TestMockGitInfo.GitRepoHttpsCloneUrl + } else { + // If only the git repo url is provided but not supported or there are no defined watches, the expected includeVulnerabilities flag should be set to true even if not provided + expectedIncludeVulnerabilitiesIfOnlyGitRepoUrlProvided = true + } + testCases := []struct { + name string + + artifactoryRepoPath string + httpCloneUrl string + watches []string + jfrogProjectKey string + includeVulnerabilities bool + includeLicenses bool + + expectedArtifactoryRepoPath string + expectedHttpCloneUrl string + expectedWatches []string + expectedJfrogProjectKey string + expectedIncludeVulnerabilities bool + expectedIncludeLicenses bool + }{ + { + name: "Only Vulnerabilities", + includeLicenses: true, + // Since no violation context is provided, the includeVulnerabilities flag should be set to true even if not provided + expectedIncludeVulnerabilities: true, + expectedIncludeLicenses: true, + }, + { + name: "Watches", + watches: mockWatches, + expectedWatches: mockWatches, + }, + { + name: "Artifactory Repo Path", + artifactoryRepoPath: mockArtifactoryRepoPath, + expectedArtifactoryRepoPath: mockArtifactoryRepoPath, + }, + { + name: "Project key", + jfrogProjectKey: mockProjectKey, + expectedJfrogProjectKey: mockProjectKey, + includeLicenses: true, + expectedIncludeLicenses: true, + }, + { + name: "Git Clone Url", + httpCloneUrl: validations.TestMockGitInfo.GitRepoHttpsCloneUrl, + expectedHttpCloneUrl: testCaseExpectedGitRepoHttpsCloneUrl, + expectedIncludeVulnerabilities: expectedIncludeVulnerabilitiesIfOnlyGitRepoUrlProvided, + }, + { + name: "All", + httpCloneUrl: validations.TestMockGitInfo.GitRepoHttpsCloneUrl, + watches: mockWatches, + jfrogProjectKey: mockProjectKey, + includeVulnerabilities: true, + includeLicenses: true, + + expectedHttpCloneUrl: testCaseExpectedGitRepoHttpsCloneUrl, + expectedWatches: mockWatches, + expectedJfrogProjectKey: mockProjectKey, + expectedIncludeVulnerabilities: true, + expectedIncludeLicenses: true, + }, + } + for _, testCase := range testCases { + t.Run(fmt.Sprintf("%s - %s", test.name, testCase.name), func(t *testing.T) { + mockServer, serverDetails := validations.XrayServer(t, validations.MockServerParams{XrayVersion: test.xrayVersion, ReturnMockPlatformWatches: test.expectedPlatformWatches}) + defer mockServer.Close() + context := CreateAuditResultsContext(serverDetails, test.xrayVersion, testCase.watches, testCase.artifactoryRepoPath, testCase.jfrogProjectKey, testCase.httpCloneUrl, testCase.includeVulnerabilities, testCase.includeLicenses) + assert.Equal(t, testCase.expectedArtifactoryRepoPath, context.RepoPath) + assert.Equal(t, testCase.expectedHttpCloneUrl, context.GitRepoHttpsCloneUrl) + assert.Equal(t, testCase.expectedWatches, context.Watches) + assert.Equal(t, testCase.expectedJfrogProjectKey, context.ProjectKey) + assert.Equal(t, testCase.expectedIncludeVulnerabilities, context.IncludeVulnerabilities) + assert.Equal(t, testCase.expectedIncludeLicenses, context.IncludeLicenses) + }) + } + } +} diff --git a/commands/audit/auditparams.go b/commands/audit/auditparams.go index 595c9673..90dc3678 100644 --- a/commands/audit/auditparams.go +++ b/commands/audit/auditparams.go @@ -4,25 +4,25 @@ import ( "time" xrayutils "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" - "github.com/jfrog/jfrog-cli-security/utils/xray/scangraph" "github.com/jfrog/jfrog-client-go/xray/services" - clientservices "github.com/jfrog/jfrog-client-go/xsc/services" + xscservices "github.com/jfrog/jfrog-client-go/xsc/services" ) type AuditParams struct { // Common params to all scan routines - commonGraphScanParams *scangraph.CommonGraphScanParams - workingDirs []string - installFunc func(tech string) error - fixableOnly bool - minSeverityFilter severityutils.Severity + resultsContext results.ResultContext + workingDirs []string + installFunc func(tech string) error + fixableOnly bool + minSeverityFilter severityutils.Severity *xrayutils.AuditBasicParams multiScanId string // Include third party dependencies source code in the applicability scan. thirdPartyApplicabilityScan bool threads int - configProfile *clientservices.ConfigProfile + configProfile *xscservices.ConfigProfile scanResultsOutputDir string startTime time.Time } @@ -107,12 +107,12 @@ func (params *AuditParams) SetThreads(threads int) *AuditParams { return params } -func (params *AuditParams) SetCommonGraphScanParams(commonParams *scangraph.CommonGraphScanParams) *AuditParams { - params.commonGraphScanParams = commonParams +func (params *AuditParams) SetResultsContext(resultsContext results.ResultContext) *AuditParams { + params.resultsContext = resultsContext return params } -func (params *AuditParams) SetConfigProfile(configProfile *clientservices.ConfigProfile) *AuditParams { +func (params *AuditParams) SetConfigProfile(configProfile *xscservices.ConfigProfile) *AuditParams { params.configProfile = configProfile return params } @@ -124,11 +124,12 @@ func (params *AuditParams) SetScansResultsOutputDir(outputDir string) *AuditPara func (params *AuditParams) createXrayGraphScanParams() *services.XrayGraphScanParams { return &services.XrayGraphScanParams{ - RepoPath: params.commonGraphScanParams.RepoPath, - Watches: params.commonGraphScanParams.Watches, - ScanType: params.commonGraphScanParams.ScanType, - ProjectKey: params.commonGraphScanParams.ProjectKey, - IncludeVulnerabilities: params.commonGraphScanParams.IncludeVulnerabilities, - IncludeLicenses: params.commonGraphScanParams.IncludeLicenses, + RepoPath: params.resultsContext.RepoPath, + Watches: params.resultsContext.Watches, + ProjectKey: params.resultsContext.ProjectKey, + GitRepoHttpsCloneUrl: params.resultsContext.GitRepoHttpsCloneUrl, + IncludeVulnerabilities: params.resultsContext.IncludeVulnerabilities, + IncludeLicenses: params.resultsContext.IncludeLicenses, + ScanType: services.Dependency, } } diff --git a/commands/audit/sca/common.go b/commands/audit/sca/common.go index 158e188b..3b4e0612 100644 --- a/commands/audit/sca/common.go +++ b/commands/audit/sca/common.go @@ -37,23 +37,16 @@ func GetExcludePattern(params utils.AuditParams) string { return fspatterns.PrepareExcludePathPattern(exclusions, clientutils.WildCardPattern, params.IsRecursiveScan()) } -func RunXrayDependenciesTreeScanGraph(dependencyTree xrayUtils.GraphNode, technology techutils.Technology, scanGraphParams *scangraph.ScanGraphParams) (results []services.ScanResponse, err error) { - scanGraphParams.XrayGraphScanParams().XrayVersion = scanGraphParams.XrayVersion() - scanGraphParams.XrayGraphScanParams().DependenciesGraph = &dependencyTree - xscGitInfoContext := scanGraphParams.XrayGraphScanParams().XscGitInfoContext - if xscGitInfoContext != nil { - xscGitInfoContext.Technologies = []string{technology.String()} - } - scanMessage := fmt.Sprintf("Scanning %d %s dependencies", len(dependencyTree.Nodes), technology) - log.Info(scanMessage + "...") +func RunXrayDependenciesTreeScanGraph(scanGraphParams *scangraph.ScanGraphParams) (results []services.ScanResponse, err error) { var scanResults *services.ScanResponse + technology := scanGraphParams.Technology() xrayManager, err := xray.CreateXrayServiceManager(scanGraphParams.ServerDetails()) if err != nil { return nil, err } scanResults, err = scangraph.RunScanGraphAndGetResults(scanGraphParams, xrayManager) if err != nil { - err = errorutils.CheckErrorf("scanning %s dependencies failed with error: %s", string(technology), err.Error()) + err = errorutils.CheckErrorf("scanning %s dependencies failed with error: %s", technology.ToFormal(), err.Error()) return } for i := range scanResults.Vulnerabilities { @@ -66,6 +59,19 @@ func RunXrayDependenciesTreeScanGraph(dependencyTree xrayUtils.GraphNode, techno return } +// Infer the status code of SCA Xray scan, must have at least one result, if err occurred or any of the results is `failed` return 1, otherwise return 0. +func GetScaScansStatusCode(err error, results ...services.ScanResponse) int { + if err != nil || len(results) == 0 { + return 1 + } + for _, result := range results { + if result.ScannedStatus == "Failed" { + return 1 + } + } + return 0 +} + func CreateTestWorkspace(t *testing.T, sourceDir string) (string, func()) { return tests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", sourceDir)) } diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index b24e36a1..abbe9451 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -41,7 +41,7 @@ import ( xrayCmdUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" ) -// We can only preform SCA scan if we identified at least one technology for a target. +// We can only perform SCA scan if we identified at least one technology for a target. func hasAtLeastOneTech(cmdResults *results.SecurityCommandResults) bool { if len(cmdResults.Targets) == 0 { return false @@ -86,7 +86,7 @@ func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner // Make sure to return to the original working directory, buildDependencyTree may change it generalError = errors.Join(generalError, errorutils.CheckError(os.Chdir(currentWorkingDir))) }() - // Preform SCA scans + // Perform SCA scans for _, targetResult := range cmdResults.Targets { if targetResult.Technology == "" { log.Warn(fmt.Sprintf("Couldn't determine a package manager or build tool used by this project. Skipping the SCA scan in '%s'...", targetResult.Target)) @@ -100,7 +100,7 @@ func buildDepTreeAndRunScaScan(auditParallelRunner *utils.SecurityParallelRunner log.Warn(bdtErr.Error()) continue } - _ = targetResult.AddTargetError(fmt.Errorf("Failed to build dependency tree: %s", bdtErr.Error()), auditParams.AllowPartialResults()) + _ = targetResult.AddTargetError(fmt.Errorf("failed to build dependency tree: %s", bdtErr.Error()), auditParams.AllowPartialResults()) continue } // Create sca scan task @@ -125,7 +125,7 @@ func getRequestedDescriptors(params *AuditParams) map[techutils.Technology][]str return requestedDescriptors } -// Preform the SCA scan for the given scan information. +// Perform the SCA scan for the given scan information. func executeScaScanTask(auditParallelRunner *utils.SecurityParallelRunner, serverDetails *config.ServerDetails, auditParams *AuditParams, scan *results.TargetResults, treeResult *DependencyTreeResult) parallel.TaskFunc { return func(threadId int) (err error) { @@ -133,34 +133,43 @@ func executeScaScanTask(auditParallelRunner *utils.SecurityParallelRunner, serve log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Running SCA scan for", scan.Target, "vulnerable dependencies in", scan.Target, "directory...") // Scan the dependency tree. scanResults, xrayErr := runScaWithTech(scan.Technology, auditParams, serverDetails, *treeResult.FlatTree, treeResult.FullDepTrees) + + auditParallelRunner.ResultsMu.Lock() + defer auditParallelRunner.ResultsMu.Unlock() + // We add the results before checking for errors, so we can display the results even if an error occurred. + scan.NewScaScanResults(sca.GetScaScansStatusCode(xrayErr, scanResults...), scanResults...).IsMultipleRootProject = clientutils.Pointer(len(treeResult.FullDepTrees) > 1) + addThirdPartyDependenciesToParams(auditParams, scan.Technology, treeResult.FlatTree, treeResult.FullDepTrees) + if xrayErr != nil { return fmt.Errorf("%s Xray dependency tree scan request on '%s' failed:\n%s", clientutils.GetLogMsgPrefix(threadId, false), scan.Technology, xrayErr.Error()) } - auditParallelRunner.ResultsMu.Lock() - scan.NewScaScanResults(scanResults...).IsMultipleRootProject = clientutils.Pointer(len(treeResult.FullDepTrees) > 1) - addThirdPartyDependenciesToParams(auditParams, scan.Technology, treeResult.FlatTree, treeResult.FullDepTrees) err = dumpScanResponseToFileIfNeeded(scanResults, auditParams.scanResultsOutputDir, utils.ScaScan) - auditParallelRunner.ResultsMu.Unlock() return } } func runScaWithTech(tech techutils.Technology, params *AuditParams, serverDetails *config.ServerDetails, flatTree xrayCmdUtils.GraphNode, fullDependencyTrees []*xrayCmdUtils.GraphNode) (techResults []services.ScanResponse, err error) { + // Create the scan graph parameters. xrayScanGraphParams := params.createXrayGraphScanParams() xrayScanGraphParams.MultiScanId = params.GetMultiScanId() + xrayScanGraphParams.XrayVersion = params.GetXrayVersion() xrayScanGraphParams.XscVersion = params.GetXscVersion() + xrayScanGraphParams.Technology = tech.String() + xrayScanGraphParams.DependenciesGraph = &flatTree scanGraphParams := scangraph.NewScanGraphParams(). SetServerDetails(serverDetails). SetXrayGraphScanParams(xrayScanGraphParams). - SetXrayVersion(params.GetXrayVersion()). SetFixableOnly(params.fixableOnly). SetSeverityLevel(params.minSeverityFilter.String()) - techResults, err = sca.RunXrayDependenciesTreeScanGraph(flatTree, tech, scanGraphParams) + + log.Info(fmt.Sprintf("Scanning %d %s dependencies", len(flatTree.Nodes), tech) + "...") + techResults, err = sca.RunXrayDependenciesTreeScanGraph(scanGraphParams) if err != nil { return } + log.Debug(fmt.Sprintf("Finished '%s' dependency tree scan. %s", tech.ToFormal(), utils.GetScanFindingsLog(utils.ScaScan, len(techResults[0].Vulnerabilities), len(techResults[0].Violations), -1))) techResults = sca.BuildImpactPathsForScanResponse(techResults, fullDependencyTrees) return } diff --git a/commands/enrich/enrich.go b/commands/enrich/enrich.go index e4a1e35a..4ac6871d 100644 --- a/commands/enrich/enrich.go +++ b/commands/enrich/enrich.go @@ -14,6 +14,7 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/common/spec" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-security/commands/audit/sca" "github.com/jfrog/jfrog-cli-security/commands/enrich/enrichgraph" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/results" @@ -265,7 +266,7 @@ func (enrichCmd *EnrichCommand) createIndexerHandlerFunc(indexedFileProducer par if err != nil { return targetResults.AddTargetError(fmt.Errorf("%s failed to import graph: %s", logPrefix, err.Error()), false) } - targetResults.NewScaScanResults(*scanResults) + targetResults.NewScaScanResults(sca.GetScaScansStatusCode(err, *scanResults), *scanResults) targetResults.Technology = techutils.Technology(scanResults.ScannedPackageType) return } diff --git a/commands/scan/buildscan.go b/commands/scan/buildscan.go index 4b491bf9..ba3995c6 100644 --- a/commands/scan/buildscan.go +++ b/commands/scan/buildscan.go @@ -145,9 +145,12 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS log.Info("The scan data is available at: " + buildScanResults.MoreDetailsUrl) isFailBuildResponse = buildScanResults.FailBuild - cmdResults := results.NewCommandResults(utils.Build).SetXrayVersion(xrayVersion) + cmdResults := results.NewCommandResults(utils.Build). + SetXrayVersion(xrayVersion). + SetResultsContext(results.ResultContext{ProjectKey: params.Project, IncludeVulnerabilities: bsc.includeVulnerabilities}) + scanResults := cmdResults.NewScanResults(results.ScanTarget{Name: fmt.Sprintf("%s (%s)", params.BuildName, params.BuildNumber)}) - scanResults.NewScaScanResults(services.ScanResponse{ + scanResults.NewScaScanResults(0, services.ScanResponse{ Violations: buildScanResults.Violations, Vulnerabilities: buildScanResults.Vulnerabilities, XrayDataUrl: buildScanResults.MoreDetailsUrl, @@ -156,9 +159,8 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS resultsPrinter := output.NewResultsWriter(cmdResults). SetOutputFormat(bsc.outputFormat). SetPlatformUrl(bsc.serverDetails.Url). + // In build-scan we always want to print the violations. SetHasViolationContext(true). - SetIncludeVulnerabilities(bsc.includeVulnerabilities). - SetIncludeLicenses(false). SetIsMultipleRootProject(true). SetPrintExtendedTable(bsc.printExtendedTable) @@ -167,14 +169,10 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS if err = resultsPrinter.PrintScanResults(); err != nil { return } - } else { - // Print two different tables for violations and vulnerabilities (if needed) - + } else if !noFailBuildPolicy { // If "No Xray Fail build policy...." error received, no need to print violations - if !noFailBuildPolicy { - if err = resultsPrinter.PrintScanResults(); err != nil { - return false, err - } + if err = resultsPrinter.PrintScanResults(); err != nil { + return false, err } } err = bsc.recordResults(cmdResults, params) diff --git a/commands/scan/dockerscan.go b/commands/scan/dockerscan.go index f200628e..f06a40ce 100644 --- a/commands/scan/dockerscan.go +++ b/commands/scan/dockerscan.go @@ -28,8 +28,7 @@ const ( type DockerScanCommand struct { ScanCommand - imageTag string - targetRepoPath string + imageTag string } func NewDockerScanCommand() *DockerScanCommand { @@ -41,11 +40,6 @@ func (dsc *DockerScanCommand) SetImageTag(imageTag string) *DockerScanCommand { return dsc } -func (dsc *DockerScanCommand) SetTargetRepoPath(repoPath string) *DockerScanCommand { - dsc.targetRepoPath = repoPath - return dsc -} - func (dsc *DockerScanCommand) Run() (err error) { // Validate Xray minimum version err = clientutils.ValidateMinimumVersion(clientutils.Xray, dsc.xrayVersion, DockerScanMinXrayVersion) @@ -88,7 +82,7 @@ func (dsc *DockerScanCommand) Run() (err error) { dsc.SetSpec(spec.NewBuilder(). Pattern(imageTarPath). - Target(dsc.targetRepoPath). + Target(dsc.resultsContext.RepoPath). BuildSpec()).SetThreads(1) dsc.ScanCommand.SetTargetNameOverride(dsc.imageTag).SetRunJasScans(true) err = dsc.setCredentialEnvsForIndexerApp() @@ -111,12 +105,12 @@ func (dsc *DockerScanCommand) Run() (err error) { } func (dsc *DockerScanCommand) recordResults(scanResults *results.SecurityCommandResults) (err error) { - hasViolationContext := dsc.ScanCommand.hasViolationContext() - if err = output.RecordSarifOutput(scanResults, dsc.ScanCommand.serverDetails, dsc.ScanCommand.includeVulnerabilities, hasViolationContext); err != nil { + hasViolationContext := dsc.ScanCommand.resultsContext.HasViolationContext() + if err = output.RecordSarifOutput(scanResults, dsc.ScanCommand.serverDetails, dsc.ScanCommand.resultsContext.IncludeVulnerabilities, hasViolationContext); err != nil { return } var summary output.ScanCommandResultSummary - if summary, err = output.NewDockerScanSummary(scanResults, dsc.ScanCommand.serverDetails, dsc.ScanCommand.includeVulnerabilities, hasViolationContext, dsc.imageTag); err != nil { + if summary, err = output.NewDockerScanSummary(scanResults, dsc.ScanCommand.serverDetails, dsc.ScanCommand.resultsContext.IncludeVulnerabilities, hasViolationContext, dsc.imageTag); err != nil { return } return output.RecordSecurityCommandSummary(summary) diff --git a/commands/scan/scan.go b/commands/scan/scan.go index 7bea7280..c7ea0227 100644 --- a/commands/scan/scan.go +++ b/commands/scan/scan.go @@ -15,6 +15,7 @@ import ( "golang.org/x/exp/slices" jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" + "github.com/jfrog/jfrog-cli-security/commands/audit/sca" "github.com/jfrog/jfrog-cli-security/jas" "github.com/jfrog/jfrog-cli-security/jas/applicability" "github.com/jfrog/jfrog-cli-security/jas/runner" @@ -59,24 +60,22 @@ type ScanCommand struct { spec *spec.SpecFiles threads int // The location of the downloaded Xray indexer binary on the local file system. - indexerPath string - indexerTempDir string - outputFormat format.OutputFormat - projectKey string - minSeverityFilter severityutils.Severity - watches []string - includeVulnerabilities bool - includeLicenses bool - fail bool - printExtendedTable bool - validateSecrets bool - bypassArchiveLimits bool - fixableOnly bool - progress ioUtils.ProgressMgr + indexerPath string + indexerTempDir string + outputFormat format.OutputFormat + minSeverityFilter severityutils.Severity + fail bool + printExtendedTable bool + validateSecrets bool + bypassArchiveLimits bool + fixableOnly bool + progress ioUtils.ProgressMgr // JAS is only supported for Docker images. commandSupportsJAS bool targetNameOverride string + resultsContext results.ResultContext + xrayVersion string xscVersion string multiScanId string @@ -133,22 +132,27 @@ func (scanCmd *ScanCommand) SetSpec(spec *spec.SpecFiles) *ScanCommand { } func (scanCmd *ScanCommand) SetProject(project string) *ScanCommand { - scanCmd.projectKey = project + scanCmd.resultsContext.ProjectKey = project return scanCmd } func (scanCmd *ScanCommand) SetWatches(watches []string) *ScanCommand { - scanCmd.watches = watches + scanCmd.resultsContext.Watches = watches + return scanCmd +} + +func (scanCmd *ScanCommand) SetBaseRepoPath(artifactoryRepoPath string) *ScanCommand { + scanCmd.resultsContext.RepoPath = artifactoryRepoPath return scanCmd } func (scanCmd *ScanCommand) SetIncludeVulnerabilities(include bool) *ScanCommand { - scanCmd.includeVulnerabilities = include + scanCmd.resultsContext.IncludeVulnerabilities = include return scanCmd } func (scanCmd *ScanCommand) SetIncludeLicenses(include bool) *ScanCommand { - scanCmd.includeLicenses = include + scanCmd.resultsContext.IncludeLicenses = include return scanCmd } @@ -181,10 +185,6 @@ func (scanCmd *ScanCommand) SetXscVersion(xscVersion string) *ScanCommand { return scanCmd } -func (scanCmd *ScanCommand) hasViolationContext() bool { - return len(scanCmd.watches) > 0 || scanCmd.projectKey != "" -} - func (scanCmd *ScanCommand) indexFile(filePath string) (*xrayUtils.BinaryGraphNode, error) { var indexerResults xrayUtils.BinaryGraphNode indexerCmd := exec.Command(scanCmd.indexerPath, indexingCommand, filePath, "--temp-dir", scanCmd.indexerTempDir) @@ -218,12 +218,12 @@ func (scanCmd *ScanCommand) Run() (err error) { } func (scanCmd *ScanCommand) recordResults(scanResults *results.SecurityCommandResults) (err error) { - hasViolationContext := scanCmd.hasViolationContext() - if err = output.RecordSarifOutput(scanResults, scanCmd.serverDetails, scanCmd.includeVulnerabilities, hasViolationContext); err != nil { + hasViolationContext := scanCmd.resultsContext.HasViolationContext() + if err = output.RecordSarifOutput(scanResults, scanCmd.serverDetails, scanCmd.resultsContext.IncludeVulnerabilities, hasViolationContext); err != nil { return } var summary output.ScanCommandResultSummary - if summary, err = output.NewBinaryScanSummary(scanResults, scanCmd.serverDetails, scanCmd.includeVulnerabilities, hasViolationContext); err != nil { + if summary, err = output.NewBinaryScanSummary(scanResults, scanCmd.serverDetails, scanCmd.resultsContext.IncludeVulnerabilities, hasViolationContext); err != nil { return } return output.RecordSecurityCommandSummary(summary) @@ -252,9 +252,6 @@ func (scanCmd *ScanCommand) RunAndRecordResults(cmdType utils.CommandType, recor if err = output.NewResultsWriter(cmdResults). SetOutputFormat(scanCmd.outputFormat). SetPlatformUrl(scanCmd.serverDetails.Url). - SetHasViolationContext(scanCmd.hasViolationContext()). - SetIncludeVulnerabilities(scanCmd.includeVulnerabilities). - SetIncludeLicenses(scanCmd.includeLicenses). SetPrintExtendedTable(scanCmd.printExtendedTable). SetIsMultipleRootProject(cmdResults.HasMultipleTargets()). PrintScanResults(); err != nil { @@ -269,7 +266,7 @@ func (scanCmd *ScanCommand) RunAndRecordResults(cmdType utils.CommandType, recor } // If includeVulnerabilities is false it means that context was provided, so we need to check for build violations. // If user provided --fail=false, don't fail the build. - if scanCmd.fail && !scanCmd.includeVulnerabilities { + if scanCmd.fail && !scanCmd.resultsContext.IncludeVulnerabilities { if results.CheckIfFailBuild(cmdResults.GetScaScansXrayResults()) { return results.NewFailBuildError() } @@ -279,21 +276,10 @@ func (scanCmd *ScanCommand) RunAndRecordResults(cmdType utils.CommandType, recor } func (scanCmd *ScanCommand) RunScan(cmdType utils.CommandType) (cmdResults *results.SecurityCommandResults) { - xrayManager, cmdResults := initScanCmdResults( - cmdType, - scanCmd.serverDetails, - scanCmd.xrayVersion, - scanCmd.xscVersion, - scanCmd.multiScanId, - scanCmd.startTime, - scanCmd.bypassArchiveLimits, - scanCmd.validateSecrets, - scanCmd.commandSupportsJAS, - ) - if cmdResults.GeneralError != nil { + xrayManager, cmdResults := scanCmd.initScanCmdResults(cmdType) + if cmdResults.GetErrors() != nil { return } - log.Info("JFrog Xray version is:", cmdResults.XrayVersion) // First, Download (if needed) the analyzer manager in a background routine. errGroup := new(errgroup.Group) if cmdResults.EntitledForJas { @@ -329,34 +315,35 @@ func (scanCmd *ScanCommand) RunScan(cmdType utils.CommandType) (cmdResults *resu return } -func initScanCmdResults(cmdType utils.CommandType, serverDetails *config.ServerDetails, xrayVersion, xscVersion, msi string, startTime time.Time, bypassArchiveLimits, validateSecrets, useJas bool) (xrayManager *xrayClient.XrayServicesManager, cmdResults *results.SecurityCommandResults) { +func (scanCmd *ScanCommand) initScanCmdResults(cmdType utils.CommandType) (xrayManager *xrayClient.XrayServicesManager, cmdResults *results.SecurityCommandResults) { cmdResults = results.NewCommandResults(cmdType) // Validate Xray minimum version for graph scan command - if err := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, scangraph.GraphScanMinXrayVersion); err != nil { + if err := clientutils.ValidateMinimumVersion(clientutils.Xray, scanCmd.xrayVersion, scangraph.GraphScanMinXrayVersion); err != nil { return xrayManager, cmdResults.AddGeneralError(err, false) } - if bypassArchiveLimits { + if scanCmd.bypassArchiveLimits { // Validate Xray minimum version for BypassArchiveLimits flag for indexer - if err := clientutils.ValidateMinimumVersion(clientutils.Xray, xrayVersion, BypassArchiveLimitsMinXrayVersion); err != nil { + if err := clientutils.ValidateMinimumVersion(clientutils.Xray, scanCmd.xrayVersion, BypassArchiveLimitsMinXrayVersion); err != nil { return xrayManager, cmdResults.AddGeneralError(err, false) } } - xrayManager, err := xray.CreateXrayServiceManager(serverDetails) + xrayManager, err := xray.CreateXrayServiceManager(scanCmd.serverDetails) if err != nil { return xrayManager, cmdResults.AddGeneralError(err, false) } // Initialize general information - cmdResults.SetXrayVersion(xrayVersion) - cmdResults.SetXscVersion(xscVersion) - cmdResults.SetMultiScanId(msi) - cmdResults.SetStartTime(startTime) + cmdResults.SetXrayVersion(scanCmd.xrayVersion) + cmdResults.SetXscVersion(scanCmd.xscVersion) + cmdResults.SetMultiScanId(scanCmd.multiScanId) + cmdResults.SetStartTime(scanCmd.startTime) + cmdResults.SetResultsContext(scanCmd.resultsContext) // Send entitlement request - if entitledForJas, err := isEntitledForJas(xrayManager, xrayVersion, useJas); err != nil { + if entitledForJas, err := isEntitledForJas(xrayManager, scanCmd.xrayVersion, scanCmd.commandSupportsJAS); err != nil { return xrayManager, cmdResults.AddGeneralError(err, false) } else { cmdResults.SetEntitledForJas(entitledForJas) if entitledForJas { - cmdResults.SetSecretValidation(jas.CheckForSecretValidation(xrayManager, xrayVersion, validateSecrets)) + cmdResults.SetSecretValidation(jas.CheckForSecretValidation(xrayManager, scanCmd.xrayVersion, scanCmd.validateSecrets)) } } return @@ -447,23 +434,21 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults params := &services.XrayGraphScanParams{ BinaryGraph: graph, RepoPath: getXrayRepoPathFromTarget(file.Target), - Watches: scanCmd.watches, - IncludeLicenses: scanCmd.includeLicenses, - IncludeVulnerabilities: scanCmd.includeVulnerabilities, - ProjectKey: scanCmd.projectKey, + Watches: scanCmd.resultsContext.Watches, + IncludeLicenses: scanCmd.resultsContext.IncludeLicenses, + IncludeVulnerabilities: scanCmd.resultsContext.IncludeVulnerabilities, + ProjectKey: scanCmd.resultsContext.ProjectKey, ScanType: services.Binary, MultiScanId: cmdResults.MultiScanId, XscVersion: cmdResults.XscVersion, XrayVersion: cmdResults.XrayVersion, } - if scanCmd.progress != nil { scanCmd.progress.SetHeadlineMsg("Scanning 🔍") } scanGraphParams := scangraph.NewScanGraphParams(). SetServerDetails(scanCmd.serverDetails). SetXrayGraphScanParams(params). - SetXrayVersion(cmdResults.XrayVersion). SetFixableOnly(scanCmd.fixableOnly). SetSeverityLevel(scanCmd.minSeverityFilter.String()) xrayManager, err := xray.CreateXrayServiceManager(scanGraphParams.ServerDetails()) @@ -474,7 +459,7 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults if err != nil { return targetResults.AddTargetError(fmt.Errorf("%s sca scanning '%s' failed with error: %s", scanLogPrefix, graph.Id, err.Error()), false) } else { - targetResults.NewScaScanResults(*graphScanResults) + targetResults.NewScaScanResults(sca.GetScaScansStatusCode(err, *graphScanResults), *graphScanResults) targetResults.Technology = techutils.Technology(graphScanResults.ScannedPackageType) } if !cmdResults.EntitledForJas { @@ -485,7 +470,18 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults return targetResults.AddTargetError(fmt.Errorf("%s jas scanning failed with error: %s", scanLogPrefix, err.Error()), false) } // Run Jas scans - scanner, err := jas.CreateJasScanner(scanCmd.serverDetails, cmdResults.SecretValidation, scanCmd.minSeverityFilter, jas.GetAnalyzerManagerXscEnvVars(cmdResults.MultiScanId, targetResults.GetTechnologies()...)) + scanner, err := jas.CreateJasScanner(scanCmd.serverDetails, + cmdResults.SecretValidation, + scanCmd.minSeverityFilter, + jas.GetAnalyzerManagerXscEnvVars( + cmdResults.MultiScanId, + // Passing but empty since not supported for binary scans + scanCmd.resultsContext.GitRepoHttpsCloneUrl, + scanCmd.resultsContext.ProjectKey, + scanCmd.resultsContext.Watches, + targetResults.GetTechnologies()..., + ), + ) if err != nil { return targetResults.AddTargetError(fmt.Errorf("failed to create jas scanner: %s", err.Error()), false) } else if scanner == nil { @@ -497,7 +493,7 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, cmdResults ServerDetails: scanCmd.serverDetails, Scanner: scanner, Module: module, - ScansToPreform: utils.GetAllSupportedScans(), + ScansToPerform: utils.GetAllSupportedScans(), SecretsScanType: secrets.SecretsScannerDockerScanType, DirectDependencies: directDepsListFromVulnerabilities(*graphScanResults), ApplicableScanType: applicability.ApplicabilityDockerScanScanType, diff --git a/go.mod b/go.mod index 5c08a53b..2eb9ed0c 100644 --- a/go.mod +++ b/go.mod @@ -112,7 +112,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go dev +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1 // replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev diff --git a/go.sum b/go.sum index 9c3fec37..8685e030 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYL github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= github.com/jfrog/jfrog-cli-core/v2 v2.57.5 h1:guVB/zPPtS8CWpNvAFPCxNvSgVra4TyX8lzs4V4+I/4= github.com/jfrog/jfrog-cli-core/v2 v2.57.5/go.mod h1:LfKvCRXbvwgE0V6aX3/GabkzCedghXq0Y6lmsEuxr44= -github.com/jfrog/jfrog-client-go v1.48.6 h1:Pl9foa9XBaPbP3gz4wevDmF/mpfit94IQHKQKnfk3lA= -github.com/jfrog/jfrog-client-go v1.48.6/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1 h1:JQvbTSPDkPNpts1NLHGTKvtG4cMFY1ptBHTNMYFyMhs= +github.com/jfrog/jfrog-client-go v1.28.1-0.20241230154616-e342ed5065f1/go.mod h1:2ySOMva54L3EYYIlCBYBTcTgqfrrQ19gtpA/MWfA/ec= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/jas/analyzermanager.go b/jas/analyzermanager.go index ea09ae09..976e3561 100644 --- a/jas/analyzermanager.go +++ b/jas/analyzermanager.go @@ -24,7 +24,7 @@ import ( const ( ApplicabilityFeatureId = "contextual_analysis" AnalyzerManagerZipName = "analyzerManager.zip" - defaultAnalyzerManagerVersion = "1.12.2" + defaultAnalyzerManagerVersion = "1.13.2" analyzerManagerDownloadPath = "xsc-gen-exe-analyzer-manager-local/v1" analyzerManagerDirName = "analyzerManager" analyzerManagerExecutableName = "analyzerManager" @@ -35,6 +35,9 @@ const ( jfPlatformUrlEnvVariable = "JF_PLATFORM_URL" jfPlatformXrayUrlEnvVariable = "JF_PLATFORM_XRAY_URL" logDirEnvVariable = "AM_LOG_DIRECTORY" + watchesEnvVariable = "AM_WATCHES" + projectEnvVariable = "AM_PROJECT_VIOLATIONS" + gitRepoEnvVariable = "AM_GIT_REPO_VIOLATIONS" notEntitledExitCode = 31 unsupportedCommandExitCode = 13 unsupportedOsExitCode = 55 @@ -156,18 +159,26 @@ func GetAnalyzerManagerEnvVariables(serverDetails *config.ServerDetails) (envVar } func ParseAnalyzerManagerError(scanner jasutils.JasScanType, err error) (formatErr error) { + if err == nil { + return + } + if exitCodeDescription, exitCodeExists := exitCodeErrorsMap[GetAnalyzerManagerExitCode(err)]; exitCodeExists { + log.Warn(exitCodeDescription) + return nil + } + return fmt.Errorf(ErrFailedScannerRun, scanner, err.Error()) +} + +func GetAnalyzerManagerExitCode(err error) int { var exitError *exec.ExitError if errors.As(err, &exitError) { - exitCode := exitError.ExitCode() - if exitCodeDescription, exitCodeExists := exitCodeErrorsMap[exitCode]; exitCodeExists { - log.Warn(exitCodeDescription) - return nil - } + return exitError.ExitCode() } if err != nil { - return fmt.Errorf(ErrFailedScannerRun, scanner, err.Error()) + // An exit code of -1 is used to indicate that an error occurred before the command was executed or that the exit code could not be determined. + return -1 } - return + return 0 } // Download the latest AnalyzerManager executable if not cached locally. diff --git a/jas/applicability/applicabilitymanager.go b/jas/applicability/applicabilitymanager.go index 5e346bea..c8725e8d 100644 --- a/jas/applicability/applicabilitymanager.go +++ b/jas/applicability/applicabilitymanager.go @@ -28,7 +28,6 @@ const ( type ApplicabilityScanType string type ApplicabilityScanManager struct { - applicabilityScanResults []*sarif.Run directDependenciesCves []string indirectDependenciesCves []string xrayResults []services.ScanResponse @@ -44,10 +43,6 @@ type ApplicabilityScanManager struct { // Checking if the scanned project is eligible for applicability scan. // Running the analyzer manager executable. // Parsing the analyzer manager results. -// Return values: -// map[string]string: A map containing the applicability result of each XRAY CVE. -// bool: true if the user is entitled to the applicability scan, false otherwise. -// error: An error object (if any). func RunApplicabilityScan(xrayResults []services.ScanResponse, directDependencies []string, scanner *jas.JasScanner, thirdPartyContextualAnalysis bool, scanType ApplicabilityScanType, module jfrogappsconfig.Module, threadId int) (results []*sarif.Run, err error) { var scannerTempDir string @@ -60,13 +55,13 @@ func RunApplicabilityScan(xrayResults []services.ScanResponse, directDependencie return } log.Info(clientutils.GetLogMsgPrefix(threadId, false) + "Running applicability scan...") - if err = applicabilityScanManager.scanner.Run(applicabilityScanManager, module); err != nil { - err = jas.ParseAnalyzerManagerError(jasutils.Applicability, err) + // Applicability scan does not produce violations. + if results, _, err = applicabilityScanManager.scanner.Run(applicabilityScanManager, module); err != nil { return } - results = applicabilityScanManager.applicabilityScanResults - if len(results) > 0 { - log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Found", sarifutils.GetRulesPropertyCount("applicability", "applicable", results...), "applicable cves") + applicableCveCount := sarifutils.GetRulesPropertyCount("applicability", "applicable", results...) + if applicableCveCount > 0 { + log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Found", applicableCveCount, "applicable cves") } return } @@ -74,7 +69,6 @@ func RunApplicabilityScan(xrayResults []services.ScanResponse, directDependencie func newApplicabilityScanManager(xrayScanResults []services.ScanResponse, directDependencies []string, scanner *jas.JasScanner, thirdPartyScan bool, scanType ApplicabilityScanType, scannerTempDir string) (manager *ApplicabilityScanManager) { directDependenciesCves, indirectDependenciesCves := extractDependenciesCvesFromScan(xrayScanResults, directDependencies) return &ApplicabilityScanManager{ - applicabilityScanResults: []*sarif.Run{}, directDependenciesCves: directDependenciesCves, indirectDependenciesCves: indirectDependenciesCves, xrayResults: xrayScanResults, @@ -128,19 +122,14 @@ func isDirectComponents(components []string, directDependencies []string) bool { return false } -func (asm *ApplicabilityScanManager) Run(module jfrogappsconfig.Module) (err error) { +func (asm *ApplicabilityScanManager) Run(module jfrogappsconfig.Module) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) { if err = asm.createConfigFile(module, asm.scanner.Exclusions...); err != nil { return } if err = asm.runAnalyzerManager(); err != nil { return } - workingDirResults, err := jas.ReadJasScanRunsFromFile(asm.resultsFileName, module.SourceRoot, applicabilityDocsUrlSuffix, asm.scanner.MinSeverity) - if err != nil { - return - } - asm.applicabilityScanResults = append(asm.applicabilityScanResults, workingDirResults...) - return + return jas.ReadJasScanRunsFromFile(asm.resultsFileName, module.SourceRoot, applicabilityDocsUrlSuffix, asm.scanner.MinSeverity) } func (asm *ApplicabilityScanManager) cvesExists() bool { diff --git a/jas/applicability/applicabilitymanager_test.go b/jas/applicability/applicabilitymanager_test.go index a26fb0c7..b12017cf 100644 --- a/jas/applicability/applicabilitymanager_test.go +++ b/jas/applicability/applicabilitymanager_test.go @@ -310,15 +310,14 @@ func TestParseResults_NewApplicabilityStatuses(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { applicabilityManager.resultsFileName = filepath.Join(jas.GetTestDataPath(), "applicability-scan", tc.fileName) - var err error - applicabilityManager.applicabilityScanResults, err = jas.ReadJasScanRunsFromFile(applicabilityManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, applicabilityDocsUrlSuffix, scanner.MinSeverity) - if assert.NoError(t, err) && assert.NotNil(t, applicabilityManager.applicabilityScanResults) { - assert.Len(t, applicabilityManager.applicabilityScanResults, 1) - assert.Len(t, applicabilityManager.applicabilityScanResults[0].Results, tc.expectedResults) + vulnerabilitiesResults, _, innerErr := jas.ReadJasScanRunsFromFile(applicabilityManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, applicabilityDocsUrlSuffix, scanner.MinSeverity) + if assert.NoError(t, innerErr) && assert.NotNil(t, vulnerabilitiesResults) { + assert.Len(t, vulnerabilitiesResults, 1) + assert.Len(t, vulnerabilitiesResults[0].Results, tc.expectedResults) if tc.name == "new applicability statuses" { - assert.Len(t, applicabilityManager.applicabilityScanResults[0].Tool.Driver.Rules, len(tc.expectedApplicabilityStatuses)) + assert.Len(t, vulnerabilitiesResults[0].Tool.Driver.Rules, len(tc.expectedApplicabilityStatuses)) for i, value := range tc.expectedApplicabilityStatuses { - assert.Equal(t, value, applicabilityManager.applicabilityScanResults[0].Tool.Driver.Rules[i].Properties["applicability"]) + assert.Equal(t, value, vulnerabilitiesResults[0].Tool.Driver.Rules[i].Properties["applicability"]) } } } diff --git a/jas/common.go b/jas/common.go index 8c060fd2..9ac16c2f 100644 --- a/jas/common.go +++ b/jas/common.go @@ -25,6 +25,7 @@ import ( "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray" "github.com/jfrog/jfrog-client-go/xray/services" + xscutils "github.com/jfrog/jfrog-client-go/xsc/services/utils" "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" "golang.org/x/exp/slices" @@ -117,22 +118,60 @@ func CreateJFrogAppsConfig(workingDirs []string) (*jfrogappsconfig.JFrogAppsConf } type ScannerCmd interface { - Run(module jfrogappsconfig.Module) (err error) + Run(module jfrogappsconfig.Module) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) } -func (a *JasScanner) Run(scannerCmd ScannerCmd, module jfrogappsconfig.Module) (err error) { +func (a *JasScanner) Run(scannerCmd ScannerCmd, module jfrogappsconfig.Module) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) { func() { - if err = scannerCmd.Run(module); err != nil { + if vulnerabilitiesSarifRuns, violationsSarifRuns, err = scannerCmd.Run(module); err != nil { return } }() return } -func ReadJasScanRunsFromFile(fileName, wd, informationUrlSuffix string, minSeverity severityutils.Severity) (sarifRuns []*sarif.Run, err error) { +func ReadJasScanRunsFromFile(fileName, wd, informationUrlSuffix string, minSeverity severityutils.Severity) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) { + violationFileName := fmt.Sprintf("%s_violations.sarif", strings.TrimSuffix(fileName, ".sarif")) + vulnFileExist, violationsFileExist, err := checkJasResultsFilesExist(fileName, violationFileName) + if err != nil { + return + } + if !vulnFileExist && !violationsFileExist { + err = fmt.Errorf("Analyzer-Manager did not generate results files at: %s", filepath.Base(fileName)) + return + } + if vulnFileExist { + vulnerabilitiesSarifRuns, err = readJasScanRunsFromFile(fileName, wd, informationUrlSuffix, minSeverity) + if err != nil { + return + } + } + if violationsFileExist { + violationsSarifRuns, err = readJasScanRunsFromFile(violationFileName, wd, informationUrlSuffix, minSeverity) + } + return +} + +func checkJasResultsFilesExist(vulnFileName, violationsFileName string) (vulnFileExist, violationsFileExist bool, err error) { + if vulnFileExist, err = fileutils.IsFileExists(vulnFileName, false); err != nil { + return + } + if violationsFileExist, err = fileutils.IsFileExists(violationsFileName, false); err != nil { + return + } + return +} + +func readJasScanRunsFromFile(fileName, wd, informationUrlSuffix string, minSeverity severityutils.Severity) (sarifRuns []*sarif.Run, err error) { if sarifRuns, err = sarifutils.ReadScanRunsFromFile(fileName); err != nil { return } + processSarifRuns(sarifRuns, wd, informationUrlSuffix, minSeverity) + return +} + +// This function processes the Sarif runs results: update invocations, fill missing information, exclude results and adding scores to rules +func processSarifRuns(sarifRuns []*sarif.Run, wd string, informationUrlSuffix string, minSeverity severityutils.Severity) { for _, sarifRun := range sarifRuns { // Jas reports has only one invocation // Set the actual working directory to the invocation, not the analyzerManager directory @@ -147,7 +186,6 @@ func ReadJasScanRunsFromFile(fileName, wd, informationUrlSuffix string, minSever sarifRun.Results = excludeMinSeverityResults(sarifRun.Results, minSeverity) addScoreToRunRules(sarifRun) } - return } func fillMissingRequiredDriverInformation(defaultJasInformationUri, defaultVersion string, run *sarif.Run) { @@ -252,7 +290,7 @@ var FakeBasicXrayResults = []services.ScanResponse{ func InitJasTest(t *testing.T) (*JasScanner, func()) { assert.NoError(t, DownloadAnalyzerManagerIfNeeded(0)) - scanner, err := CreateJasScanner(&FakeServerDetails, false, "", GetAnalyzerManagerXscEnvVars("")) + scanner, err := CreateJasScanner(&FakeServerDetails, false, "", GetAnalyzerManagerXscEnvVars("", "", "", []string{})) assert.NoError(t, err) return scanner, func() { assert.NoError(t, scanner.ScannerDirCleanupFunc()) @@ -336,8 +374,25 @@ func CheckForSecretValidation(xrayManager *xray.XrayServicesManager, xrayVersion return err == nil && isEnabled } -func GetAnalyzerManagerXscEnvVars(msi string, technologies ...techutils.Technology) map[string]string { +// Analyzer Manager expect the git repo url to be in the env vars in a specific way, this function will return the key for the git repo url +func GetGitRepoUrlKey(gitRepoHttpsCloneUrl string) string { + if gitRepoHttpsCloneUrl == "" { + return "" + } + return xscutils.GetGitRepoUrlKey(gitRepoHttpsCloneUrl) +} + +func GetAnalyzerManagerXscEnvVars(msi string, gitRepoUrl, projectKey string, watches []string, technologies ...techutils.Technology) map[string]string { envVars := map[string]string{utils.JfMsiEnvVariable: msi} + if gitRepoUrl != "" { + envVars[gitRepoEnvVariable] = gitRepoUrl + } + if projectKey != "" { + envVars[projectEnvVariable] = projectKey + } + if len(watches) > 0 { + envVars[watchesEnvVariable] = strings.Join(watches, ",") + } if len(technologies) != 1 { return envVars } diff --git a/jas/common_test.go b/jas/common_test.go index 594fc391..4a7c43fe 100644 --- a/jas/common_test.go +++ b/jas/common_test.go @@ -2,6 +2,7 @@ package jas import ( "os" + "path/filepath" "testing" "github.com/jfrog/jfrog-cli-core/v2/utils/config" @@ -11,8 +12,72 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/techutils" "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" + + coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" ) +func TestReadJasScanRunsFromFile(t *testing.T) { + dummyReport, err := sarifutils.NewReport() + assert.NoError(t, err) + dummyReport.AddRun(sarifutils.CreateRunWithDummyResults( + sarifutils.CreateResultWithOneLocation("file1", 0, 0, 0, 0, "snippet", "rule1", "info"), + sarifutils.CreateResultWithOneLocation("file2", 0, 0, 0, 0, "snippet", "rule1", "info"), + )) + + tests := []struct { + name string + generateVulnFile bool + generateVioFile bool + }{ + { + name: "Expect AM to generate vulnerabilities file", + generateVulnFile: true, + }, + { + name: "Expect AM to generate violation file", + generateVioFile: true, + }, + { + name: "Expect AM to generate both files", + generateVulnFile: true, + generateVioFile: true, + }, + { + // Expecting error if no files are generated. + name: "AM generate none - error", + }, + } + + for _, test := range tests { + tempDir, cleanUp := coreTests.CreateTempDirWithCallbackAndAssert(t) + defer cleanUp() + fileName := filepath.Join(tempDir, "results.sarif") + if test.generateVulnFile { + assert.NoError(t, dummyReport.WriteFile(fileName)) + } + if test.generateVioFile { + assert.NoError(t, dummyReport.WriteFile(filepath.Join(tempDir, "results_violations.sarif"))) + } + + vuln, vio, err := ReadJasScanRunsFromFile(fileName, "some-working-dir-of-project", "docs URL", "") + + // Expecting error if no files are generated. + if !test.generateVulnFile && !test.generateVioFile { + assert.Error(t, err) + assert.Empty(t, vuln) + assert.Empty(t, vio) + return + } + assert.NoError(t, err) + if test.generateVulnFile { + assert.NotEmpty(t, vuln) + } + if test.generateVioFile { + assert.NotEmpty(t, vio) + } + } +} + func TestExcludeSuppressResults(t *testing.T) { tests := []struct { name string @@ -207,6 +272,9 @@ func TestGetAnalyzerManagerXscEnvVars(t *testing.T) { tests := []struct { name string msi string + gitRepoUrl string + projectKey string + watches []string technologies []techutils.Technology expectedOutput map[string]string }{ @@ -232,10 +300,50 @@ func TestGetAnalyzerManagerXscEnvVars(t *testing.T) { technologies: []techutils.Technology{}, expectedOutput: map[string]string{utils.JfMsiEnvVariable: "msi"}, }, + { + name: "With git repo url", + msi: "msi", + gitRepoUrl: "gitRepoUrl", + technologies: []techutils.Technology{techutils.Npm}, + expectedOutput: map[string]string{ + JfPackageManagerEnvVariable: string(techutils.Npm), + JfLanguageEnvVariable: string(techutils.JavaScript), + utils.JfMsiEnvVariable: "msi", + gitRepoEnvVariable: "gitRepoUrl", + }, + }, + { + name: "With project key", + msi: "msi", + gitRepoUrl: "gitRepoUrl", + projectKey: "projectKey", + technologies: []techutils.Technology{techutils.Npm}, + expectedOutput: map[string]string{ + JfPackageManagerEnvVariable: string(techutils.Npm), + JfLanguageEnvVariable: string(techutils.JavaScript), + utils.JfMsiEnvVariable: "msi", + gitRepoEnvVariable: "gitRepoUrl", + projectEnvVariable: "projectKey", + }, + }, + { + name: "With watches", + msi: "msi", + gitRepoUrl: "gitRepoUrl", + watches: []string{"watch1", "watch2"}, + technologies: []techutils.Technology{techutils.Npm}, + expectedOutput: map[string]string{ + JfPackageManagerEnvVariable: string(techutils.Npm), + JfLanguageEnvVariable: string(techutils.JavaScript), + utils.JfMsiEnvVariable: "msi", + gitRepoEnvVariable: "gitRepoUrl", + watchesEnvVariable: "watch1,watch2", + }, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expectedOutput, GetAnalyzerManagerXscEnvVars(test.msi, test.technologies...)) + assert.Equal(t, test.expectedOutput, GetAnalyzerManagerXscEnvVars(test.msi, test.gitRepoUrl, test.projectKey, test.watches, test.technologies...)) }) } } diff --git a/jas/iac/iacscanner.go b/jas/iac/iacscanner.go index 5a139d37..89b3820f 100644 --- a/jas/iac/iacscanner.go +++ b/jas/iac/iacscanner.go @@ -5,6 +5,7 @@ import ( jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-security/jas" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" "github.com/jfrog/jfrog-cli-security/utils/jasutils" clientutils "github.com/jfrog/jfrog-client-go/utils" @@ -20,59 +21,44 @@ const ( ) type IacScanManager struct { - iacScannerResults []*sarif.Run - scanner *jas.JasScanner - configFileName string - resultsFileName string + scanner *jas.JasScanner + configFileName string + resultsFileName string } // The getIacScanResults function runs the iac scan flow, which includes the following steps: // Creating an IacScanManager object. // Running the analyzer manager executable. // Parsing the analyzer manager results. -// Return values: -// []utils.SourceCodeScanResult: a list of the iac violations that were found. -// bool: true if the user is entitled to iac scan, false otherwise. -// error: An error object (if any). -func RunIacScan(scanner *jas.JasScanner, module jfrogappsconfig.Module, threadId int) (results []*sarif.Run, err error) { +func RunIacScan(scanner *jas.JasScanner, module jfrogappsconfig.Module, threadId int) (vulnerabilitiesResults []*sarif.Run, violationsResults []*sarif.Run, err error) { var scannerTempDir string if scannerTempDir, err = jas.CreateScannerTempDirectory(scanner, jasutils.IaC.String()); err != nil { return } iacScanManager := newIacScanManager(scanner, scannerTempDir) log.Info(clientutils.GetLogMsgPrefix(threadId, false) + "Running IaC scan...") - if err = iacScanManager.scanner.Run(iacScanManager, module); err != nil { - err = jas.ParseAnalyzerManagerError(jasutils.IaC, err) + if vulnerabilitiesResults, violationsResults, err = iacScanManager.scanner.Run(iacScanManager, module); err != nil { return } - results = iacScanManager.iacScannerResults - if len(results) > 0 { - log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Found", sarifutils.GetResultsLocationCount(iacScanManager.iacScannerResults...), "IaC vulnerabilities") - } + log.Info(utils.GetScanFindingsLog(utils.IacScan, sarifutils.GetResultsLocationCount(vulnerabilitiesResults...), sarifutils.GetResultsLocationCount(violationsResults...), threadId)) return } func newIacScanManager(scanner *jas.JasScanner, scannerTempDir string) (manager *IacScanManager) { return &IacScanManager{ - iacScannerResults: []*sarif.Run{}, - scanner: scanner, - configFileName: filepath.Join(scannerTempDir, "config.yaml"), - resultsFileName: filepath.Join(scannerTempDir, "results.sarif")} + scanner: scanner, + configFileName: filepath.Join(scannerTempDir, "config.yaml"), + resultsFileName: filepath.Join(scannerTempDir, "results.sarif")} } -func (iac *IacScanManager) Run(module jfrogappsconfig.Module) (err error) { +func (iac *IacScanManager) Run(module jfrogappsconfig.Module) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) { if err = iac.createConfigFile(module, iac.scanner.Exclusions...); err != nil { return } if err = iac.runAnalyzerManager(); err != nil { return } - workingDirResults, err := jas.ReadJasScanRunsFromFile(iac.resultsFileName, module.SourceRoot, iacDocsUrlSuffix, iac.scanner.MinSeverity) - if err != nil { - return - } - iac.iacScannerResults = append(iac.iacScannerResults, workingDirResults...) - return + return jas.ReadJasScanRunsFromFile(iac.resultsFileName, module.SourceRoot, iacDocsUrlSuffix, iac.scanner.MinSeverity) } type iacScanConfig struct { diff --git a/jas/iac/iacscanner_test.go b/jas/iac/iacscanner_test.go index dd7b4348..b4184862 100644 --- a/jas/iac/iacscanner_test.go +++ b/jas/iac/iacscanner_test.go @@ -11,7 +11,9 @@ import ( jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-security/jas" + biutils "github.com/jfrog/build-info-go/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" "github.com/stretchr/testify/assert" ) @@ -68,11 +70,12 @@ func TestIacParseResults_EmptyResults(t *testing.T) { iacScanManager.resultsFileName = filepath.Join(jas.GetTestDataPath(), "iac-scan", "no-violations.sarif") // Act - iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, iacDocsUrlSuffix, scanner.MinSeverity) - if assert.NoError(t, err) && assert.NotNil(t, iacScanManager.iacScannerResults) { - assert.Len(t, iacScanManager.iacScannerResults, 1) - assert.Empty(t, iacScanManager.iacScannerResults[0].Results) + vulnerabilitiesResults, violationResults, err := jas.ReadJasScanRunsFromFile(iacScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, iacDocsUrlSuffix, scanner.MinSeverity) + if assert.NoError(t, err) && assert.NotNil(t, vulnerabilitiesResults) { + assert.Len(t, vulnerabilitiesResults, 1) + assert.Empty(t, vulnerabilitiesResults[0].Results) } + assert.Empty(t, violationResults) } func TestIacParseResults_ResultsContainIacViolations(t *testing.T) { @@ -80,14 +83,22 @@ func TestIacParseResults_ResultsContainIacViolations(t *testing.T) { defer cleanUp() jfrogAppsConfigForTest, err := jas.CreateJFrogAppsConfig([]string{}) assert.NoError(t, err) + // Arrange + tempDirPath, createTempDirCallback := coreTests.CreateTempDirWithCallbackAndAssert(t) + defer createTempDirCallback() iacScanManager := newIacScanManager(scanner, "temoDirPath") - iacScanManager.resultsFileName = filepath.Join(jas.GetTestDataPath(), "iac-scan", "contains-iac-violations.sarif") + assert.NoError(t, biutils.CopyDir(filepath.Join(jas.GetTestDataPath(), "iac-scan"), tempDirPath, true, nil)) + iacScanManager.resultsFileName = filepath.Join(tempDirPath, "contains-iac-issues.sarif") // Act - iacScanManager.iacScannerResults, err = jas.ReadJasScanRunsFromFile(iacScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, iacDocsUrlSuffix, scanner.MinSeverity) - if assert.NoError(t, err) && assert.NotNil(t, iacScanManager.iacScannerResults) { - assert.Len(t, iacScanManager.iacScannerResults, 1) - assert.Len(t, iacScanManager.iacScannerResults[0].Results, 4) + vulnerabilitiesResults, violationResults, err := jas.ReadJasScanRunsFromFile(iacScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, iacDocsUrlSuffix, scanner.MinSeverity) + if assert.NoError(t, err) && assert.NotNil(t, vulnerabilitiesResults) { + assert.Len(t, vulnerabilitiesResults, 1) + assert.Len(t, vulnerabilitiesResults[0].Results, 4) + } + if assert.NotNil(t, violationResults) { + assert.Len(t, violationResults, 1) + assert.Len(t, violationResults[0].Results, 1) } } diff --git a/jas/runner/jasrunner.go b/jas/runner/jasrunner.go index cf36a70e..21f1d543 100644 --- a/jas/runner/jasrunner.go +++ b/jas/runner/jasrunner.go @@ -31,7 +31,7 @@ type JasRunnerParams struct { ConfigProfile *services.ConfigProfile AllowPartialResults bool - ScansToPreform []utils.SubScanType + ScansToPerform []utils.SubScanType // Secret scan flags SecretsScanType secrets.SecretsScanType @@ -80,7 +80,7 @@ func addJasScanTaskForModuleIfNeeded(params JasRunnerParams, subScan utils.SubSc if jasType == "" { return fmt.Errorf("failed to determine Jas scan type for %s", subScan) } - if len(params.ScansToPreform) > 0 && !slices.Contains(params.ScansToPreform, subScan) { + if len(params.ScansToPerform) > 0 && !slices.Contains(params.ScansToPerform, subScan) { log.Debug(fmt.Sprintf("Skipping %s scan as requested by input...", subScan)) return } @@ -133,15 +133,15 @@ func runSecretsScan(securityParallelRunner *utils.SecurityParallelRunner, scanne defer func() { securityParallelRunner.JasScannersWg.Done() }() - results, err := secrets.RunSecretsScan(scanner, secretsScanType, module, threadId) - if err != nil { - return fmt.Errorf("%s%s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) - } + vulnerabilitiesResults, violationsResults, err := secrets.RunSecretsScan(scanner, secretsScanType, module, threadId) securityParallelRunner.ResultsMu.Lock() defer securityParallelRunner.ResultsMu.Unlock() - extendedScanResults.SecretsScanResults = append(extendedScanResults.SecretsScanResults, results...) - err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.Secrets) - return + // We first add the scan results and only then check for errors, so we can store the exit code in order to report it in the end + extendedScanResults.AddJasScanResults(jasutils.Secrets, vulnerabilitiesResults, violationsResults, jas.GetAnalyzerManagerExitCode(err)) + if err = jas.ParseAnalyzerManagerError(jasutils.Secrets, err); err != nil { + return fmt.Errorf("%s%s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) + } + return dumpSarifRunToFileIfNeeded(vulnerabilitiesResults, scansOutputDir, jasutils.Secrets) } } @@ -151,15 +151,15 @@ func runIacScan(securityParallelRunner *utils.SecurityParallelRunner, scanner *j defer func() { securityParallelRunner.JasScannersWg.Done() }() - results, err := iac.RunIacScan(scanner, module, threadId) - if err != nil { - return fmt.Errorf("%s %s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) - } + vulnerabilitiesResults, violationsResults, err := iac.RunIacScan(scanner, module, threadId) securityParallelRunner.ResultsMu.Lock() defer securityParallelRunner.ResultsMu.Unlock() - extendedScanResults.IacScanResults = append(extendedScanResults.IacScanResults, results...) - err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.IaC) - return + // We first add the scan results and only then check for errors, so we can store the exit code in order to report it in the end + extendedScanResults.AddJasScanResults(jasutils.IaC, vulnerabilitiesResults, violationsResults, jas.GetAnalyzerManagerExitCode(err)) + if err = jas.ParseAnalyzerManagerError(jasutils.IaC, err); err != nil { + return fmt.Errorf("%s%s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) + } + return dumpSarifRunToFileIfNeeded(vulnerabilitiesResults, scansOutputDir, jasutils.IaC) } } @@ -169,15 +169,15 @@ func runSastScan(securityParallelRunner *utils.SecurityParallelRunner, scanner * defer func() { securityParallelRunner.JasScannersWg.Done() }() - results, err := sast.RunSastScan(scanner, module, signedDescriptions, threadId) - if err != nil { - return fmt.Errorf("%s %s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) - } + vulnerabilitiesResults, violationsResults, err := sast.RunSastScan(scanner, module, signedDescriptions, threadId) securityParallelRunner.ResultsMu.Lock() defer securityParallelRunner.ResultsMu.Unlock() - extendedScanResults.SastScanResults = append(extendedScanResults.SastScanResults, results...) - err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.Sast) - return + // We first add the scan results and only then check for errors, so we can store the exit code in order to report it in the end + extendedScanResults.AddJasScanResults(jasutils.Sast, vulnerabilitiesResults, violationsResults, jas.GetAnalyzerManagerExitCode(err)) + if err = jas.ParseAnalyzerManagerError(jasutils.Sast, err); err != nil { + return fmt.Errorf("%s%s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) + } + return dumpSarifRunToFileIfNeeded(vulnerabilitiesResults, scansOutputDir, jasutils.Sast) } } @@ -189,15 +189,15 @@ func runContextualScan(securityParallelRunner *utils.SecurityParallelRunner, sca }() // Wait for sca scans to complete before running contextual scan securityParallelRunner.ScaScansWg.Wait() - results, err := applicability.RunApplicabilityScan(scanResults.GetScaScansXrayResults(), *directDependencies, scanner, thirdPartyApplicabilityScan, scanType, module, threadId) - if err != nil { - return fmt.Errorf("%s %s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) - } + caScanResults, err := applicability.RunApplicabilityScan(scanResults.GetScaScansXrayResults(), *directDependencies, scanner, thirdPartyApplicabilityScan, scanType, module, threadId) securityParallelRunner.ResultsMu.Lock() defer securityParallelRunner.ResultsMu.Unlock() - scanResults.JasResults.ApplicabilityScanResults = append(scanResults.JasResults.ApplicabilityScanResults, results...) - err = dumpSarifRunToFileIfNeeded(results, scansOutputDir, jasutils.Applicability) - return + // We first add the scan results and only then check for errors, so we can store the exit code in order to report it in the end + scanResults.JasResults.NewApplicabilityScanResults(jas.GetAnalyzerManagerExitCode(err), caScanResults...) + if err = jas.ParseAnalyzerManagerError(jasutils.Applicability, err); err != nil { + return fmt.Errorf("%s%s", clientutils.GetLogMsgPrefix(threadId, false), err.Error()) + } + return dumpSarifRunToFileIfNeeded(caScanResults, scansOutputDir, jasutils.Applicability) } } diff --git a/jas/runner/jasrunner_test.go b/jas/runner/jasrunner_test.go index 22b906fa..42a7f699 100644 --- a/jas/runner/jasrunner_test.go +++ b/jas/runner/jasrunner_test.go @@ -11,6 +11,7 @@ import ( "github.com/jfrog/jfrog-cli-security/jas/applicability" "github.com/jfrog/jfrog-cli-security/jas/secrets" "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/techutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" @@ -27,7 +28,7 @@ func TestJasRunner_AnalyzerManagerNotExist(t *testing.T) { defer func() { assert.NoError(t, os.Unsetenv(coreutils.HomeDir)) }() - scanner, err := jas.CreateJasScanner(&jas.FakeServerDetails, false, "", jas.GetAnalyzerManagerXscEnvVars("")) + scanner, err := jas.CreateJasScanner(&jas.FakeServerDetails, false, "", jas.GetAnalyzerManagerXscEnvVars("", "", "", []string{})) assert.NoError(t, err) if scanner.AnalyzerManager.AnalyzerManagerFullPath, err = jas.GetAnalyzerManagerExecutable(); err != nil { return @@ -42,15 +43,15 @@ func TestJasRunner(t *testing.T) { securityParallelRunnerForTest := utils.CreateSecurityParallelRunner(cliutils.Threads) targetResults := results.NewCommandResults(utils.SourceCode).SetEntitledForJas(true).SetSecretValidation(true).NewScanResults(results.ScanTarget{Target: "target", Technology: techutils.Pip}) - jasScanner, err := jas.CreateJasScanner(&jas.FakeServerDetails, false, "", jas.GetAnalyzerManagerXscEnvVars("", targetResults.GetTechnologies()...)) + jasScanner, err := jas.CreateJasScanner(&jas.FakeServerDetails, false, "", jas.GetAnalyzerManagerXscEnvVars("", "", "", []string{}, targetResults.GetTechnologies()...)) assert.NoError(t, err) - targetResults.NewScaScanResults(jas.FakeBasicXrayResults[0]) + targetResults.NewScaScanResults(0, jas.FakeBasicXrayResults[0]) testParams := JasRunnerParams{ Runner: securityParallelRunnerForTest, Scanner: jasScanner, ScanResults: targetResults, - ScansToPreform: utils.GetAllSupportedScans(), + ScansToPerform: utils.GetAllSupportedScans(), ApplicableScanType: applicability.ApplicabilityScannerType, SecretsScanType: secrets.SecretsScannerType, DirectDependencies: &[]string{"issueId_1_direct_dependency", "issueId_2_direct_dependency"}, @@ -62,8 +63,8 @@ func TestJasRunner_AnalyzerManagerReturnsError(t *testing.T) { assert.NoError(t, jas.DownloadAnalyzerManagerIfNeeded(0)) jfrogAppsConfigForTest, _ := jas.CreateJFrogAppsConfig(nil) - scanner, _ := jas.CreateJasScanner(&jas.FakeServerDetails, false, "", jas.GetAnalyzerManagerXscEnvVars("")) + scanner, _ := jas.CreateJasScanner(&jas.FakeServerDetails, false, "", jas.GetAnalyzerManagerXscEnvVars("", "", "", []string{})) _, err := applicability.RunApplicabilityScan(jas.FakeBasicXrayResults, []string{"issueId_2_direct_dependency", "issueId_1_direct_dependency"}, scanner, false, applicability.ApplicabilityScannerType, jfrogAppsConfigForTest.Modules[0], 0) // Expect error: - assert.ErrorContains(t, err, "failed to run Applicability scan") + assert.ErrorContains(t, jas.ParseAnalyzerManagerError(jasutils.Applicability, err), "failed to run Applicability scan") } diff --git a/jas/sast/sastscanner.go b/jas/sast/sastscanner.go index 5df23e43..f3f73191 100644 --- a/jas/sast/sastscanner.go +++ b/jas/sast/sastscanner.go @@ -6,6 +6,7 @@ import ( jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-security/jas" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" "github.com/jfrog/jfrog-cli-security/utils/jasutils" clientutils "github.com/jfrog/jfrog-client-go/utils" @@ -21,53 +22,47 @@ const ( ) type SastScanManager struct { - sastScannerResults []*sarif.Run scanner *jas.JasScanner signedDescriptions bool configFileName string resultsFileName string } -func RunSastScan(scanner *jas.JasScanner, module jfrogappsconfig.Module, signedDescriptions bool, threadId int) (results []*sarif.Run, err error) { +func RunSastScan(scanner *jas.JasScanner, module jfrogappsconfig.Module, signedDescriptions bool, threadId int) (vulnerabilitiesResults []*sarif.Run, violationsResults []*sarif.Run, err error) { var scannerTempDir string if scannerTempDir, err = jas.CreateScannerTempDirectory(scanner, jasutils.Sast.String()); err != nil { return } sastScanManager := newSastScanManager(scanner, scannerTempDir, signedDescriptions) log.Info(clientutils.GetLogMsgPrefix(threadId, false) + "Running SAST scan...") - if err = sastScanManager.scanner.Run(sastScanManager, module); err != nil { - err = jas.ParseAnalyzerManagerError(jasutils.Sast, err) + if vulnerabilitiesResults, violationsResults, err = sastScanManager.scanner.Run(sastScanManager, module); err != nil { return } - results = sastScanManager.sastScannerResults - if len(results) > 0 { - log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Found", sarifutils.GetResultsLocationCount(sastScanManager.sastScannerResults...), "SAST vulnerabilities") - } + log.Info(utils.GetScanFindingsLog(utils.SastScan, sarifutils.GetResultsLocationCount(vulnerabilitiesResults...), sarifutils.GetResultsLocationCount(violationsResults...), threadId)) return } func newSastScanManager(scanner *jas.JasScanner, scannerTempDir string, signedDescriptions bool) (manager *SastScanManager) { return &SastScanManager{ - sastScannerResults: []*sarif.Run{}, scanner: scanner, signedDescriptions: signedDescriptions, configFileName: filepath.Join(scannerTempDir, "config.yaml"), resultsFileName: filepath.Join(scannerTempDir, "results.sarif")} } -func (ssm *SastScanManager) Run(module jfrogappsconfig.Module) (err error) { +func (ssm *SastScanManager) Run(module jfrogappsconfig.Module) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) { if err = ssm.createConfigFile(module, ssm.signedDescriptions, ssm.scanner.Exclusions...); err != nil { return } if err = ssm.runAnalyzerManager(filepath.Dir(ssm.scanner.AnalyzerManager.AnalyzerManagerFullPath)); err != nil { return } - workingDirRuns, err := jas.ReadJasScanRunsFromFile(ssm.resultsFileName, module.SourceRoot, sastDocsUrlSuffix, ssm.scanner.MinSeverity) + vulnerabilitiesSarifRuns, violationsSarifRuns, err = jas.ReadJasScanRunsFromFile(ssm.resultsFileName, module.SourceRoot, sastDocsUrlSuffix, ssm.scanner.MinSeverity) if err != nil { return } - groupResultsByLocation(workingDirRuns) - ssm.sastScannerResults = append(ssm.sastScannerResults, workingDirRuns...) + groupResultsByLocation(vulnerabilitiesSarifRuns) + groupResultsByLocation(violationsSarifRuns) return } diff --git a/jas/sast/sastscanner_test.go b/jas/sast/sastscanner_test.go index fdcc41ba..33c94f77 100644 --- a/jas/sast/sastscanner_test.go +++ b/jas/sast/sastscanner_test.go @@ -40,15 +40,15 @@ func TestSastParseResults_EmptyResults(t *testing.T) { sastScanManager.resultsFileName = filepath.Join(jas.GetTestDataPath(), "sast-scan", "no-violations.sarif") // Act - sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, sastDocsUrlSuffix, scanner.MinSeverity) + vulnerabilitiesResults, _, err := jas.ReadJasScanRunsFromFile(sastScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, sastDocsUrlSuffix, scanner.MinSeverity) // Assert - if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { - assert.Len(t, sastScanManager.sastScannerResults, 1) - assert.Empty(t, sastScanManager.sastScannerResults[0].Results) - groupResultsByLocation(sastScanManager.sastScannerResults) - assert.Len(t, sastScanManager.sastScannerResults, 1) - assert.Empty(t, sastScanManager.sastScannerResults[0].Results) + if assert.NoError(t, err) && assert.NotNil(t, vulnerabilitiesResults) { + assert.Len(t, vulnerabilitiesResults, 1) + assert.Empty(t, vulnerabilitiesResults[0].Results) + groupResultsByLocation(vulnerabilitiesResults) + assert.Len(t, vulnerabilitiesResults, 1) + assert.Empty(t, vulnerabilitiesResults[0].Results) } } @@ -62,15 +62,15 @@ func TestSastParseResults_ResultsContainIacViolations(t *testing.T) { sastScanManager.resultsFileName = filepath.Join(jas.GetTestDataPath(), "sast-scan", "contains-sast-violations.sarif") // Act - sastScanManager.sastScannerResults, err = jas.ReadJasScanRunsFromFile(sastScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, sastDocsUrlSuffix, scanner.MinSeverity) + vulnerabilitiesResults, _, err := jas.ReadJasScanRunsFromFile(sastScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, sastDocsUrlSuffix, scanner.MinSeverity) // Assert - if assert.NoError(t, err) && assert.NotNil(t, sastScanManager.sastScannerResults) { - assert.Len(t, sastScanManager.sastScannerResults, 1) - assert.NotEmpty(t, sastScanManager.sastScannerResults[0].Results) - groupResultsByLocation(sastScanManager.sastScannerResults) + if assert.NoError(t, err) && assert.NotNil(t, vulnerabilitiesResults) { + assert.Len(t, vulnerabilitiesResults, 1) + assert.NotEmpty(t, vulnerabilitiesResults[0].Results) + groupResultsByLocation(vulnerabilitiesResults) // File has 4 results, 2 of them at the same location different codeFlow - assert.Len(t, sastScanManager.sastScannerResults[0].Results, 3) + assert.Len(t, vulnerabilitiesResults[0].Results, 3) } } diff --git a/jas/secrets/secretsscanner.go b/jas/secrets/secretsscanner.go index 7a0e5290..e589db78 100644 --- a/jas/secrets/secretsscanner.go +++ b/jas/secrets/secretsscanner.go @@ -8,6 +8,7 @@ import ( jfrogappsconfig "github.com/jfrog/jfrog-apps-config/go" "github.com/jfrog/jfrog-cli-security/jas" + "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -25,61 +26,47 @@ const ( type SecretsScanType string type SecretScanManager struct { - secretsScannerResults []*sarif.Run - scanner *jas.JasScanner - scanType SecretsScanType - configFileName string - resultsFileName string + scanner *jas.JasScanner + scanType SecretsScanType + configFileName string + resultsFileName string } // The getSecretsScanResults function runs the secrets scan flow, which includes the following steps: // Creating an SecretScanManager object. // Running the analyzer manager executable. // Parsing the analyzer manager results. -// Return values: -// []utils.IacOrSecretResult: a list of the secrets that were found. -// error: An error object (if any). -func RunSecretsScan(scanner *jas.JasScanner, scanType SecretsScanType, module jfrogappsconfig.Module, threadId int) (results []*sarif.Run, err error) { +func RunSecretsScan(scanner *jas.JasScanner, scanType SecretsScanType, module jfrogappsconfig.Module, threadId int) (vulnerabilitiesResults []*sarif.Run, violationsResults []*sarif.Run, err error) { var scannerTempDir string if scannerTempDir, err = jas.CreateScannerTempDirectory(scanner, jasutils.Secrets.String()); err != nil { return } secretScanManager := newSecretsScanManager(scanner, scanType, scannerTempDir) log.Info(clientutils.GetLogMsgPrefix(threadId, false) + "Running secrets scan...") - if err = secretScanManager.scanner.Run(secretScanManager, module); err != nil { - err = jas.ParseAnalyzerManagerError(jasutils.Secrets, err) + if vulnerabilitiesResults, violationsResults, err = secretScanManager.scanner.Run(secretScanManager, module); err != nil { return } - results = secretScanManager.secretsScannerResults - if len(results) > 0 { - log.Info(clientutils.GetLogMsgPrefix(threadId, false)+"Found", sarifutils.GetResultsLocationCount(results...), "secrets vulnerabilities") - } + log.Info(utils.GetScanFindingsLog(utils.SecretsScan, sarifutils.GetResultsLocationCount(vulnerabilitiesResults...), sarifutils.GetResultsLocationCount(violationsResults...), threadId)) return } func newSecretsScanManager(scanner *jas.JasScanner, scanType SecretsScanType, scannerTempDir string) (manager *SecretScanManager) { return &SecretScanManager{ - secretsScannerResults: []*sarif.Run{}, - scanner: scanner, - scanType: scanType, - configFileName: filepath.Join(scannerTempDir, "config.yaml"), - resultsFileName: filepath.Join(scannerTempDir, "results.sarif"), + scanner: scanner, + scanType: scanType, + configFileName: filepath.Join(scannerTempDir, "config.yaml"), + resultsFileName: filepath.Join(scannerTempDir, "results.sarif"), } } -func (ssm *SecretScanManager) Run(module jfrogappsconfig.Module) (err error) { +func (ssm *SecretScanManager) Run(module jfrogappsconfig.Module) (vulnerabilitiesSarifRuns []*sarif.Run, violationsSarifRuns []*sarif.Run, err error) { if err = ssm.createConfigFile(module, ssm.scanner.Exclusions...); err != nil { return } if err = ssm.runAnalyzerManager(); err != nil { return } - workingDirRuns, err := jas.ReadJasScanRunsFromFile(ssm.resultsFileName, module.SourceRoot, secretsDocsUrlSuffix, ssm.scanner.MinSeverity) - if err != nil { - return - } - ssm.secretsScannerResults = append(ssm.secretsScannerResults, processSecretScanRuns(workingDirRuns)...) - return + return jas.ReadJasScanRunsFromFile(ssm.resultsFileName, module.SourceRoot, secretsDocsUrlSuffix, ssm.scanner.MinSeverity) } type secretsScanConfig struct { diff --git a/jas/secrets/secretsscanner_test.go b/jas/secrets/secretsscanner_test.go index b98c172e..99b68cb8 100644 --- a/jas/secrets/secretsscanner_test.go +++ b/jas/secrets/secretsscanner_test.go @@ -74,15 +74,15 @@ func TestParseResults_EmptyResults(t *testing.T) { secretScanManager.resultsFileName = filepath.Join(jas.GetTestDataPath(), "secrets-scan", "no-secrets.sarif") // Act - secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, secretsDocsUrlSuffix, scanner.MinSeverity) + vulnerabilitiesResults, _, err := jas.ReadJasScanRunsFromFile(secretScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, secretsDocsUrlSuffix, scanner.MinSeverity) // Assert - if assert.NoError(t, err) && assert.NotNil(t, secretScanManager.secretsScannerResults) { - assert.Len(t, secretScanManager.secretsScannerResults, 1) - assert.Empty(t, secretScanManager.secretsScannerResults[0].Results) - secretScanManager.secretsScannerResults = processSecretScanRuns(secretScanManager.secretsScannerResults) - assert.Len(t, secretScanManager.secretsScannerResults, 1) - assert.Empty(t, secretScanManager.secretsScannerResults[0].Results) + if assert.NoError(t, err) && assert.NotNil(t, vulnerabilitiesResults) { + assert.Len(t, vulnerabilitiesResults, 1) + assert.Empty(t, vulnerabilitiesResults[0].Results) + vulnerabilitiesResults = processSecretScanRuns(vulnerabilitiesResults) + assert.Len(t, vulnerabilitiesResults, 1) + assert.Empty(t, vulnerabilitiesResults[0].Results) } } @@ -98,15 +98,15 @@ func TestParseResults_ResultsContainSecrets(t *testing.T) { secretScanManager.resultsFileName = filepath.Join(jas.GetTestDataPath(), "secrets-scan", "contain-secrets.sarif") // Act - secretScanManager.secretsScannerResults, err = jas.ReadJasScanRunsFromFile(secretScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, secretsDocsUrlSuffix, severityutils.Medium) + vulnerabilitiesResults, _, err := jas.ReadJasScanRunsFromFile(secretScanManager.resultsFileName, jfrogAppsConfigForTest.Modules[0].SourceRoot, secretsDocsUrlSuffix, severityutils.Medium) // Assert - if assert.NoError(t, err) && assert.NotNil(t, secretScanManager.secretsScannerResults) { - assert.Len(t, secretScanManager.secretsScannerResults, 1) - assert.NotEmpty(t, secretScanManager.secretsScannerResults[0].Results) - secretScanManager.secretsScannerResults = processSecretScanRuns(secretScanManager.secretsScannerResults) - assert.Len(t, secretScanManager.secretsScannerResults, 1) - assert.Len(t, secretScanManager.secretsScannerResults[0].Results, 6) + if assert.NoError(t, err) && assert.NotNil(t, vulnerabilitiesResults) { + assert.Len(t, vulnerabilitiesResults, 1) + assert.NotEmpty(t, vulnerabilitiesResults[0].Results) + vulnerabilitiesResults = processSecretScanRuns(vulnerabilitiesResults) + assert.Len(t, vulnerabilitiesResults, 1) + assert.Len(t, vulnerabilitiesResults[0].Results, 6) } assert.NoError(t, err) @@ -117,10 +117,10 @@ func TestGetSecretsScanResults_AnalyzerManagerReturnsError(t *testing.T) { defer cleanUp() jfrogAppsConfigForTest, err := jas.CreateJFrogAppsConfig([]string{}) assert.NoError(t, err) - scanResults, err := RunSecretsScan(scanner, SecretsScannerType, jfrogAppsConfigForTest.Modules[0], 0) + vulnerabilitiesResults, _, err := RunSecretsScan(scanner, SecretsScannerType, jfrogAppsConfigForTest.Modules[0], 0) assert.Error(t, err) - assert.ErrorContains(t, err, "failed to run Secrets scan") - assert.Nil(t, scanResults) + assert.ErrorContains(t, jas.ParseAnalyzerManagerError(jasutils.Secrets, err), "failed to run Secrets scan") + assert.Nil(t, vulnerabilitiesResults) } func TestHideSecret(t *testing.T) { diff --git a/scans_test.go b/scans_test.go index a389f484..7c36abd4 100644 --- a/scans_test.go +++ b/scans_test.go @@ -43,20 +43,17 @@ import ( func TestXrayBinaryScanJson(t *testing.T) { integration.InitScanTest(t, scangraph.GraphScanMinXrayVersion) - output := testXrayBinaryScan(t, string(format.Json), false) + output := testXrayBinaryScan(t, string(format.Json), "", "") validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } func TestXrayBinaryScanSimpleJson(t *testing.T) { integration.InitScanTest(t, scangraph.GraphScanMinXrayVersion) - output := testXrayBinaryScan(t, string(format.SimpleJson), true) + output := testXrayBinaryScan(t, string(format.SimpleJson), "xray-scan-binary-policy", "scan-binary-watch") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - SecurityViolations: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1, Violations: 1}, }) } @@ -64,10 +61,9 @@ func TestXrayBinaryScanJsonWithProgress(t *testing.T) { integration.InitScanTest(t, scangraph.GraphScanMinXrayVersion) callback := commonTests.MockProgressInitialization() defer callback() - output := testXrayBinaryScan(t, string(format.Json), false) + output := testXrayBinaryScan(t, string(format.Json), "", "") validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } @@ -75,19 +71,17 @@ func TestXrayBinaryScanSimpleJsonWithProgress(t *testing.T) { integration.InitScanTest(t, scangraph.GraphScanMinXrayVersion) callback := commonTests.MockProgressInitialization() defer callback() - output := testXrayBinaryScan(t, string(format.SimpleJson), true) + output := testXrayBinaryScan(t, string(format.SimpleJson), "xray-scan-binary-progress-policy", "scan-binary-progress-watch") validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - SecurityViolations: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1, Violations: 1}, }) } -func testXrayBinaryScan(t *testing.T, format string, withViolation bool) string { +func testXrayBinaryScan(t *testing.T, format, policyName, watchName string) string { binariesPath := filepath.Join(filepath.FromSlash(securityTests.GetTestResourcesPath()), "projects", "binaries", "*") args := []string{"scan", binariesPath, "--licenses", "--format=" + format} - if withViolation { - watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "audit-policy", "audit-watch", xrayUtils.High) + if policyName != "" && watchName != "" { + watchName, deleteWatch := securityTestUtils.CreateTestPolicyAndWatch(t, "xray-scan-binary-policy", "scan-binary-watch", xrayUtils.High) defer deleteWatch() // Include violations and vulnerabilities args = append(args, "--watches="+watchName, "--vuln") @@ -110,8 +104,7 @@ func TestXrayBinaryScanWithBypassArchiveLimits(t *testing.T) { scanArgs = append(scanArgs, "--bypass-archive-limits") output := securityTests.PlatformCli.RunCliCmdWithOutput(t, scanArgs...) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } @@ -137,7 +130,7 @@ func TestDockerScan(t *testing.T) { testCli, cleanup := integration.InitNativeDockerTest(t) defer cleanup() - watchName, deleteWatch := securityTestUtils.CreateTestWatch(t, "docker-policy", "docker-watch", xrayUtils.Low) + watchName, deleteWatch := securityTestUtils.CreateTestPolicyAndWatch(t, "docker-policy", "docker-watch", xrayUtils.Low) defer deleteWatch() imagesToScan := []string{ @@ -172,9 +165,11 @@ func runDockerScan(t *testing.T, testCli *coreTests.JfrogCli, imageName, watchNa output := testCli.WithoutCredentials().RunCliCmdWithOutput(t, cmdArgs...) if assert.NotEmpty(t, output) { if validateSecrets { - validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{Inactive: minInactives}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ + Vulnerabilities: &validations.VulnerabilityCount{ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Inactive: minInactives}}, + }) } else { - validations.VerifyJsonResults(t, output, validations.ValidationParams{Vulnerabilities: minVulnerabilities, Licenses: minLicenses}) + validations.VerifyJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Vulnerabilities: minVulnerabilities, Licenses: minLicenses}}) } } // Run docker scan on image with watch @@ -184,7 +179,7 @@ func runDockerScan(t *testing.T, testCli *coreTests.JfrogCli, imageName, watchNa cmdArgs = append(cmdArgs, "--watches="+watchName) output = testCli.WithoutCredentials().RunCliCmdWithOutput(t, cmdArgs...) if assert.NotEmpty(t, output) { - validations.VerifyJsonResults(t, output, validations.ValidationParams{SecurityViolations: minViolations}) + validations.VerifyJsonResults(t, output, validations.ValidationParams{Total: &validations.TotalCount{Violations: minViolations}}) } } } @@ -230,7 +225,7 @@ func verifyAdvancedSecurityScanResults(t *testing.T, content string) { assert.True(t, applicableStatusExists) // Verify that secretes detection succeeded. - assert.NotEqual(t, 0, len(results.Secrets)) + assert.NotEqual(t, 0, len(results.SecretsVulnerabilities)) } diff --git a/tests/config.go b/tests/config.go index f1cc6af3..54ae9d73 100644 --- a/tests/config.go +++ b/tests/config.go @@ -63,6 +63,8 @@ var ( JfrogSshPassphrase *string ContainerRegistry *string ciRunId *string + // Used for tests that require a project key + JfrogTestProjectKey *string ) func getTestUrlDefaultValue() string { @@ -116,6 +118,7 @@ func init() { JfrogSshPassphrase = flag.String("jfrog.sshPassphrase", "", "Ssh key passphrase") ContainerRegistry = flag.String("test.containerRegistry", "localhost:8084", "Container registry") ciRunId = flag.String("ci.runId", "", "A unique identifier used as a suffix to create repositories and builds in the tests") + JfrogTestProjectKey = flag.String("jfrog.projectKey", os.Getenv(TestJfrogPlatformProjectKeyEnvVar), "Project key used for tests") } func InitTestFlags() { diff --git a/tests/consts.go b/tests/consts.go index 4cfd9103..27fe25a5 100644 --- a/tests/consts.go +++ b/tests/consts.go @@ -13,10 +13,11 @@ const ( GoCacheEnvVar = "GOMODCACHE" PipCacheEnvVar = "PIP_CACHE_DIR" - TestJfrogUrlEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_URL" - TestJfrogTokenEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_ACCESS_TOKEN" - TestJfrogUserEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_USER" - TestJfrogPasswordEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_PASSWORD" + TestJfrogUrlEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_URL" + TestJfrogTokenEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_ACCESS_TOKEN" + TestJfrogUserEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_USER" + TestJfrogPasswordEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_PASSWORD" + TestJfrogPlatformProjectKeyEnvVar = "JFROG_SECURITY_CLI_TESTS_JFROG_PLATFORM_PROJECT_KEY" MavenCacheRedirectionVal = "-Dmaven.repo.local=" ) diff --git a/tests/testdata/other/iac-scan/contains-iac-violations.sarif b/tests/testdata/other/iac-scan/contains-iac-issues.sarif similarity index 100% rename from tests/testdata/other/iac-scan/contains-iac-violations.sarif rename to tests/testdata/other/iac-scan/contains-iac-issues.sarif diff --git a/tests/testdata/other/iac-scan/contains-iac-issues_violations.sarif b/tests/testdata/other/iac-scan/contains-iac-issues_violations.sarif new file mode 100644 index 00000000..ec8876ae --- /dev/null +++ b/tests/testdata/other/iac-scan/contains-iac-issues_violations.sarif @@ -0,0 +1,63 @@ +{ + "runs": [ + { + "tool": { + "driver": { + "name": "JFrog Terraform scanner", + "rules": [], + "version": "" + } + }, + "invocations": [ + { + "executionSuccessful": true, + "arguments": [ + "./tf_scanner", + "scan", + "scan.yaml" + ], + "workingDirectory": { + "uri": "file:///Users/ilya/Downloads/tf-scanner-main/src/dist/tf_scanner" + } + } + ], + "results": [ + { + "properties": { + "issueId": "sast-violation-1", + "policies": [ + "policy", + "policy2" + ], + "watch": "watch" + }, + "message": { + "text": "AWS Load balancer using insecure communications" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file:///Users/ilya/Downloads/tf-scanner-main/tests/hcl/applicable/req_sw_terraform_aws_alb_https_only.tf" + }, + "region": { + "endColumn": 2, + "endLine": 12, + "snippet": { + "text": "vulnerable_example" + }, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "ruleId": "aws_alb_https_only" + } + ] + } + ], + "version": "2.1.0", + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json" +} \ No newline at end of file diff --git a/tests/testdata/output/audit/audit_results.json b/tests/testdata/output/audit/audit_results.json deleted file mode 100644 index 8b381201..00000000 --- a/tests/testdata/output/audit/audit_results.json +++ /dev/null @@ -1,2505 +0,0 @@ -{ - "xray_version": "3.104.8", - "jas_entitled": true, - "command_type": "source_code", - "multi_scan_id": "7d5e4733-3f93-11ef-8147-e610d09d7daa", - "targets": [ - { - "target": "/Users/user/ejs-frog-demo", - "technology": "npm", - "sca_scans": { - "is_multiple_root_project": false, - "descriptors": [ - "/Users/user/ejs-frog-demo/package.json" - ], - "xray_scan": [ - { - "scan_id": "711851ce-68c4-4dfd-7afb-c29737ebcb96", - "violations": [ - { - "summary": "Prototype pollution attack when using _.zipObjectDeep in lodash before 4.17.20.", - "severity": "High", - "type": "security", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.19]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-114089", - "cves": [ - { - "cve": "CVE-2020-8203", - "cvss_v2_score": "5.8", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:M/Au:N/C:N/I:P/A:P", - "cvss_v3_score": "7.4", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H" - } - ], - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2020-8203", - "https://www.oracle.com/security-alerts/cpuapr2022.html", - "https://hackerone.com/reports/864701", - "https://hackerone.com/reports/712065", - "https://github.com/advisories/GHSA-p6mc-m468-83gw", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuApr2021.html", - "https://github.com/github/advisory-database/pull/2884", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/c84fe82760fb2d3e03a63379b297a1cc1a2fce12", - "https://security.netapp.com/advisory/ntap-20200724-0006/", - "https://web.archive.org/web/20210914001339/https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://github.com/lodash/lodash/issues/4874", - "https://github.com/lodash/lodash/wiki/Changelog#v41719" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-114089\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "Prototype pollution in lodash object merging and zipping functions leads to code injection.", - "full_description": "[lodash](https://lodash.com/) is a JavaScript library which provides utility functions for common programming tasks.\n\nJavaScript frontend and Node.js-based backend applications that merge or zip objects using the lodash functions `mergeWith`, `merge` and `zipObjectDeep` are vulnerable to [prototype pollution](https://medium.com/node-modules/what-is-prototype-pollution-and-why-is-it-such-a-big-deal-2dd8d89a93c) if one or more of the objects it receives as arguments are obtained from user input. \nAn attacker controlling this input given to the vulnerable functions can inject properties to JavaScript special objects such as [Object.prototype](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes) from which all JavaScript objects inherit properties and methods. Any change on `Object.prototype` properties will then propagate through the prototype chain inheritance to all of the objects in a JavaScript application. This in turn would allow an attacker to add new properties or modify existing properties which will have application specific implications that could lead to DoS (denial of service), authentication bypass, privilege escalation and even RCE (remote code execution) in [some cases](https://youtu.be/LUsiFV3dsK8?t=1152). \nAs an example for privilege escalation, consider a JavaScript application that has a `user` object which has a Boolean property of `user.isAdmin` which is used to decide which actions the user may take. If an attacker can modify or add the `isAdmin` property through prototype pollution, it can escalate the privileges of its own user to those of an admin. \nAs exploitation is usually application specific, successful exploitation is much more likely if an attacker have access to the JavaScript application code. As such, frontend applications are more vulnerable to this vulnerability than Node.js backend applications.", - "jfrog_research_severity": "Critical", - "jfrog_research_severity_reasons": [ - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "is_positive": true - }, - { - "name": "The issue can be exploited by attackers over the network" - }, - { - "name": "The issue is trivial to exploit and does not require a published writeup or PoC" - } - ], - "remediation": "##### Deployment mitigations\n\nAs general guidelines against prototype pollution, first consider not merging objects originating from user input or using a Map structure instead of an object. If merging objects is needed, look into creating objects without a prototype with `Object.create(null)` or into freezing `Object.prototype` with `Object.freeze()`. Finally, it is always best to perform input validation with a a [JSON schema validator](https://github.com/ajv-validator/ajv), which could mitigate this issue entirely in many cases." - } - }, - { - "summary": "lodash node module before 4.17.5 suffers from a Modification of Assumed-Immutable Data (MAID) vulnerability via defaultsDeep, merge, and mergeWith functions, which allows a malicious user to modify the prototype of \"Object\" via __proto__, causing the addition or modification of an existing property that will exist on all objects.", - "severity": "Medium", - "type": "security", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.5]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-72918", - "cves": [ - { - "cve": "CVE-2018-3721", - "cvss_v2_score": "4.0", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:S/C:N/I:P/A:N", - "cvss_v3_score": "6.5", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N" - } - ], - "references": [ - "https://www.npmjs.com/advisories/577", - "https://hackerone.com/reports/310443", - "https://github.com/advisories/GHSA-fvqr-27wr-82fm", - "https://nvd.nist.gov/vuln/detail/CVE-2018-3721", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/commit/d8e069cc3410082e44eb18fcf8e7f3d08ebe1d4a" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-72918\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1" - }, - { - "summary": "Express.js minimalist web framework for node. Versions of Express.js prior to 4.19.0 and all pre-release alpha and beta versions of 5.0 are affected by an open redirect vulnerability using malformed URLs. When a user of Express performs a redirect using a user-provided URL Express performs an encode [using `encodeurl`](https://github.com/pillarjs/encodeurl) on the contents before passing it to the `location` header. This can cause malformed URLs to be evaluated in unexpected ways by common redirect allow list implementations in Express applications, leading to an Open Redirect via bypass of a properly implemented allow list. The main method impacted is `res.location()` but this is also called from within `res.redirect()`. The vulnerability is fixed in 4.19.2 and 5.0.0-beta.3.", - "severity": "Medium", - "type": "security", - "components": { - "npm://express:4.18.2": { - "fixed_versions": [ - "[4.19.2]", - "[5.0.0-beta.3]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://express:4.18.2" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-594935", - "cves": [ - { - "cve": "CVE-2024-29041", - "cvss_v3_score": "6.1", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N" - } - ], - "references": [ - "https://github.com/koajs/koa/issues/1800", - "https://github.com/expressjs/express/pull/5539", - "https://github.com/expressjs/express/commit/0b746953c4bd8e377123527db11f9cd866e39f94", - "https://github.com/expressjs/express/commit/0867302ddbde0e9463d0564fea5861feb708c2dd", - "https://github.com/advisories/GHSA-rv95-896h-c2vc", - "https://expressjs.com/en/4x/api.html#res.location", - "https://nvd.nist.gov/vuln/detail/CVE-2024-29041", - "https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-594935\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1" - }, - { - "summary": "A prototype pollution vulnerability was found in lodash \u003c4.17.11 where the functions merge, mergeWith, and defaultsDeep can be tricked into adding or modifying properties of Object.prototype.", - "severity": "Medium", - "type": "security", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.11]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-75300", - "cves": [ - { - "cve": "CVE-2018-16487", - "cvss_v2_score": "6.8", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:M/Au:N/C:P/I:P/A:P", - "cvss_v3_score": "5.6", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L" - } - ], - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2018-16487", - "https://www.npmjs.com/advisories/782", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/advisories/GHSA-4xc9-xhrj-v574", - "https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://hackerone.com/reports/380873" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-75300\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "Insufficient input validation in the Lodash library leads to prototype pollution.", - "full_description": "The [Lodash](https://lodash.com/) library is an open-source JavaScript project that simplifies operations on string, arrays, numbers, and other objects. It is widely used in connected devices. \n\nThe `merge`, `mergeWith`, and `defaultsDeep` methods in Lodash are vulnerable to [prototype pollution](https://shieldfy.io/security-wiki/prototype-pollution/introduction-to-prototype-pollution/). Attackers can exploit this vulnerability by specifying a crafted `sources` parameter to any of these methods, which can modify the prototype properties of the `Object`, `Function`, `Array`, `String`, `Number`, and `Boolean` objects. A public [exploit](https://hackerone.com/reports/380873) exists which performs the prototype pollution with an arbitrary key and value.\n\nThe library implementation has a bug in the `safeGet()` function in the `lodash.js` module that allows for adding or modifying `prototype` properties of various objects. The official [solution](https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad) fixes the bug by explicitly forbidding the addition or modification of `prototype` properties.\n\nA related CVE (CVE-2018-3721) covers the same issue prior to Lodash version 4.17.5, but the fix for that was incomplete.", - "jfrog_research_severity": "High", - "jfrog_research_severity_reasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into one of the following methods - \n* `merge` - 2nd argument\n* `mergeWith` - 2nd argument\n* `defaultsDeep` - 2nd argument", - "is_positive": true - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "is_positive": true - }, - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrated exploitation by injecting an attacker controlled key and value into the prototype" - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } - }, - { - "summary": "The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).", - "severity": "Critical", - "type": "security", - "components": { - "npm://ejs:3.1.6": { - "fixed_versions": [ - "[3.1.7]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://ejs:3.1.6" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-209002", - "cves": [ - { - "cve": "CVE-2022-29078", - "cvss_v2_score": "7.5", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", - "cvss_v3_score": "9.8", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" - } - ], - "references": [ - "https://github.com/mde/ejs/commit/15ee698583c98dadc456639d6245580d17a24baf", - "https://eslam.io/posts/ejs-server-side-template-injection-rce/", - "https://security.netapp.com/advisory/ntap-20220804-0001", - "https://github.com/mde/ejs/releases", - "https://nvd.nist.gov/vuln/detail/CVE-2022-29078", - "https://eslam.io/posts/ejs-server-side-template-injection-rce", - "https://github.com/mde/ejs", - "https://security.netapp.com/advisory/ntap-20220804-0001/" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-209002\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "Insufficient input validation in EJS enables attackers to perform template injection when attacker can control the rendering options.", - "full_description": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to perform template injection on the `opts.outputFunctionName` variable, since the variable is injected into the template body without any escaping. Although it is unlikely that the attacker can directly control the `outputFunctionName` property, it is possible that it can be influenced in conjunction with a prototype pollution vulnerability.\n\nOnce template injection is achieved, the attacker can immediately perform remote code execution since the template engine (EJS) allows executing arbitrary JavaScript code.\n\nExample of a vulnerable Node.js application -\n```js\nconst express = require('express');\nconst bodyParser = require('body-parser');\nconst lodash = require('lodash');\nconst ejs = require('ejs');\n\nconst app = express();\n\napp\n .use(bodyParser.urlencoded({extended: true}))\n .use(bodyParser.json());\n\napp.set('views', './');\napp.set('view engine', 'ejs');\n\napp.get(\"/\", (req, res) =\u003e {\n res.render('index');\n});\n\napp.post(\"/\", (req, res) =\u003e {\n let data = {};\n let input = JSON.parse(req.body.content);\n lodash.defaultsDeep(data, input);\n res.json({message: \"OK\"});\n});\n\nlet server = app.listen(8086, '0.0.0.0', function() {\n console.log('Listening on port %d', server.address().port);\n});\n```\n\nExploiting the above example for RCE -\n`curl 127.0.0.1:8086 -v --data 'content={\"constructor\": {\"prototype\": {\"outputFunctionName\": \"a; return global.process.mainModule.constructor._load(\\\"child_process\\\").execSync(\\\"whoami\\\"); //\"}}}'\n`\n\nDue to the prototype pollution in the `lodash.defaultsDeep` call, an attacker can inject the `outputFunctionName` property with an arbitrary value. The chosen value executes an arbitrary process via the `child_process` module.", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The attacker has to find a way to get their malicious input to `opts.outputFunctionName`, which will usually require exploitation of a prototype pollution vulnerability somewhere else in the code. However, there could be cases where the attacker can pass malicious data to the render function directly because of design problems in other code using EJS.", - "is_positive": true - }, - { - "name": "The issue has an exploit published", - "description": "There are multiple examples of exploits for this vulnerability online." - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Successful exploitation of this vulnerability leads to remote code execution." - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\nNote that this mitigation is supposed to stop any prototype pollution attacks which can allow an attacker to control the `opts.outputFunctionName` parameter indirectly.\n\nThe mitigation will not stop any (extremely unlikely) scenarios where the JavaScript code allows external input to directly affect `opts.outputFunctionName`." - } - }, - { - "summary": "The ejs (aka Embedded JavaScript templates) package before 3.1.10 for Node.js lacks certain pollution protection.", - "severity": "Medium", - "type": "security", - "components": { - "npm://ejs:3.1.6": { - "fixed_versions": [ - "[3.1.10]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://ejs:3.1.6" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-599735", - "cves": [ - { - "cve": "CVE-2024-33883", - "cvss_v3_score": "4.0", - "cvss_v3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" - } - ], - "references": [ - "https://security.netapp.com/advisory/ntap-20240605-0003/", - "https://security.netapp.com/advisory/ntap-20240605-0003", - "https://github.com/mde/ejs/commit/e469741dca7df2eb400199e1cdb74621e3f89aa5", - "https://github.com/mde/ejs/compare/v3.1.9...v3.1.10", - "https://github.com/advisories/GHSA-ghr5-ch3p-vcr6", - "https://nvd.nist.gov/vuln/detail/CVE-2024-33883" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-599735\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "Insufficient input validation in EJS may lead to prototype pollution.", - "full_description": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as `EJS`, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nA prototype pollution gadget within the EJS template engine could potentially be leveraged by attackers to achieve remote code execution or DoS via prototype pollution.\n\n```\nfunction Template(text, opts) {\n opts = opts || utils.createNullProtoObjWherePossible();\n```\n\nWhen checking for the presence of a property within an object variable, the lookup scope isn't explicitly defined. In JavaScript, the absence of a defined lookup scope prompts a search up to the root prototype (`Object.prototype`). This could potentially be under the control of an attacker if another prototype pollution vulnerability is present within the application.\n\nIf the application server is using the EJS as the backend template engine, and there is another prototype pollution vulnerability in the application, then the attacker could leverage the found gadgets in the EJS template engine to escalate the prototype pollution to remote code execution or DoS.\n\nThe following code will execute a command on the server by polluting `opts.escapeFunction`:\n \n```\nconst express = require('express');\nconst app = express();\nconst port = 8008;\nconst ejs = require('ejs');\n\n// Set EJS as the view engine\napp.set('view engine', 'ejs');\n\napp.get('/', (req, res) =\u003e {\n \n const data = {title: 'Welcome', message: 'Hello'};\n\n // Sample EJS template string\n const templateString = `\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\u003c%= title %\u003e\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003ch1\u003e\u003c%= message %\u003e\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e`;\n\n const { exec } = require('child_process');\n\n function myFunc() {\n exec('bash -c \"echo 123\"', (error, stdout, stderr) =\u003e {\n if (error) {\n console.error(`exec error: ${error}`);\n return;\n }\n if (stderr){\n console.log(`stderr : ${stderr}`);\n return;\n }\n // Handle success\n console.log(`Command executed successfully. Output: ${stdout}`);\n });\n }\n\n const options = {client:false};\n\n Object.prototype.escapeFunction = myFunc;\n \n const compiledTemplate = ejs.compile(templateString, options);\n const renderedHtml = compiledTemplate(data);\n res.send(renderedHtml);\n});\n\n// Start the server\napp.listen(port, () =\u003e {\n console.log(`Server is running on http://localhost:${port}`);\n});\n```", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "Attackers can only leverage this vulnerability when the application server is using the EJS as the backend template engine. Moreover, there must be a second prototype pollution vulnerability in the application.", - "is_positive": true - }, - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "CVSS does not take into account the unlikely prerequisites necessary for exploitation.", - "is_positive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "A prototype pollution attack allows the attacker to inject new properties into all JavaScript objects.\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable." - } - ] - } - }, - { - "summary": "ejs v3.1.9 is vulnerable to server-side template injection. If the ejs file is controllable, template injection can be implemented through the configuration settings of the closeDelimiter parameter. NOTE: this is disputed by the vendor because the render function is not intended to be used with untrusted input.", - "severity": "Critical", - "type": "security", - "components": { - "npm://ejs:3.1.6": { - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://ejs:3.1.6" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-520200", - "cves": [ - { - "cve": "CVE-2023-29827", - "cvss_v3_score": "9.8", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" - } - ], - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2023-29827", - "https://github.com/mde/ejs/issues/720", - "https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-520200\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "Insufficient input validation can lead to template injection in ejs when attackers can control both the rendered template and rendering options.", - "full_description": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to bypass ejs' template injection restrictions, by abusing the `closeDelimiter` rendering option, in the case when -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\n\nThe vulnerability was **rightfully disputed** due to the fact that a vulnerable configuration is extremely unlikely to exist in any real-world setup. As such, the maintainers will not provide a fix for this (non-)issue.\n\nExample of a vulnerable application -\n```js\nconst express = require('express')\nconst app = express()\nconst port = 3000\n\napp.set('view engine', 'ejs');\n\napp.get('/page', (req,res) =\u003e {\n res.render('page', req.query); // OPTS (2nd parameter) IS ATTACKER-CONTROLLED\n})\n\napp.listen(port, () =\u003e {\n console.log(\"Example app listening on port ${port}\")\n})\n```\n\nContents of `page.ejs` (very unlikely to be attacker controlled) -\n```js\n%%1\");process.mainModule.require('child_process').execSync('calc');//\n```\n\nIn this case, sending `closeDelimiter` with the same malicious code that already exists at `page.ejs` will trigger the injection -\n`http://127.0.0.1:3000/page?settings[view%20options][closeDelimiter]=1\")%3bprocess.mainModule.require('child_process').execSync('calc')%3b//`", - "jfrog_research_severity": "Low", - "jfrog_research_severity_reasons": [ - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The CVSS does not take into account the rarity of a vulnerable configuration to exist", - "is_positive": true - }, - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The vulnerability can be exploited only under the following conditions -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\nThis vulnerable configuration is extremely unlikely to exist in any real-world setup.", - "is_positive": true - }, - { - "name": "The issue has been disputed by the vendor", - "is_positive": true - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates template injection" - } - ] - } - }, - { - "summary": "Versions of lodash lower than 4.17.12 are vulnerable to Prototype Pollution. The function defaultsDeep could be tricked into adding or modifying properties of Object.prototype using a constructor payload.", - "severity": "Critical", - "type": "security", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.12]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-85679", - "cves": [ - { - "cve": "CVE-2019-10744", - "cvss_v2_score": "6.4", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:P/A:P", - "cvss_v3_score": "9.1", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H" - } - ], - "references": [ - "https://www.npmjs.com/advisories/1065", - "https://github.com/lodash/lodash/pull/4336", - "https://www.oracle.com/security-alerts/cpujan2021.html", - "https://security.netapp.com/advisory/ntap-20191004-0005/", - "https://snyk.io/vuln/SNYK-JS-LODASH-450202", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp;utm_medium=RSS", - "https://access.redhat.com/errata/RHSA-2019:3024", - "https://www.oracle.com/security-alerts/cpuoct2020.html", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp%3Butm_medium=RSS", - "https://github.com/advisories/GHSA-jf85-cpcp-j695", - "https://nvd.nist.gov/vuln/detail/CVE-2019-10744" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-85679\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "Insufficient input validation in lodash defaultsDeep() leads to prototype pollution.", - "full_description": "[lodash](https://www.npmjs.com/package/lodash) is a modern JavaScript utility library delivering modularity, performance, \u0026 extras.\n\nThe function `defaultsDeep` was found to be vulnerable to prototype pollution, when accepting arbitrary source objects from untrusted input\n\nExample of code vulnerable to this issue - \n```js\nconst lodash = require('lodash'); \nconst evilsrc = {constructor: {prototype: {evilkey: \"evilvalue\"}}};\nlodash.defaultsDeep({}, evilsrc)\n```", - "jfrog_research_severity": "High", - "jfrog_research_severity_reasons": [ - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrates exploitation of this issue" - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "is_positive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into the `defaultsDeep` method (2nd arg)", - "is_positive": true - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } - }, - { - "summary": "lodash prior to 4.17.11 is affected by: CWE-400: Uncontrolled Resource Consumption. The impact is: Denial of service. The component is: Date handler. The attack vector is: Attacker provides very long strings, which the library attempts to match using a regular expression. The fixed version is: 4.17.11.", - "severity": "Medium", - "type": "security", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.11]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-85049", - "cves": [ - { - "cve": "CVE-2019-1010266", - "cvss_v2_score": "4.0", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:S/C:N/I:N/A:P", - "cvss_v3_score": "6.5", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H" - } - ], - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2019-1010266", - "https://github.com/lodash/lodash/wiki/Changelog", - "https://snyk.io/vuln/SNYK-JS-LODASH-73639", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/issues/3359", - "https://github.com/lodash/lodash/commit/5c08f18d365b64063bfbfa686cbb97cdd6267347" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-85049\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1" - }, - { - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Regular Expression Denial of Service (ReDoS) via the toNumber, trim and trimEnd functions.", - "severity": "Medium", - "type": "security", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.21]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-140562", - "cves": [ - { - "cve": "CVE-2020-28500", - "cvss_v2_score": "5.0", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:P", - "cvss_v3_score": "5.3", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" - } - ], - "references": [ - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://github.com/lodash/lodash/commit/c4847ebe7d14540bb28a8b932a9ce1b9ecbfee1a", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074894", - "https://github.com/lodash/lodash/blob/npm/trimEnd.js%23L8", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074893", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074892", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://nvd.nist.gov/vuln/detail/CVE-2020-28500", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074895", - "https://github.com/lodash/lodash/pull/5065/commits/02906b8191d3c100c193fe6f7b27d1c40f200bb7", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/advisories/GHSA-29mw-wpgm-hmr9", - "https://github.com/lodash/lodash/pull/5065", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074896", - "https://snyk.io/vuln/SNYK-JS-LODASH-1018905" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-140562\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "ReDoS in lodash could lead to a denial of service when handling untrusted strings.", - "full_description": "JavaScript-based applications that use [lodash](https://github.com/lodash/lodash) and specifically the [_.toNumber](https://lodash.com/docs/4.17.15#toNumber), [_.trim](https://lodash.com/docs/4.17.15#trim) and [_.trimEnd](https://lodash.com/docs/4.17.15#trimEnd) functions, could be vulnerable to DoS (Denial of Service) through a faulty regular expression that introduces a ReDoS (Regular Expression DoS) vulnerability. This vulnerability is only triggered if untrusted user input flows into these vulnerable functions and the attacker can supply arbitrary long strings (over 50kB) that contain whitespaces. \n\nOn a modern Core i7-based system, calling the vulnerable functions with a 50kB string could take between 2 to 3 seconds to execute and 4.5 minutes for a longer 500kB string. The fix improved the regular expression performance so it took only a few milliseconds on the same Core i7-based system. This vulnerability is easily exploitable as all is required is to build a string that triggers it as can be seen in this PoC reproducing code - \n\n```js\nvar untrusted_user_input_50k = \"a\" + ' '.repeat(50000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_50k); // should take a few seconds to run\nvar untrusted_user_input_500k = \"a\" + ' '.repeat(500000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_500k); // should take a few minutes to run\n```", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The issue has an exploit published", - "description": "Public exploit demonstrated ReDoS" - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "Exploitation depends on parsing user input by the `.toNumber`, `.trim` or `.trimEnd` `lodash` functions, and requires the input to contain whitespaces and be very long (over 50KB)", - "is_positive": true - } - ], - "remediation": "##### Deployment mitigations\n\nTrim untrusted strings based on size before providing it to the vulnerable functions by using the `substring` function to with a fixed maximum size like so - ```js untrusted_user_input.substring(0, max_string_size_less_than_50kB); ```" - } - }, - { - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.", - "severity": "High", - "type": "security", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.21]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "watch_name": "Security_watch_1", - "issue_id": "XRAY-140575", - "cves": [ - { - "cve": "CVE-2021-23337", - "cvss_v2_score": "6.5", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:S/C:P/I:P/A:P", - "cvss_v3_score": "7.2", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H" - } - ], - "references": [ - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074929", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JS-LODASH-1040724", - "https://security.netapp.com/advisory/ntap-20210312-0006", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c", - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074928", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074932", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js%23L14851", - "https://github.com/advisories/GHSA-35jh-r3h4-6jhm", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074930", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074931", - "https://nvd.nist.gov/vuln/detail/CVE-2021-23337", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js#L14851" - ], - "ignore_url": "https://platform.jfrog.io/ui/admin/xray/policiesGovernance/ignore-rules?graph_scan_id=711851ce-68c4-4dfd-7afb-c29737ebcb96\u0026issue_id=XRAY-140575\u0026on_demand_scanning=true\u0026show_popup=true\u0026type=security\u0026watch_name=Security_watch_1", - "extended_information": { - "short_description": "Improper sanitization in the lodash template function leads to JavaScript code injection through the options argument.", - "full_description": "JavaScript-based applications (both frontend and backend) that use the [template function](https://lodash.com/docs/4.17.15#template) -`_.template([string=''], [options={}])` from the [lodash](https://lodash.com/) utility library and provide the `options` argument (specifically the `variable` option) from untrusted user input, are vulnerable to JavaScript code injection. This issue can be easily exploited, and an exploitation example is [publicly available](https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c#diff-a561630bb56b82342bc66697aee2ad96efddcbc9d150665abd6fb7ecb7c0ab2fR22303) in the fix tests that was introduced in version 4.17.21 - \n```js\nlodash.template('', { variable: '){console.log(process.env)}; with(obj' })()\n```", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "It is highly unlikely that a JS program will accept arbitrary remote input into the template's `options` argument", - "is_positive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The attacker must find remote input that propagates into the `options` argument of a `template` call", - "is_positive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Leads to remote code execution through JS code injection" - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates arbitrary JS code execution" - } - ] - } - } - ], - "vulnerabilities": [ - { - "cves": [ - { - "cve": "CVE-2024-39249" - } - ], - "summary": "Async \u003c= 2.6.4 and \u003c= 3.2.5 are vulnerable to ReDoS (Regular Expression Denial of Service) while parsing function in autoinject function. NOTE: this is disputed by the supplier because there is no realistic threat model: regular expressions are not used with untrusted input.", - "severity": "Unknown", - "components": { - "npm://async:3.2.4": { - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://ejs:3.1.6" - }, - { - "component_id": "npm://jake:10.8.7" - }, - { - "component_id": "npm://async:3.2.4" - } - ] - ] - } - }, - "issue_id": "XRAY-609848", - "references": [ - "https://github.com/zunak/CVE-2024-39249", - "https://github.com/caolan/async/blob/v3.2.5/lib/autoInject.js#L41", - "https://nvd.nist.gov/vuln/detail/CVE-2024-39249", - "https://github.com/caolan/async/blob/v3.2.5/lib/autoInject.js#L6", - "https://github.com/caolan/async/issues/1975#issuecomment-2204528153", - "https://github.com/zunak/CVE-2024-39249/issues/1" - ], - "extended_information": { - "short_description": "ReDoS in Async may lead to denial of service while parsing malformed source code.", - "jfrog_research_severity": "Low", - "jfrog_research_severity_reasons": [ - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The reported CVSS does not reflect the severity of the vulnerability.", - "is_positive": true - }, - { - "name": "The issue cannot result in a severe impact (such as remote code execution)", - "description": "To exploit this issue an attacker must change the source code of the application. In cases where an attacker can already modify (or fully control) the source code, the attacker can immediately achieve arbitrary code execution - thus this issue has almost no security impact.", - "is_positive": true - }, - { - "name": "The issue has an exploit published", - "description": "A proof-of-concept has been published in the advisory." - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The issue requires the use of the `async.autoInject` function to be vulnerable.", - "is_positive": true - } - ] - } - }, - { - "cves": [ - { - "cve": "CVE-2020-8203", - "cvss_v2_score": "5.8", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:M/Au:N/C:N/I:P/A:P", - "cvss_v3_score": "7.4", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H", - "cwe": [ - "CWE-770", - "CWE-1321" - ], - "cwe_details": { - "CWE-1321": { - "name": "Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')", - "description": "The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype." - }, - "CWE-770": { - "name": "Allocation of Resources Without Limits or Throttling", - "description": "The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor." - } - } - } - ], - "summary": "Prototype pollution attack when using _.zipObjectDeep in lodash before 4.17.20.", - "severity": "High", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.19]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "issue_id": "XRAY-114089", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2020-8203", - "https://www.oracle.com/security-alerts/cpuapr2022.html", - "https://hackerone.com/reports/864701", - "https://hackerone.com/reports/712065", - "https://github.com/advisories/GHSA-p6mc-m468-83gw", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuApr2021.html", - "https://github.com/github/advisory-database/pull/2884", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/c84fe82760fb2d3e03a63379b297a1cc1a2fce12", - "https://security.netapp.com/advisory/ntap-20200724-0006/", - "https://web.archive.org/web/20210914001339/https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://github.com/lodash/lodash/issues/4874", - "https://github.com/lodash/lodash/wiki/Changelog#v41719" - ], - "extended_information": { - "short_description": "Prototype pollution in lodash object merging and zipping functions leads to code injection.", - "full_description": "[lodash](https://lodash.com/) is a JavaScript library which provides utility functions for common programming tasks.\n\nJavaScript frontend and Node.js-based backend applications that merge or zip objects using the lodash functions `mergeWith`, `merge` and `zipObjectDeep` are vulnerable to [prototype pollution](https://medium.com/node-modules/what-is-prototype-pollution-and-why-is-it-such-a-big-deal-2dd8d89a93c) if one or more of the objects it receives as arguments are obtained from user input. \nAn attacker controlling this input given to the vulnerable functions can inject properties to JavaScript special objects such as [Object.prototype](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes) from which all JavaScript objects inherit properties and methods. Any change on `Object.prototype` properties will then propagate through the prototype chain inheritance to all of the objects in a JavaScript application. This in turn would allow an attacker to add new properties or modify existing properties which will have application specific implications that could lead to DoS (denial of service), authentication bypass, privilege escalation and even RCE (remote code execution) in [some cases](https://youtu.be/LUsiFV3dsK8?t=1152). \nAs an example for privilege escalation, consider a JavaScript application that has a `user` object which has a Boolean property of `user.isAdmin` which is used to decide which actions the user may take. If an attacker can modify or add the `isAdmin` property through prototype pollution, it can escalate the privileges of its own user to those of an admin. \nAs exploitation is usually application specific, successful exploitation is much more likely if an attacker have access to the JavaScript application code. As such, frontend applications are more vulnerable to this vulnerability than Node.js backend applications.", - "jfrog_research_severity": "Critical", - "jfrog_research_severity_reasons": [ - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "is_positive": true - }, - { - "name": "The issue can be exploited by attackers over the network" - }, - { - "name": "The issue is trivial to exploit and does not require a published writeup or PoC" - } - ], - "remediation": "##### Deployment mitigations\n\nAs general guidelines against prototype pollution, first consider not merging objects originating from user input or using a Map structure instead of an object. If merging objects is needed, look into creating objects without a prototype with `Object.create(null)` or into freezing `Object.prototype` with `Object.freeze()`. Finally, it is always best to perform input validation with a a [JSON schema validator](https://github.com/ajv-validator/ajv), which could mitigate this issue entirely in many cases." - } - }, - { - "cves": [ - { - "cve": "CVE-2019-10744", - "cvss_v2_score": "6.4", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:P/A:P", - "cvss_v3_score": "9.1", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H", - "cwe": [ - "CWE-1321", - "CWE-20" - ], - "cwe_details": { - "CWE-1321": { - "name": "Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')", - "description": "The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype." - }, - "CWE-20": { - "name": "Improper Input Validation", - "description": "The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.", - "categories": [ - { - "category": "2023 CWE Top 25", - "rank": "6" - } - ] - } - } - } - ], - "summary": "Versions of lodash lower than 4.17.12 are vulnerable to Prototype Pollution. The function defaultsDeep could be tricked into adding or modifying properties of Object.prototype using a constructor payload.", - "severity": "Critical", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.12]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "issue_id": "XRAY-85679", - "references": [ - "https://www.npmjs.com/advisories/1065", - "https://github.com/lodash/lodash/pull/4336", - "https://www.oracle.com/security-alerts/cpujan2021.html", - "https://security.netapp.com/advisory/ntap-20191004-0005/", - "https://snyk.io/vuln/SNYK-JS-LODASH-450202", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp;utm_medium=RSS", - "https://access.redhat.com/errata/RHSA-2019:3024", - "https://www.oracle.com/security-alerts/cpuoct2020.html", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp%3Butm_medium=RSS", - "https://github.com/advisories/GHSA-jf85-cpcp-j695", - "https://nvd.nist.gov/vuln/detail/CVE-2019-10744" - ], - "extended_information": { - "short_description": "Insufficient input validation in lodash defaultsDeep() leads to prototype pollution.", - "full_description": "[lodash](https://www.npmjs.com/package/lodash) is a modern JavaScript utility library delivering modularity, performance, \u0026 extras.\n\nThe function `defaultsDeep` was found to be vulnerable to prototype pollution, when accepting arbitrary source objects from untrusted input\n\nExample of code vulnerable to this issue - \n```js\nconst lodash = require('lodash'); \nconst evilsrc = {constructor: {prototype: {evilkey: \"evilvalue\"}}};\nlodash.defaultsDeep({}, evilsrc)\n```", - "jfrog_research_severity": "High", - "jfrog_research_severity_reasons": [ - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrates exploitation of this issue" - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "is_positive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into the `defaultsDeep` method (2nd arg)", - "is_positive": true - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } - }, - { - "cves": [ - { - "cve": "CVE-2019-1010266", - "cvss_v2_score": "4.0", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:S/C:N/I:N/A:P", - "cvss_v3_score": "6.5", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", - "cwe": [ - "CWE-400", - "CWE-770" - ], - "cwe_details": { - "CWE-400": { - "name": "Uncontrolled Resource Consumption", - "description": "The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources." - }, - "CWE-770": { - "name": "Allocation of Resources Without Limits or Throttling", - "description": "The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor." - } - } - } - ], - "summary": "lodash prior to 4.17.11 is affected by: CWE-400: Uncontrolled Resource Consumption. The impact is: Denial of service. The component is: Date handler. The attack vector is: Attacker provides very long strings, which the library attempts to match using a regular expression. The fixed version is: 4.17.11.", - "severity": "Medium", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.11]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "issue_id": "XRAY-85049", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2019-1010266", - "https://github.com/lodash/lodash/wiki/Changelog", - "https://snyk.io/vuln/SNYK-JS-LODASH-73639", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/issues/3359", - "https://github.com/lodash/lodash/commit/5c08f18d365b64063bfbfa686cbb97cdd6267347" - ] - }, - { - "cves": [ - { - "cve": "CVE-2020-28500", - "cvss_v2_score": "5.0", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:P", - "cvss_v3_score": "5.3", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "cwe": [ - "CWE-400", - "CWE-1333", - "NVD-CWE-Other" - ], - "cwe_details": { - "CWE-1333": { - "name": "Inefficient Regular Expression Complexity", - "description": "The product uses a regular expression with an inefficient, possibly exponential worst-case computational complexity that consumes excessive CPU cycles." - }, - "CWE-400": { - "name": "Uncontrolled Resource Consumption", - "description": "The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources." - } - } - } - ], - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Regular Expression Denial of Service (ReDoS) via the toNumber, trim and trimEnd functions.", - "severity": "Medium", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.21]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "issue_id": "XRAY-140562", - "references": [ - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://github.com/lodash/lodash/commit/c4847ebe7d14540bb28a8b932a9ce1b9ecbfee1a", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074894", - "https://github.com/lodash/lodash/blob/npm/trimEnd.js%23L8", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074893", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074892", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://nvd.nist.gov/vuln/detail/CVE-2020-28500", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074895", - "https://github.com/lodash/lodash/pull/5065/commits/02906b8191d3c100c193fe6f7b27d1c40f200bb7", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/advisories/GHSA-29mw-wpgm-hmr9", - "https://github.com/lodash/lodash/pull/5065", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074896", - "https://snyk.io/vuln/SNYK-JS-LODASH-1018905" - ], - "extended_information": { - "short_description": "ReDoS in lodash could lead to a denial of service when handling untrusted strings.", - "full_description": "JavaScript-based applications that use [lodash](https://github.com/lodash/lodash) and specifically the [_.toNumber](https://lodash.com/docs/4.17.15#toNumber), [_.trim](https://lodash.com/docs/4.17.15#trim) and [_.trimEnd](https://lodash.com/docs/4.17.15#trimEnd) functions, could be vulnerable to DoS (Denial of Service) through a faulty regular expression that introduces a ReDoS (Regular Expression DoS) vulnerability. This vulnerability is only triggered if untrusted user input flows into these vulnerable functions and the attacker can supply arbitrary long strings (over 50kB) that contain whitespaces. \n\nOn a modern Core i7-based system, calling the vulnerable functions with a 50kB string could take between 2 to 3 seconds to execute and 4.5 minutes for a longer 500kB string. The fix improved the regular expression performance so it took only a few milliseconds on the same Core i7-based system. This vulnerability is easily exploitable as all is required is to build a string that triggers it as can be seen in this PoC reproducing code - \n\n```js\nvar untrusted_user_input_50k = \"a\" + ' '.repeat(50000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_50k); // should take a few seconds to run\nvar untrusted_user_input_500k = \"a\" + ' '.repeat(500000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_500k); // should take a few minutes to run\n```", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The issue has an exploit published", - "description": "Public exploit demonstrated ReDoS" - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "Exploitation depends on parsing user input by the `.toNumber`, `.trim` or `.trimEnd` `lodash` functions, and requires the input to contain whitespaces and be very long (over 50KB)", - "is_positive": true - } - ], - "remediation": "##### Deployment mitigations\n\nTrim untrusted strings based on size before providing it to the vulnerable functions by using the `substring` function to with a fixed maximum size like so - ```js untrusted_user_input.substring(0, max_string_size_less_than_50kB); ```" - } - }, - { - "cves": [ - { - "cve": "CVE-2018-3721", - "cvss_v2_score": "4.0", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:S/C:N/I:P/A:N", - "cvss_v3_score": "6.5", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N", - "cwe": [ - "CWE-1321", - "CWE-471" - ], - "cwe_details": { - "CWE-1321": { - "name": "Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')", - "description": "The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype." - }, - "CWE-471": { - "name": "Modification of Assumed-Immutable Data (MAID)", - "description": "The product does not properly protect an assumed-immutable element from being modified by an attacker." - } - } - } - ], - "summary": "lodash node module before 4.17.5 suffers from a Modification of Assumed-Immutable Data (MAID) vulnerability via defaultsDeep, merge, and mergeWith functions, which allows a malicious user to modify the prototype of \"Object\" via __proto__, causing the addition or modification of an existing property that will exist on all objects.", - "severity": "Medium", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.5]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "issue_id": "XRAY-72918", - "references": [ - "https://www.npmjs.com/advisories/577", - "https://hackerone.com/reports/310443", - "https://github.com/advisories/GHSA-fvqr-27wr-82fm", - "https://nvd.nist.gov/vuln/detail/CVE-2018-3721", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/commit/d8e069cc3410082e44eb18fcf8e7f3d08ebe1d4a" - ] - }, - { - "cves": [ - { - "cve": "CVE-2021-23337", - "cvss_v2_score": "6.5", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:S/C:P/I:P/A:P", - "cvss_v3_score": "7.2", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H", - "cwe": [ - "CWE-77", - "CWE-94" - ], - "cwe_details": { - "CWE-77": { - "name": "Improper Neutralization of Special Elements used in a Command ('Command Injection')", - "description": "The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.", - "categories": [ - { - "category": "2023 CWE Top 25", - "rank": "16" - } - ] - }, - "CWE-94": { - "name": "Improper Control of Generation of Code ('Code Injection')", - "description": "The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.", - "categories": [ - { - "category": "2023 CWE Top 25", - "rank": "23" - } - ] - } - } - } - ], - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.", - "severity": "High", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.21]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "issue_id": "XRAY-140575", - "references": [ - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074929", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JS-LODASH-1040724", - "https://security.netapp.com/advisory/ntap-20210312-0006", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c", - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074928", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074932", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js%23L14851", - "https://github.com/advisories/GHSA-35jh-r3h4-6jhm", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074930", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074931", - "https://nvd.nist.gov/vuln/detail/CVE-2021-23337", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js#L14851" - ], - "extended_information": { - "short_description": "Improper sanitization in the lodash template function leads to JavaScript code injection through the options argument.", - "full_description": "JavaScript-based applications (both frontend and backend) that use the [template function](https://lodash.com/docs/4.17.15#template) -`_.template([string=''], [options={}])` from the [lodash](https://lodash.com/) utility library and provide the `options` argument (specifically the `variable` option) from untrusted user input, are vulnerable to JavaScript code injection. This issue can be easily exploited, and an exploitation example is [publicly available](https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c#diff-a561630bb56b82342bc66697aee2ad96efddcbc9d150665abd6fb7ecb7c0ab2fR22303) in the fix tests that was introduced in version 4.17.21 - \n```js\nlodash.template('', { variable: '){console.log(process.env)}; with(obj' })()\n```", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "It is highly unlikely that a JS program will accept arbitrary remote input into the template's `options` argument", - "is_positive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The attacker must find remote input that propagates into the `options` argument of a `template` call", - "is_positive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Leads to remote code execution through JS code injection" - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates arbitrary JS code execution" - } - ] - } - }, - { - "cves": [ - { - "cve": "CVE-2018-16487", - "cvss_v2_score": "6.8", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:M/Au:N/C:P/I:P/A:P", - "cvss_v3_score": "5.6", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L", - "cwe": [ - "CWE-400", - "NVD-CWE-noinfo" - ], - "cwe_details": { - "CWE-400": { - "name": "Uncontrolled Resource Consumption", - "description": "The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources." - } - } - } - ], - "summary": "A prototype pollution vulnerability was found in lodash \u003c4.17.11 where the functions merge, mergeWith, and defaultsDeep can be tricked into adding or modifying properties of Object.prototype.", - "severity": "Medium", - "components": { - "npm://lodash:4.17.0": { - "fixed_versions": [ - "[4.17.11]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://lodash:4.17.0" - } - ] - ] - } - }, - "issue_id": "XRAY-75300", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2018-16487", - "https://www.npmjs.com/advisories/782", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/advisories/GHSA-4xc9-xhrj-v574", - "https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://hackerone.com/reports/380873" - ], - "extended_information": { - "short_description": "Insufficient input validation in the Lodash library leads to prototype pollution.", - "full_description": "The [Lodash](https://lodash.com/) library is an open-source JavaScript project that simplifies operations on string, arrays, numbers, and other objects. It is widely used in connected devices. \n\nThe `merge`, `mergeWith`, and `defaultsDeep` methods in Lodash are vulnerable to [prototype pollution](https://shieldfy.io/security-wiki/prototype-pollution/introduction-to-prototype-pollution/). Attackers can exploit this vulnerability by specifying a crafted `sources` parameter to any of these methods, which can modify the prototype properties of the `Object`, `Function`, `Array`, `String`, `Number`, and `Boolean` objects. A public [exploit](https://hackerone.com/reports/380873) exists which performs the prototype pollution with an arbitrary key and value.\n\nThe library implementation has a bug in the `safeGet()` function in the `lodash.js` module that allows for adding or modifying `prototype` properties of various objects. The official [solution](https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad) fixes the bug by explicitly forbidding the addition or modification of `prototype` properties.\n\nA related CVE (CVE-2018-3721) covers the same issue prior to Lodash version 4.17.5, but the fix for that was incomplete.", - "jfrog_research_severity": "High", - "jfrog_research_severity_reasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into one of the following methods - \n* `merge` - 2nd argument\n* `mergeWith` - 2nd argument\n* `defaultsDeep` - 2nd argument", - "is_positive": true - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "is_positive": true - }, - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrated exploitation by injecting an attacker controlled key and value into the prototype" - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } - }, - { - "cves": [ - { - "cve": "CVE-2024-29041", - "cvss_v3_score": "6.1", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", - "cwe": [ - "CWE-601", - "CWE-1286" - ], - "cwe_details": { - "CWE-1286": { - "name": "Improper Validation of Syntactic Correctness of Input", - "description": "The product receives input that is expected to be well-formed - i.e., to comply with a certain syntax - but it does not validate or incorrectly validates that the input complies with the syntax." - }, - "CWE-601": { - "name": "URL Redirection to Untrusted Site ('Open Redirect')", - "description": "A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks." - } - } - } - ], - "summary": "Express.js minimalist web framework for node. Versions of Express.js prior to 4.19.0 and all pre-release alpha and beta versions of 5.0 are affected by an open redirect vulnerability using malformed URLs. When a user of Express performs a redirect using a user-provided URL Express performs an encode [using `encodeurl`](https://github.com/pillarjs/encodeurl) on the contents before passing it to the `location` header. This can cause malformed URLs to be evaluated in unexpected ways by common redirect allow list implementations in Express applications, leading to an Open Redirect via bypass of a properly implemented allow list. The main method impacted is `res.location()` but this is also called from within `res.redirect()`. The vulnerability is fixed in 4.19.2 and 5.0.0-beta.3.", - "severity": "Medium", - "components": { - "npm://express:4.18.2": { - "fixed_versions": [ - "[4.19.2]", - "[5.0.0-beta.3]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://express:4.18.2" - } - ] - ] - } - }, - "issue_id": "XRAY-594935", - "references": [ - "https://github.com/koajs/koa/issues/1800", - "https://github.com/expressjs/express/pull/5539", - "https://github.com/expressjs/express/commit/0b746953c4bd8e377123527db11f9cd866e39f94", - "https://github.com/expressjs/express/commit/0867302ddbde0e9463d0564fea5861feb708c2dd", - "https://github.com/advisories/GHSA-rv95-896h-c2vc", - "https://expressjs.com/en/4x/api.html#res.location", - "https://nvd.nist.gov/vuln/detail/CVE-2024-29041", - "https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc" - ] - }, - { - "cves": [ - { - "cve": "CVE-2022-29078", - "cvss_v2_score": "7.5", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:L/Au:N/C:P/I:P/A:P", - "cvss_v3_score": "9.8", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "cwe": [ - "CWE-94", - "CWE-74" - ], - "cwe_details": { - "CWE-74": { - "name": "Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')", - "description": "The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component." - }, - "CWE-94": { - "name": "Improper Control of Generation of Code ('Code Injection')", - "description": "The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.", - "categories": [ - { - "category": "2023 CWE Top 25", - "rank": "23" - } - ] - } - } - } - ], - "summary": "The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).", - "severity": "Critical", - "components": { - "npm://ejs:3.1.6": { - "fixed_versions": [ - "[3.1.7]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://ejs:3.1.6" - } - ] - ] - } - }, - "issue_id": "XRAY-209002", - "references": [ - "https://github.com/mde/ejs/commit/15ee698583c98dadc456639d6245580d17a24baf", - "https://eslam.io/posts/ejs-server-side-template-injection-rce/", - "https://security.netapp.com/advisory/ntap-20220804-0001", - "https://github.com/mde/ejs/releases", - "https://nvd.nist.gov/vuln/detail/CVE-2022-29078", - "https://eslam.io/posts/ejs-server-side-template-injection-rce", - "https://github.com/mde/ejs", - "https://security.netapp.com/advisory/ntap-20220804-0001/" - ], - "extended_information": { - "short_description": "Insufficient input validation in EJS enables attackers to perform template injection when attacker can control the rendering options.", - "full_description": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to perform template injection on the `opts.outputFunctionName` variable, since the variable is injected into the template body without any escaping. Although it is unlikely that the attacker can directly control the `outputFunctionName` property, it is possible that it can be influenced in conjunction with a prototype pollution vulnerability.\n\nOnce template injection is achieved, the attacker can immediately perform remote code execution since the template engine (EJS) allows executing arbitrary JavaScript code.\n\nExample of a vulnerable Node.js application -\n```js\nconst express = require('express');\nconst bodyParser = require('body-parser');\nconst lodash = require('lodash');\nconst ejs = require('ejs');\n\nconst app = express();\n\napp\n .use(bodyParser.urlencoded({extended: true}))\n .use(bodyParser.json());\n\napp.set('views', './');\napp.set('view engine', 'ejs');\n\napp.get(\"/\", (req, res) =\u003e {\n res.render('index');\n});\n\napp.post(\"/\", (req, res) =\u003e {\n let data = {};\n let input = JSON.parse(req.body.content);\n lodash.defaultsDeep(data, input);\n res.json({message: \"OK\"});\n});\n\nlet server = app.listen(8086, '0.0.0.0', function() {\n console.log('Listening on port %d', server.address().port);\n});\n```\n\nExploiting the above example for RCE -\n`curl 127.0.0.1:8086 -v --data 'content={\"constructor\": {\"prototype\": {\"outputFunctionName\": \"a; return global.process.mainModule.constructor._load(\\\"child_process\\\").execSync(\\\"whoami\\\"); //\"}}}'\n`\n\nDue to the prototype pollution in the `lodash.defaultsDeep` call, an attacker can inject the `outputFunctionName` property with an arbitrary value. The chosen value executes an arbitrary process via the `child_process` module.", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The attacker has to find a way to get their malicious input to `opts.outputFunctionName`, which will usually require exploitation of a prototype pollution vulnerability somewhere else in the code. However, there could be cases where the attacker can pass malicious data to the render function directly because of design problems in other code using EJS.", - "is_positive": true - }, - { - "name": "The issue has an exploit published", - "description": "There are multiple examples of exploits for this vulnerability online." - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Successful exploitation of this vulnerability leads to remote code execution." - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\nNote that this mitigation is supposed to stop any prototype pollution attacks which can allow an attacker to control the `opts.outputFunctionName` parameter indirectly.\n\nThe mitigation will not stop any (extremely unlikely) scenarios where the JavaScript code allows external input to directly affect `opts.outputFunctionName`." - } - }, - { - "cves": [ - { - "cve": "CVE-2024-33883", - "cvss_v3_score": "4.0", - "cvss_v3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "cwe": [ - "CWE-1321", - "CWE-693" - ], - "cwe_details": { - "CWE-1321": { - "name": "Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')", - "description": "The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype." - }, - "CWE-693": { - "name": "Protection Mechanism Failure", - "description": "The product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product." - } - } - } - ], - "summary": "The ejs (aka Embedded JavaScript templates) package before 3.1.10 for Node.js lacks certain pollution protection.", - "severity": "Medium", - "components": { - "npm://ejs:3.1.6": { - "fixed_versions": [ - "[3.1.10]" - ], - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://ejs:3.1.6" - } - ] - ] - } - }, - "issue_id": "XRAY-599735", - "references": [ - "https://security.netapp.com/advisory/ntap-20240605-0003/", - "https://security.netapp.com/advisory/ntap-20240605-0003", - "https://github.com/mde/ejs/commit/e469741dca7df2eb400199e1cdb74621e3f89aa5", - "https://github.com/mde/ejs/compare/v3.1.9...v3.1.10", - "https://github.com/advisories/GHSA-ghr5-ch3p-vcr6", - "https://nvd.nist.gov/vuln/detail/CVE-2024-33883" - ], - "extended_information": { - "short_description": "Insufficient input validation in EJS may lead to prototype pollution.", - "full_description": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as `EJS`, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nA prototype pollution gadget within the EJS template engine could potentially be leveraged by attackers to achieve remote code execution or DoS via prototype pollution.\n\n```\nfunction Template(text, opts) {\n opts = opts || utils.createNullProtoObjWherePossible();\n```\n\nWhen checking for the presence of a property within an object variable, the lookup scope isn't explicitly defined. In JavaScript, the absence of a defined lookup scope prompts a search up to the root prototype (`Object.prototype`). This could potentially be under the control of an attacker if another prototype pollution vulnerability is present within the application.\n\nIf the application server is using the EJS as the backend template engine, and there is another prototype pollution vulnerability in the application, then the attacker could leverage the found gadgets in the EJS template engine to escalate the prototype pollution to remote code execution or DoS.\n\nThe following code will execute a command on the server by polluting `opts.escapeFunction`:\n \n```\nconst express = require('express');\nconst app = express();\nconst port = 8008;\nconst ejs = require('ejs');\n\n// Set EJS as the view engine\napp.set('view engine', 'ejs');\n\napp.get('/', (req, res) =\u003e {\n \n const data = {title: 'Welcome', message: 'Hello'};\n\n // Sample EJS template string\n const templateString = `\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\u003c%= title %\u003e\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003ch1\u003e\u003c%= message %\u003e\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e`;\n\n const { exec } = require('child_process');\n\n function myFunc() {\n exec('bash -c \"echo 123\"', (error, stdout, stderr) =\u003e {\n if (error) {\n console.error(`exec error: ${error}`);\n return;\n }\n if (stderr){\n console.log(`stderr : ${stderr}`);\n return;\n }\n // Handle success\n console.log(`Command executed successfully. Output: ${stdout}`);\n });\n }\n\n const options = {client:false};\n\n Object.prototype.escapeFunction = myFunc;\n \n const compiledTemplate = ejs.compile(templateString, options);\n const renderedHtml = compiledTemplate(data);\n res.send(renderedHtml);\n});\n\n// Start the server\napp.listen(port, () =\u003e {\n console.log(`Server is running on http://localhost:${port}`);\n});\n```", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "Attackers can only leverage this vulnerability when the application server is using the EJS as the backend template engine. Moreover, there must be a second prototype pollution vulnerability in the application.", - "is_positive": true - }, - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "CVSS does not take into account the unlikely prerequisites necessary for exploitation.", - "is_positive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "A prototype pollution attack allows the attacker to inject new properties into all JavaScript objects.\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable." - } - ] - } - }, - { - "cves": [ - { - "cve": "CVE-2023-29827", - "cvss_v3_score": "9.8", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "cwe": [ - "CWE-74" - ], - "cwe_details": { - "CWE-74": { - "name": "Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')", - "description": "The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component." - } - } - } - ], - "summary": "ejs v3.1.9 is vulnerable to server-side template injection. If the ejs file is controllable, template injection can be implemented through the configuration settings of the closeDelimiter parameter. NOTE: this is disputed by the vendor because the render function is not intended to be used with untrusted input.", - "severity": "Critical", - "components": { - "npm://ejs:3.1.6": { - "impact_paths": [ - [ - { - "component_id": "npm://froghome:1.0.0" - }, - { - "component_id": "npm://ejs:3.1.6" - } - ] - ] - } - }, - "issue_id": "XRAY-520200", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2023-29827", - "https://github.com/mde/ejs/issues/720", - "https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities" - ], - "extended_information": { - "short_description": "Insufficient input validation can lead to template injection in ejs when attackers can control both the rendered template and rendering options.", - "full_description": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to bypass ejs' template injection restrictions, by abusing the `closeDelimiter` rendering option, in the case when -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\n\nThe vulnerability was **rightfully disputed** due to the fact that a vulnerable configuration is extremely unlikely to exist in any real-world setup. As such, the maintainers will not provide a fix for this (non-)issue.\n\nExample of a vulnerable application -\n```js\nconst express = require('express')\nconst app = express()\nconst port = 3000\n\napp.set('view engine', 'ejs');\n\napp.get('/page', (req,res) =\u003e {\n res.render('page', req.query); // OPTS (2nd parameter) IS ATTACKER-CONTROLLED\n})\n\napp.listen(port, () =\u003e {\n console.log(\"Example app listening on port ${port}\")\n})\n```\n\nContents of `page.ejs` (very unlikely to be attacker controlled) -\n```js\n%%1\");process.mainModule.require('child_process').execSync('calc');//\n```\n\nIn this case, sending `closeDelimiter` with the same malicious code that already exists at `page.ejs` will trigger the injection -\n`http://127.0.0.1:3000/page?settings[view%20options][closeDelimiter]=1\")%3bprocess.mainModule.require('child_process').execSync('calc')%3b//`", - "jfrog_research_severity": "Low", - "jfrog_research_severity_reasons": [ - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The CVSS does not take into account the rarity of a vulnerable configuration to exist", - "is_positive": true - }, - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The vulnerability can be exploited only under the following conditions -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\nThis vulnerable configuration is extremely unlikely to exist in any real-world setup.", - "is_positive": true - }, - { - "name": "The issue has been disputed by the vendor", - "is_positive": true - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates template injection" - } - ] - } - } - ], - "component_id": "root", - "package_type": "generic", - "status": "completed" - } - ] - }, - "jas_scans": { - "contextual_analysis": [ - { - "tool": { - "driver": { - "informationUri": "https://jfrog.com/help/r/jfrog-security-documentation/jfrog-advanced-security", - "name": "JFrog Applicability Scanner", - "rules": [ - { - "id": "applic_CVE-2018-16487", - "name": "CVE-2018-16487", - "shortDescription": { - "text": "Scanner for CVE-2018-16487" - }, - "fullDescription": { - "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.merge` with external input to its 2nd (`sources`) argument.\n* `lodash.mergeWith` with external input to its 2nd (`sources`) argument.\n* `lodash.defaultsDeep` with external input to its 2nd (`sources`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.", - "markdown": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.merge` with external input to its 2nd (`sources`) argument.\n* `lodash.mergeWith` with external input to its 2nd (`sources`) argument.\n* `lodash.defaultsDeep` with external input to its 2nd (`sources`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2019-10744", - "name": "CVE-2019-10744", - "shortDescription": { - "text": "Scanner for CVE-2019-10744" - }, - "fullDescription": { - "text": "The scanner checks whether the vulnerable function `defaultsDeep` is called with external input to its 2nd (`sources`) argument, and the `Object.freeze()` remediation is not present.", - "markdown": "The scanner checks whether the vulnerable function `defaultsDeep` is called with external input to its 2nd (`sources`) argument, and the `Object.freeze()` remediation is not present." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2020-28500", - "name": "CVE-2020-28500", - "shortDescription": { - "text": "Scanner for CVE-2020-28500" - }, - "fullDescription": { - "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.trim` with external input to its 1st (`string`) argument.\n* `lodash.toNumber` with external input to its 1st (`value`) argument.\n* `lodash.trimEnd` with external input to its 1st (`string`) argument.", - "markdown": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.trim` with external input to its 1st (`string`) argument.\n* `lodash.toNumber` with external input to its 1st (`value`) argument.\n* `lodash.trimEnd` with external input to its 1st (`string`) argument." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2020-8203", - "name": "CVE-2020-8203", - "shortDescription": { - "text": "Scanner for CVE-2020-8203" - }, - "fullDescription": { - "text": "The scanner checks whether the vulnerable function `zipObjectDeep` is called with external input to its 1st (`props`) and 2nd (`values`) arguments, and the `Object.freeze()` remediation is not present.", - "markdown": "The scanner checks whether the vulnerable function `zipObjectDeep` is called with external input to its 1st (`props`) and 2nd (`values`) arguments, and the `Object.freeze()` remediation is not present." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2021-23337", - "name": "CVE-2021-23337", - "shortDescription": { - "text": "Scanner for CVE-2021-23337" - }, - "fullDescription": { - "text": "The scanner checks whether the vulnerable function `lodash.template` is called with external input to its 2nd (`options`) argument.", - "markdown": "The scanner checks whether the vulnerable function `lodash.template` is called with external input to its 2nd (`options`) argument." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2022-29078", - "name": "CVE-2022-29078", - "shortDescription": { - "text": "Scanner for CVE-2022-29078" - }, - "fullDescription": { - "text": "The scanner checks for two vulnerable flows:\n\n1. Whether the `express.set` function is called with the arguments: `view engine` and `ejs`, or external input and if it's followed by a call to the vulnerable function `render` with an unknown second argument.\n\n2. Whether the `renderFile` function is called with an unknown second argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.", - "markdown": "The scanner checks for two vulnerable flows:\n\n1. Whether the `express.set` function is called with the arguments: `view engine` and `ejs`, or external input and if it's followed by a call to the vulnerable function `render` with an unknown second argument.\n\n2. Whether the `renderFile` function is called with an unknown second argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2024-33883", - "name": "CVE-2024-33883", - "shortDescription": { - "text": "Scanner for CVE-2024-33883" - }, - "fullDescription": { - "text": "The scanner checks whether the vulnerable function `ejs.compile()` is called.", - "markdown": "The scanner checks whether the vulnerable function `ejs.compile()` is called." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2023-29827", - "name": "CVE-2023-29827", - "shortDescription": { - "text": "Scanner for CVE-2023-29827" - }, - "fullDescription": { - "text": "The scanner checks whether any of the following conditions are met:\n\n1. The `ejs.renderFile` function is called with an unknown third argument.\n\n2. The `ejs.compile` function is called with an unknown second argument.\n\n3. The `express.set` function is called with any of the following arguments:\n\n* `express.set(\"view engine\", \"ejs\")`\n* `express.set(\"view engine\", {USER_INPUT})`\n* `express.set({USER_INPUT}, \"ejs\")`\n* `express.set({USER_INPUT}, {USER_INPUT})`", - "markdown": "The scanner checks whether any of the following conditions are met:\n\n1. The `ejs.renderFile` function is called with an unknown third argument.\n\n2. The `ejs.compile` function is called with an unknown second argument.\n\n3. The `express.set` function is called with any of the following arguments:\n\n* `express.set(\"view engine\", \"ejs\")`\n* `express.set(\"view engine\", {USER_INPUT})`\n* `express.set({USER_INPUT}, \"ejs\")`\n* `express.set({USER_INPUT}, {USER_INPUT})`" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2018-3721", - "name": "CVE-2018-3721", - "shortDescription": { - "text": "Scanner for uncovered CVE-2018-3721" - }, - "fullDescription": { - "text": "", - "markdown": "" - }, - "properties": { - "applicability": "not_covered" - } - }, - { - "id": "applic_CVE-2019-1010266", - "name": "CVE-2019-1010266", - "shortDescription": { - "text": "Scanner for uncovered CVE-2019-1010266" - }, - "fullDescription": { - "text": "", - "markdown": "" - }, - "properties": { - "applicability": "not_covered" - } - }, - { - "id": "applic_CVE-2024-39249", - "name": "CVE-2024-39249", - "shortDescription": { - "text": "Scanner for uncovered CVE-2024-39249" - }, - "fullDescription": { - "text": "Never applicable. The vulnerability is exploitable only if an attacker has access to the source code.", - "markdown": "Never applicable. The vulnerability is exploitable only if an attacker has access to the source code." - }, - "properties": { - "applicability": "not_covered", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2024-29041", - "name": "CVE-2024-29041", - "shortDescription": { - "text": "Scanner for uncovered CVE-2024-29041" - }, - "fullDescription": { - "text": "", - "markdown": "" - }, - "properties": { - "applicability": "not_covered" - } - }, - { - "id": "applic_CVE-2024-39249", - "name": "CVE-2024-39249", - "shortDescription": { - "text": "Scanner for indirect dependency CVE-2024-39249" - }, - "fullDescription": { - "text": "Never applicable. The vulnerability is exploitable only if an attacker has access to the source code.", - "markdown": "Never applicable. The vulnerability is exploitable only if an attacker has access to the source code." - }, - "properties": { - "applicability": "not_applicable" - } - } - ], - "version": "1.0" - } - }, - "invocations": [ - { - "arguments": [ - "analyzerManager/jas_scanner/jas_scanner", - "scan", - "/Applicability_1725803037/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" - } - } - ], - "results": [ - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2018-16487", - "message": { - "text": "Prototype pollution `Object.freeze` remediation was detected" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": { - "text": "Object.freeze(Object.prototype)" - } - } - } - } - ] - }, - { - "ruleId": "applic_CVE-2018-16487", - "kind": "pass", - "message": { - "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.merge` with external input to its 2nd (`sources`) argument.\n* `lodash.mergeWith` with external input to its 2nd (`sources`) argument.\n* `lodash.defaultsDeep` with external input to its 2nd (`sources`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present." - } - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2019-10744", - "message": { - "text": "Prototype pollution `Object.freeze` remediation was detected" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": { - "text": "Object.freeze(Object.prototype)" - } - } - } - } - ] - }, - { - "ruleId": "applic_CVE-2019-10744", - "kind": "pass", - "message": { - "text": "The scanner checks whether the vulnerable function `defaultsDeep` is called with external input to its 2nd (`sources`) argument, and the `Object.freeze()` remediation is not present." - } - }, - { - "ruleId": "applic_CVE-2020-28500", - "kind": "pass", - "message": { - "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.trim` with external input to its 1st (`string`) argument.\n* `lodash.toNumber` with external input to its 1st (`value`) argument.\n* `lodash.trimEnd` with external input to its 1st (`string`) argument." - } - }, - { - "ruleId": "applic_CVE-2020-8203", - "kind": "pass", - "message": { - "text": "The scanner checks whether the vulnerable function `zipObjectDeep` is called with external input to its 1st (`props`) and 2nd (`values`) arguments, and the `Object.freeze()` remediation is not present." - } - }, - { - "ruleId": "applic_CVE-2021-23337", - "kind": "pass", - "message": { - "text": "The scanner checks whether the vulnerable function `lodash.template` is called with external input to its 2nd (`options`) argument." - } - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2022-29078", - "message": { - "text": "Prototype pollution `Object.freeze` remediation was detected" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": { - "text": "Object.freeze(Object.prototype)" - } - } - } - } - ] - }, - { - "ruleId": "applic_CVE-2022-29078", - "kind": "pass", - "message": { - "text": "The scanner checks for two vulnerable flows:\n\n1. Whether the `express.set` function is called with the arguments: `view engine` and `ejs`, or external input and if it's followed by a call to the vulnerable function `render` with an unknown second argument.\n\n2. Whether the `renderFile` function is called with an unknown second argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present." - } - }, - { - "ruleId": "applic_CVE-2024-33883", - "kind": "pass", - "message": { - "text": "The scanner checks whether the vulnerable function `ejs.compile()` is called." - } - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2023-29827", - "message": { - "text": "The vulnerable functionality is triggered since express.set is called with 'view engine' as the first argument and 'ejs' as the second argument or both arguments with external input" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 14, - "startColumn": 1, - "endLine": 14, - "endColumn": 30, - "snippet": { - "text": "app.set('view engine', 'ejs')" - } - } - } - } - ] - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2023-29827", - "message": { - "text": "The vulnerable function render is called" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 26, - "startColumn": 3, - "endLine": 26, - "endColumn": 38, - "snippet": { - "text": "res.render('pages/index',req.query)" - } - } - } - } - ] - }, - { - "ruleId": "applic_CVE-2024-39249", - "kind": "pass", - "message": { - "text": "Never applicable. The vulnerability is exploitable only if an attacker has access to the source code." - } - } - ] - } - ], - "secrets": [ - { - "tool": { - "driver": { - "informationUri": "https://jfrog.com/help/r/jfrog-security-documentation/jfrog-advanced-security", - "name": "JFrog Secrets scanner", - "rules": [ - { - "id": "REQ.SECRET.GENERIC.TEXT", - "name": "REQ.SECRET.GENERIC.TEXT", - "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.TEXT" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive" - } - }, - { - "id": "REQ.SECRET.GENERIC.CODE", - "name": "REQ.SECRET.GENERIC.CODE", - "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.CODE" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive" - } - }, - { - "id": "REQ.SECRET.KEYS", - "name": "REQ.SECRET.KEYS", - "shortDescription": { - "text": "Scanner for REQ.SECRET.KEYS" - }, - "fullDescription": { - "text": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\u0026tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n", - "markdown": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\u0026tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - }, - { - "id": "REQ.CRED.PUBLIC-ONLY", - "name": "REQ.CRED.PUBLIC-ONLY", - "shortDescription": { - "text": "Scanner for REQ.CRED.PUBLIC-ONLY" - }, - "fullDescription": { - "text": "", - "markdown": "" - }, - "properties": { - "applicability": "undetermined", - "conclusion": "private" - } - }, - { - "id": "REQ.SECRET.GENERIC.URL-TEXT", - "name": "REQ.SECRET.GENERIC.URL-TEXT", - "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.URL-TEXT" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive" - } - } - ], - "version": "1.0" - } - }, - "invocations": [ - { - "arguments": [ - "analyzerManager/jas_scanner/jas_scanner", - "scan", - "/Secrets_1725803029/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" - } - } - ], - "results": [ - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.KEYS", - "message": { - "text": "Secret keys were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/fake-creds.txt" - }, - "region": { - "startLine": 2, - "startColumn": 1, - "endLine": 2, - "endColumn": 11, - "snippet": { - "text": "Sqc************" - } - } - } - } - ] - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.KEYS", - "message": { - "text": "Secret keys were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/fake-creds.txt" - }, - "region": { - "startLine": 3, - "startColumn": 1, - "endLine": 3, - "endColumn": 11, - "snippet": { - "text": "gho************" - } - } - } - } - ] - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.KEYS", - "message": { - "text": "Secret keys were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 11, - "startColumn": 14, - "endLine": 11, - "endColumn": 24, - "snippet": { - "text": "Sqc************" - } - } - } - } - ] - } - ] - } - ], - "iac": [ - { - "tool": { - "driver": { - "informationUri": "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/infrastructure-as-code-iac", - "name": "JFrog Terraform scanner", - "rules": [], - "version": "1.8.14" - } - }, - "invocations": [ - { - "arguments": [ - "analyzerManager/iac_scanner/tf_scanner", - "scan", - "/IaC_1725803037/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" - } - } - ], - "results": [] - } - ], - "sast": [ - { - "tool": { - "driver": { - "informationUri": "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/sast", - "name": "🐸 JFrog SAST", - "rules": [ - { - "id": "js-express-without-helmet", - "shortDescription": { - "text": "Express Not Using Helmet" - }, - "fullDescription": { - "text": "\n### Overview\nHelmet library should be used when using Express in order to properly configure\nHTTP header settings to mitigate a range of well-known vulnerabilities.\n\n### Remediation\n```javascript\nconst helmet = require(\"helmet\");\nconst app = express()\n\napp.use(helmet())\n```\n\n### References\n[Best practices for Express](https://expressjs.com/en/advanced/best-practice-security.html)\n", - "markdown": "\n### Overview\nHelmet library should be used when using Express in order to properly configure\nHTTP header settings to mitigate a range of well-known vulnerabilities.\n\n### Remediation\n```javascript\nconst helmet = require(\"helmet\");\nconst app = express()\n\napp.use(helmet())\n```\n\n### References\n[Best practices for Express](https://expressjs.com/en/advanced/best-practice-security.html)\n" - }, - "defaultConfiguration": { - "parameters": { - "properties": { - "CWE": "693" - } - } - }, - "properties": { - "security-severity": "3.9" - } - }, - { - "id": "js-insecure-random", - "shortDescription": { - "text": "Use of Insecure Random" - }, - "fullDescription": { - "text": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n", - "markdown": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n" - }, - "defaultConfiguration": { - "parameters": { - "properties": { - "CWE": "338" - } - } - }, - "properties": { - "security-severity": "3.9" - } - }, - { - "id": "js-template-injection", - "shortDescription": { - "text": "Template Object Injection" - }, - "fullDescription": { - "text": "\n### Overview\nTemplate Object Injection (TOI) is a vulnerability that can occur in\nweb applications that use template engines to render dynamic content.\nTemplate engines are commonly used to generate HTML pages, emails, or\nother types of documents that include variable data. TOI happens when\nuntrusted user input is included as part of the template rendering\nprocess, and the template engine evaluates the input as a code\nexpression, leading to potential code injection or data tampering\nattacks. To prevent TOI vulnerabilities, it's important to sanitize and\nvalidate all user input that is used as part of the template rendering\nprocess.\n\n### Query operation\nIn this query we look for user inputs that flow directly to a\nrequest render.\n\n### Vulnerable example\n```javascript\nvar app = require('express')();\napp.set('view engine', 'hbs');\n\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n res.render('template', bodyParameter);\n});\n```\nIn this example, a user-provided data is injected directly into the\n`render` command, leading to potential code injection or data\ntampering attacks.\n\n### Remediation\n```diff\n+ const sanitizeHtml = require('sanitize-html');\nvar app = require('express')();\napp.set('view engine', 'hbs');\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n\n- res.render('template', bodyParameter);\n+ res.render('template', sanitizeHtml(bodyParameter));\n});\nUsing `sanitize-html`, the user-provided data is sanitized, before\nrendering to the response.\n```\n", - "markdown": "\n### Overview\nTemplate Object Injection (TOI) is a vulnerability that can occur in\nweb applications that use template engines to render dynamic content.\nTemplate engines are commonly used to generate HTML pages, emails, or\nother types of documents that include variable data. TOI happens when\nuntrusted user input is included as part of the template rendering\nprocess, and the template engine evaluates the input as a code\nexpression, leading to potential code injection or data tampering\nattacks. To prevent TOI vulnerabilities, it's important to sanitize and\nvalidate all user input that is used as part of the template rendering\nprocess.\n\n### Query operation\nIn this query we look for user inputs that flow directly to a\nrequest render.\n\n### Vulnerable example\n```javascript\nvar app = require('express')();\napp.set('view engine', 'hbs');\n\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n res.render('template', bodyParameter);\n});\n```\nIn this example, a user-provided data is injected directly into the\n`render` command, leading to potential code injection or data\ntampering attacks.\n\n### Remediation\n```diff\n+ const sanitizeHtml = require('sanitize-html');\nvar app = require('express')();\napp.set('view engine', 'hbs');\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n\n- res.render('template', bodyParameter);\n+ res.render('template', sanitizeHtml(bodyParameter));\n});\nUsing `sanitize-html`, the user-provided data is sanitized, before\nrendering to the response.\n```\n" - }, - "defaultConfiguration": { - "parameters": { - "properties": { - "CWE": "73" - } - } - }, - "properties": { - "security-severity": "8.9" - } - } - ], - "version": "1.8.14" - } - }, - "invocations": [ - { - "arguments": [ - "analyzerManager/zd_scanner/scanner", - "scan", - "/Sast_1725803040/results.sarif", - "/Sast_1725803040/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" - } - } - ], - "results": [ - { - "ruleId": "js-template-injection", - "level": "error", - "message": { - "text": "Template Object Injection" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 26, - "startColumn": 28, - "endLine": 26, - "endColumn": 37, - "snippet": { - "text": "req.query" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - ], - "fingerprints": { - "precise_sink_and_sink_function": "a549106dc43cdc0d36b0f81d0465a5d2" - }, - "codeFlows": [ - { - "threadFlows": [ - { - "locations": [ - { - "location": { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 21, - "startColumn": 23, - "endLine": 21, - "endColumn": 26, - "snippet": { - "text": "req" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - }, - { - "location": { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 26, - "startColumn": 28, - "endLine": 26, - "endColumn": 31, - "snippet": { - "text": "req" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - }, - { - "location": { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 26, - "startColumn": 28, - "endLine": 26, - "endColumn": 37, - "snippet": { - "text": "req.query" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - } - ] - } - ] - } - ] - }, - { - "ruleId": "js-insecure-random", - "level": "note", - "message": { - "text": "Use of Insecure Random" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/public/js/bootstrap.js" - }, - "region": { - "startLine": 136, - "startColumn": 22, - "endLine": 136, - "endColumn": 35, - "snippet": { - "text": "Math.random()" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "public.js.bootstrap.^_0.Util.getUID" - } - ] - } - ], - "fingerprints": { - "precise_sink_and_sink_function": "34331455b0d5edf4c232dd288225780e" - } - }, - { - "ruleId": "js-insecure-random", - "level": "note", - "message": { - "text": "Use of Insecure Random" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/public/js/bootstrap.bundle.js" - }, - "region": { - "startLine": 135, - "startColumn": 22, - "endLine": 135, - "endColumn": 35, - "snippet": { - "text": "Math.random()" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "public.js.bootstrap\u003cdot\u003ebundle.^_0.Util.getUID" - } - ] - } - ], - "fingerprints": { - "precise_sink_and_sink_function": "281a027677521fa64de4ce1fe14e01ab" - } - }, - { - "ruleId": "js-express-without-helmet", - "level": "note", - "message": { - "text": "Express Not Using Helmet" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///Users/user/ejs-frog-demo/server.js" - }, - "region": { - "startLine": 8, - "startColumn": 11, - "endLine": 8, - "endColumn": 20, - "snippet": { - "text": "express()" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server" - } - ] - } - ], - "fingerprints": { - "precise_sink_and_sink_function": "f8caf6a43a2c1eb41369843ca3c7d94c" - } - } - ] - } - ] - } - } - ] -} \ No newline at end of file diff --git a/tests/testdata/output/audit/audit_sarif.json b/tests/testdata/output/audit/audit_sarif.json index 56c0808a..620a8aad 100644 --- a/tests/testdata/output/audit/audit_sarif.json +++ b/tests/testdata/output/audit/audit_sarif.json @@ -5,141 +5,117 @@ { "tool": { "driver": { - "informationUri": "https://jfrog.com/help/r/jfrog-security-documentation/jfrog-advanced-security", - "name": "JFrog Secrets scanner", + "informationUri": "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/sca", + "name": "JFrog Xray Scanner", "rules": [ { - "id": "REQ.SECRET.GENERIC.TEXT", + "id": "CVE-2018-3721_lodash_4.17.0", "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.TEXT" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" + "text": "[CVE-2018-3721] lodash 4.17.0" }, "help": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" + "text": "Improperly Controlled Modification of Object", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 6.9 | Not Covered | `lodash 4.17.0` | [4.17.5] |" }, "properties": { - "applicability": "not_applicable", - "conclusion": "positive" + "security-severity": "6.9" } }, { - "id": "REQ.SECRET.GENERIC.CODE", + "id": "CVE-2024-39249_async_3.2.4", "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.CODE" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" + "text": "[CVE-2024-39249] async 3.2.4" }, "help": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" + "text": "Async vulnerable to ReDoS", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 0.0 | Applicable | `jake 10.8.7` | No fix available |" }, "properties": { - "applicability": "not_applicable", - "conclusion": "positive" + "security-severity": "0.0" } }, { - "id": "REQ.SECRET.KEYS", + "id": "CVE-2020-8203_lodash_4.17.0", "shortDescription": { - "text": "Scanner for REQ.SECRET.KEYS" - }, - "fullDescription": { - "text": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n", - "markdown": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" + "text": "[CVE-2020-8203] lodash 4.17.0" }, "help": { - "text": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n", - "markdown": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" + "text": "Code Injection", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.4 | Not Applicable | `lodash 4.17.0` | [4.17.19] |" }, "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" + "security-severity": "7.4" } }, { - "id": "REQ.CRED.PUBLIC-ONLY", + "id": "CVE-2020-8203_ejs_3.1.6", "shortDescription": { - "text": "Scanner for REQ.CRED.PUBLIC-ONLY" - }, - "fullDescription": { - "text": "", - "markdown": "" + "text": "[CVE-2020-8203] ejs 3.1.6" }, "help": { - "text": "", - "markdown": "" + "text": "Code Injection", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.4 | Not Applicable | `lodash 4.17.0` | [3.1.7] |" }, "properties": { - "applicability": "undetermined", - "conclusion": "private" + "security-severity": "7.4" } }, { - "id": "REQ.SECRET.GENERIC.URL-TEXT", + "id": "CVE-2018-16487_lodash_4.17.0", "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.URL-TEXT" + "text": "[CVE-2018-16487] lodash 4.17.0" }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" + "help": { + "text": "Prototype Pollution", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 6.9 | Not Applicable | `lodash 4.17.0` | [4.17.11] |" + }, + "properties": { + "security-severity": "6.9" + } + }, + { + "id": "MIT_lodash_4.17.0", + "shortDescription": { + "text": "[MIT] in lodash 4.17.0 (license-watch)" }, "help": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" + "text": "Dependency lodash version 4.17.0 is using a license (MIT) that is not allowed.", + "markdown": "Dependency lodash version 4.17.0 is using a license (MIT) that is not allowed.\u003cbr/\u003eDirect dependencies:\u003cbr/\u003e`lodash 4.17.0`" }, "properties": { - "applicability": "not_applicable", - "conclusion": "positive" + "security-severity": "8.9" } } ], - "version": "1.0" + "version": "3.107.13" } }, "invocations": [ { - "arguments": [ - "analyzerManager/jas_scanner/jas_scanner", - "scan", - "/Secrets_1725867313/config.yaml" - ], "executionSuccessful": true, "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" + "uri": "Users/user/project-with-issues" } } ], "results": [ { "properties": { - "metadata": "", - "tokenValidation": "" + "applicability": "Applicable", + "fixedVersion": "No fix available" }, - "ruleId": "REQ.SECRET.KEYS", + "ruleId": "CVE-2024-39249_async_3.2.4", + "ruleIndex": 1, + "level": "none", "message": { - "text": "Secret keys were found" + "text": "[CVE-2024-39249] jake 10.8.7", + "markdown": "[CVE-2024-39249] jake 10.8.7" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "fake-creds.txt" - }, - "region": { - "startLine": 2, - "startColumn": 1, - "endLine": 2, - "endColumn": 11, - "snippet": { - "text": "Sqc************" - } + "uri": "package.json" } } } @@ -147,27 +123,21 @@ }, { "properties": { - "metadata": "", - "tokenValidation": "" + "applicability": "Not Applicable", + "fixedVersion": "[4.17.19]" }, - "ruleId": "REQ.SECRET.KEYS", + "ruleId": "CVE-2020-8203_lodash_4.17.0", + "ruleIndex": 2, + "level": "error", "message": { - "text": "Secret keys were found" + "text": "[CVE-2020-8203] lodash 4.17.0", + "markdown": "[CVE-2020-8203] lodash 4.17.0" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "fake-creds.txt" - }, - "region": { - "startLine": 3, - "startColumn": 1, - "endLine": 3, - "endColumn": 11, - "snippet": { - "text": "gho************" - } + "uri": "package.json" } } } @@ -175,850 +145,131 @@ }, { "properties": { - "metadata": "", - "tokenValidation": "" + "applicability": "Not Applicable", + "fixedVersion": "[3.1.7]" }, - "ruleId": "REQ.SECRET.KEYS", + "ruleId": "CVE-2020-8203_ejs_3.1.6", + "ruleIndex": 3, + "level": "error", "message": { - "text": "Secret keys were found" + "text": "[CVE-2020-8203] lodash 4.17.0", + "markdown": "[CVE-2020-8203] lodash 4.17.0" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "server.js" - }, - "region": { - "startLine": 11, - "startColumn": 14, - "endLine": 11, - "endColumn": 24, - "snippet": { - "text": "Sqc************" - } + "uri": "package.json" } } } ] - } - ] - }, - { - "tool": { - "driver": { - "informationUri": "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/infrastructure-as-code-iac", - "name": "JFrog Terraform scanner", - "rules": [], - "version": "1.8.14" - } - }, - "invocations": [ + }, { - "arguments": [ - "analyzerManager/iac_scanner/tf_scanner", - "scan", - "/IaC_1725867328/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" - } - } - ], - "results": [] - }, - { - "tool": { - "driver": { - "informationUri": "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/sast", - "name": "🐸 JFrog SAST", - "rules": [ - { - "id": "js-express-without-helmet", - "shortDescription": { - "text": "Express Not Using Helmet" - }, - "fullDescription": { - "text": "\n### Overview\nHelmet library should be used when using Express in order to properly configure\nHTTP header settings to mitigate a range of well-known vulnerabilities.\n\n### Remediation\n```javascript\nconst helmet = require(\"helmet\");\nconst app = express()\n\napp.use(helmet())\n```\n\n### References\n[Best practices for Express](https://expressjs.com/en/advanced/best-practice-security.html)\n", - "markdown": "\n### Overview\nHelmet library should be used when using Express in order to properly configure\nHTTP header settings to mitigate a range of well-known vulnerabilities.\n\n### Remediation\n```javascript\nconst helmet = require(\"helmet\");\nconst app = express()\n\napp.use(helmet())\n```\n\n### References\n[Best practices for Express](https://expressjs.com/en/advanced/best-practice-security.html)\n" - }, - "defaultConfiguration": { - "parameters": { - "properties": { - "CWE": "693" - } - } - }, - "help": { - "text": "\n### Overview\nHelmet library should be used when using Express in order to properly configure\nHTTP header settings to mitigate a range of well-known vulnerabilities.\n\n### Remediation\n```javascript\nconst helmet = require(\"helmet\");\nconst app = express()\n\napp.use(helmet())\n```\n\n### References\n[Best practices for Express](https://expressjs.com/en/advanced/best-practice-security.html)\n", - "markdown": "\n### Overview\nHelmet library should be used when using Express in order to properly configure\nHTTP header settings to mitigate a range of well-known vulnerabilities.\n\n### Remediation\n```javascript\nconst helmet = require(\"helmet\");\nconst app = express()\n\napp.use(helmet())\n```\n\n### References\n[Best practices for Express](https://expressjs.com/en/advanced/best-practice-security.html)\n" - }, - "properties": { - "security-severity": "3.9" - } - }, - { - "id": "js-insecure-random", - "shortDescription": { - "text": "Use of Insecure Random" - }, - "fullDescription": { - "text": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n", - "markdown": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n" - }, - "defaultConfiguration": { - "parameters": { - "properties": { - "CWE": "338" - } - } - }, - "help": { - "text": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n", - "markdown": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n" - }, - "properties": { - "security-severity": "3.9" - } - }, + "properties": { + "applicability": "Not Applicable", + "fixedVersion": "[4.17.11]" + }, + "ruleId": "CVE-2018-16487_lodash_4.17.0", + "ruleIndex": 4, + "level": "warning", + "message": { + "text": "[CVE-2018-16487] lodash 4.17.0", + "markdown": "[CVE-2018-16487] lodash 4.17.0" + }, + "locations": [ { - "id": "js-template-injection", - "shortDescription": { - "text": "Template Object Injection" - }, - "fullDescription": { - "text": "\n### Overview\nTemplate Object Injection (TOI) is a vulnerability that can occur in\nweb applications that use template engines to render dynamic content.\nTemplate engines are commonly used to generate HTML pages, emails, or\nother types of documents that include variable data. TOI happens when\nuntrusted user input is included as part of the template rendering\nprocess, and the template engine evaluates the input as a code\nexpression, leading to potential code injection or data tampering\nattacks. To prevent TOI vulnerabilities, it's important to sanitize and\nvalidate all user input that is used as part of the template rendering\nprocess.\n\n### Query operation\nIn this query we look for user inputs that flow directly to a\nrequest render.\n\n### Vulnerable example\n```javascript\nvar app = require('express')();\napp.set('view engine', 'hbs');\n\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n res.render('template', bodyParameter);\n});\n```\nIn this example, a user-provided data is injected directly into the\n`render` command, leading to potential code injection or data\ntampering attacks.\n\n### Remediation\n```diff\n+ const sanitizeHtml = require('sanitize-html');\nvar app = require('express')();\napp.set('view engine', 'hbs');\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n\n- res.render('template', bodyParameter);\n+ res.render('template', sanitizeHtml(bodyParameter));\n});\nUsing `sanitize-html`, the user-provided data is sanitized, before\nrendering to the response.\n```\n", - "markdown": "\n### Overview\nTemplate Object Injection (TOI) is a vulnerability that can occur in\nweb applications that use template engines to render dynamic content.\nTemplate engines are commonly used to generate HTML pages, emails, or\nother types of documents that include variable data. TOI happens when\nuntrusted user input is included as part of the template rendering\nprocess, and the template engine evaluates the input as a code\nexpression, leading to potential code injection or data tampering\nattacks. To prevent TOI vulnerabilities, it's important to sanitize and\nvalidate all user input that is used as part of the template rendering\nprocess.\n\n### Query operation\nIn this query we look for user inputs that flow directly to a\nrequest render.\n\n### Vulnerable example\n```javascript\nvar app = require('express')();\napp.set('view engine', 'hbs');\n\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n res.render('template', bodyParameter);\n});\n```\nIn this example, a user-provided data is injected directly into the\n`render` command, leading to potential code injection or data\ntampering attacks.\n\n### Remediation\n```diff\n+ const sanitizeHtml = require('sanitize-html');\nvar app = require('express')();\napp.set('view engine', 'hbs');\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n\n- res.render('template', bodyParameter);\n+ res.render('template', sanitizeHtml(bodyParameter));\n});\nUsing `sanitize-html`, the user-provided data is sanitized, before\nrendering to the response.\n```\n" - }, - "defaultConfiguration": { - "parameters": { - "properties": { - "CWE": "73" - } + "physicalLocation": { + "artifactLocation": { + "uri": "package.json" } - }, - "help": { - "text": "\n### Overview\nTemplate Object Injection (TOI) is a vulnerability that can occur in\nweb applications that use template engines to render dynamic content.\nTemplate engines are commonly used to generate HTML pages, emails, or\nother types of documents that include variable data. TOI happens when\nuntrusted user input is included as part of the template rendering\nprocess, and the template engine evaluates the input as a code\nexpression, leading to potential code injection or data tampering\nattacks. To prevent TOI vulnerabilities, it's important to sanitize and\nvalidate all user input that is used as part of the template rendering\nprocess.\n\n### Query operation\nIn this query we look for user inputs that flow directly to a\nrequest render.\n\n### Vulnerable example\n```javascript\nvar app = require('express')();\napp.set('view engine', 'hbs');\n\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n res.render('template', bodyParameter);\n});\n```\nIn this example, a user-provided data is injected directly into the\n`render` command, leading to potential code injection or data\ntampering attacks.\n\n### Remediation\n```diff\n+ const sanitizeHtml = require('sanitize-html');\nvar app = require('express')();\napp.set('view engine', 'hbs');\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n\n- res.render('template', bodyParameter);\n+ res.render('template', sanitizeHtml(bodyParameter));\n});\nUsing `sanitize-html`, the user-provided data is sanitized, before\nrendering to the response.\n```\n", - "markdown": "\n### Overview\nTemplate Object Injection (TOI) is a vulnerability that can occur in\nweb applications that use template engines to render dynamic content.\nTemplate engines are commonly used to generate HTML pages, emails, or\nother types of documents that include variable data. TOI happens when\nuntrusted user input is included as part of the template rendering\nprocess, and the template engine evaluates the input as a code\nexpression, leading to potential code injection or data tampering\nattacks. To prevent TOI vulnerabilities, it's important to sanitize and\nvalidate all user input that is used as part of the template rendering\nprocess.\n\n### Query operation\nIn this query we look for user inputs that flow directly to a\nrequest render.\n\n### Vulnerable example\n```javascript\nvar app = require('express')();\napp.set('view engine', 'hbs');\n\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n res.render('template', bodyParameter);\n});\n```\nIn this example, a user-provided data is injected directly into the\n`render` command, leading to potential code injection or data\ntampering attacks.\n\n### Remediation\n```diff\n+ const sanitizeHtml = require('sanitize-html');\nvar app = require('express')();\napp.set('view engine', 'hbs');\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n\n- res.render('template', bodyParameter);\n+ res.render('template', sanitizeHtml(bodyParameter));\n});\nUsing `sanitize-html`, the user-provided data is sanitized, before\nrendering to the response.\n```\n" - }, - "properties": { - "security-severity": "8.9" } } - ], - "version": "1.8.14" - } - }, - "invocations": [ - { - "arguments": [ - "analyzerManager/zd_scanner/scanner", - "scan", - "/Sast_1725867332/results.sarif", - "/Sast_1725867332/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" - } - } - ], - "results": [ + ] + }, { - "ruleId": "js-insecure-random", - "level": "note", + "properties": { + "applicability": "Not Covered", + "fixedVersion": "[4.17.5]" + }, + "ruleId": "CVE-2018-3721_lodash_4.17.0", + "ruleIndex": 0, + "level": "warning", "message": { - "text": "Use of Insecure Random" + "text": "[CVE-2018-3721] lodash 4.17.0", + "markdown": "[CVE-2018-3721] lodash 4.17.0" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "public/js/bootstrap.js" - }, - "region": { - "startLine": 136, - "startColumn": 22, - "endLine": 136, - "endColumn": 35, - "snippet": { - "text": "Math.random()" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "public.js.bootstrap.^_0.Util.getUID" + "uri": "package.json" } - ] + } } - ], - "fingerprints": { - "precise_sink_and_sink_function": "3cb8327f723c9d1b6664949748868899" - } + ] }, { - "ruleId": "js-insecure-random", - "level": "note", + "properties": { + "applicability": "Applicable", + "fixedVersion": "No fix available", + "policies": "npm-security", + "watch": "security-watch" + }, + "ruleId": "CVE-2024-39249_async_3.2.4", + "ruleIndex": 1, + "level": "none", "message": { - "text": "Use of Insecure Random" + "text": "[CVE-2024-39249] jake 10.8.7 (security-watch)", + "markdown": "[CVE-2024-39249] jake 10.8.7 (security-watch)" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "public/js/bootstrap.bundle.js" - }, - "region": { - "startLine": 135, - "startColumn": 22, - "endLine": 135, - "endColumn": 35, - "snippet": { - "text": "Math.random()" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "public.js.bootstrapbundle.^_0.Util.getUID" + "uri": "package.json" } - ] + } } - ], - "fingerprints": { - "precise_sink_and_sink_function": "ec68d229b6bdd85b67dd2ddce27337bd" - } + ] }, { - "ruleId": "js-express-without-helmet", - "level": "note", + "properties": { + "applicability": "Not Covered", + "fixedVersion": "[4.17.5]", + "policies": "npm-security", + "watch": "security-watch" + }, + "ruleId": "CVE-2018-3721_lodash_4.17.0", + "ruleIndex": 0, + "level": "warning", "message": { - "text": "Express Not Using Helmet" + "text": "[CVE-2018-3721] lodash 4.17.0 (security-watch)", + "markdown": "[CVE-2018-3721] lodash 4.17.0 (security-watch)" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "server.js" - }, - "region": { - "startLine": 8, - "startColumn": 11, - "endLine": 8, - "endColumn": 20, - "snippet": { - "text": "express()" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server" + "uri": "package.json" } - ] + } } - ], - "fingerprints": { - "precise_sink_and_sink_function": "f8caf6a43a2c1eb41369843ca3c7d94c" - } + ] }, { - "ruleId": "js-template-injection", + "properties": { + "applicability": "Not Covered", + "fixedVersion": "No fix available", + "policies": "npm-license", + "watch": "license-watch" + }, + "ruleId": "MIT_lodash_4.17.0", + "ruleIndex": 5, "level": "error", "message": { - "text": "Template Object Injection" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "server.js" - }, - "region": { - "startLine": 26, - "startColumn": 28, - "endLine": 26, - "endColumn": 37, - "snippet": { - "text": "req.query" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - ], - "fingerprints": { - "precise_sink_and_sink_function": "a549106dc43cdc0d36b0f81d0465a5d2" - }, - "codeFlows": [ - { - "threadFlows": [ - { - "locations": [ - { - "location": { - "physicalLocation": { - "artifactLocation": { - "uri": "server.js" - }, - "region": { - "startLine": 21, - "startColumn": 23, - "endLine": 21, - "endColumn": 26, - "snippet": { - "text": "req" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - }, - { - "location": { - "physicalLocation": { - "artifactLocation": { - "uri": "server.js" - }, - "region": { - "startLine": 26, - "startColumn": 28, - "endLine": 26, - "endColumn": 31, - "snippet": { - "text": "req" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - }, - { - "location": { - "physicalLocation": { - "artifactLocation": { - "uri": "server.js" - }, - "region": { - "startLine": 26, - "startColumn": 28, - "endLine": 26, - "endColumn": 37, - "snippet": { - "text": "req.query" - } - } - }, - "logicalLocations": [ - { - "fullyQualifiedName": "server.^_4" - } - ] - } - } - ] - } - ] - } - ] - } - ] - }, - { - "tool": { - "driver": { - "informationUri": "https://docs.jfrog-applications.jfrog.io/jfrog-security-features/sca", - "name": "JFrog Xray Scanner", - "rules": [ - { - "id": "CVE-2018-3721_lodash_4.17.0", - "shortDescription": { - "text": "[CVE-2018-3721] lodash 4.17.0" - }, - "help": { - "text": "lodash node module before 4.17.5 suffers from a Modification of Assumed-Immutable Data (MAID) vulnerability via defaultsDeep, merge, and mergeWith functions, which allows a malicious user to modify the prototype of \"Object\" via __proto__, causing the addition or modification of an existing property that will exist on all objects.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 6.5 | Not Covered | `lodash 4.17.0` | [4.17.5] |" - }, - "properties": { - "security-severity": "6.5" - } - }, - { - "id": "CVE-2021-23337_lodash_4.17.0", - "shortDescription": { - "text": "[CVE-2021-23337] lodash 4.17.0" - }, - "help": { - "text": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.2 | Not Applicable | `lodash 4.17.0` | [4.17.21] |" - }, - "properties": { - "security-severity": "7.2" - } - }, - { - "id": "CVE-2019-1010266_lodash_4.17.0", - "shortDescription": { - "text": "[CVE-2019-1010266] lodash 4.17.0" - }, - "help": { - "text": "lodash prior to 4.17.11 is affected by: CWE-400: Uncontrolled Resource Consumption. The impact is: Denial of service. The component is: Date handler. The attack vector is: Attacker provides very long strings, which the library attempts to match using a regular expression. The fixed version is: 4.17.11.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 6.5 | Not Covered | `lodash 4.17.0` | [4.17.11] |" - }, - "properties": { - "security-severity": "6.5" - } - }, - { - "id": "CVE-2024-33883_ejs_3.1.6", - "shortDescription": { - "text": "[CVE-2024-33883] ejs 3.1.6" - }, - "help": { - "text": "The ejs (aka Embedded JavaScript templates) package before 3.1.10 for Node.js lacks certain pollution protection.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 4.0 | Not Applicable | `ejs 3.1.6` | [3.1.10] |" - }, - "properties": { - "security-severity": "4.0" - } - }, - { - "id": "CVE-2023-29827_ejs_3.1.6", - "shortDescription": { - "text": "[CVE-2023-29827] ejs 3.1.6" - }, - "help": { - "text": "ejs v3.1.9 is vulnerable to server-side template injection. If the ejs file is controllable, template injection can be implemented through the configuration settings of the closeDelimiter parameter. NOTE: this is disputed by the vendor because the render function is not intended to be used with untrusted input.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 9.8 | Applicable | `ejs 3.1.6` | No fix available |" - }, - "properties": { - "security-severity": "9.8" - } - }, - { - "id": "CVE-2024-39249_async_3.2.4", - "shortDescription": { - "text": "[CVE-2024-39249] async 3.2.4" - }, - "help": { - "text": "Async <= 2.6.4 and <= 3.2.5 are vulnerable to ReDoS (Regular Expression Denial of Service) while parsing function in autoinject function. NOTE: this is disputed by the supplier because there is no realistic threat model: regular expressions are not used with untrusted input.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 0.0 | Not Covered | `ejs 3.1.6` | No fix available |" - }, - "properties": { - "security-severity": "0.0" - } - }, - { - "id": "CVE-2020-28500_lodash_4.17.0", - "shortDescription": { - "text": "[CVE-2020-28500] lodash 4.17.0" - }, - "help": { - "text": "Lodash versions prior to 4.17.21 are vulnerable to Regular Expression Denial of Service (ReDoS) via the toNumber, trim and trimEnd functions.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.3 | Not Applicable | `lodash 4.17.0` | [4.17.21] |" - }, - "properties": { - "security-severity": "5.3" - } - }, - { - "id": "CVE-2020-8203_lodash_4.17.0", - "shortDescription": { - "text": "[CVE-2020-8203] lodash 4.17.0" - }, - "help": { - "text": "Prototype pollution attack when using _.zipObjectDeep in lodash before 4.17.20.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.4 | Not Applicable | `lodash 4.17.0` | [4.17.19] |" - }, - "properties": { - "security-severity": "7.4" - } - }, - { - "id": "CVE-2019-10744_lodash_4.17.0", - "shortDescription": { - "text": "[CVE-2019-10744] lodash 4.17.0" - }, - "help": { - "text": "Versions of lodash lower than 4.17.12 are vulnerable to Prototype Pollution. The function defaultsDeep could be tricked into adding or modifying properties of Object.prototype using a constructor payload.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 9.1 | Not Applicable | `lodash 4.17.0` | [4.17.12] |" - }, - "properties": { - "security-severity": "9.1" - } - }, - { - "id": "CVE-2022-29078_ejs_3.1.6", - "shortDescription": { - "text": "[CVE-2022-29078] ejs 3.1.6" - }, - "help": { - "text": "The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 9.8 | Not Applicable | `ejs 3.1.6` | [3.1.7] |" - }, - "properties": { - "security-severity": "9.8" - } - }, - { - "id": "CVE-2024-29041_express_4.18.2", - "shortDescription": { - "text": "[CVE-2024-29041] express 4.18.2" - }, - "help": { - "text": "Express.js minimalist web framework for node. Versions of Express.js prior to 4.19.0 and all pre-release alpha and beta versions of 5.0 are affected by an open redirect vulnerability using malformed URLs. When a user of Express performs a redirect using a user-provided URL Express performs an encode [using `encodeurl`](https://github.com/pillarjs/encodeurl) on the contents before passing it to the `location` header. This can cause malformed URLs to be evaluated in unexpected ways by common redirect allow list implementations in Express applications, leading to an Open Redirect via bypass of a properly implemented allow list. The main method impacted is `res.location()` but this is also called from within `res.redirect()`. The vulnerability is fixed in 4.19.2 and 5.0.0-beta.3.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 6.1 | Not Covered | `express 4.18.2` | [4.19.2], [5.0.0-beta.3] |" - }, - "properties": { - "security-severity": "6.1" - } - }, - { - "id": "CVE-2018-16487_lodash_4.17.0", - "shortDescription": { - "text": "[CVE-2018-16487] lodash 4.17.0" - }, - "help": { - "text": "A prototype pollution vulnerability was found in lodash <4.17.11 where the functions merge, mergeWith, and defaultsDeep can be tricked into adding or modifying properties of Object.prototype.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 5.6 | Not Applicable | `lodash 4.17.0` | [4.17.11] |" - }, - "properties": { - "security-severity": "5.6" - } - } - ], - "version": "3.104.8" - } - }, - "invocations": [ - { - "executionSuccessful": true, - "workingDirectory": { - "uri": "/Users/user/ejs-frog-demo" - } - } - ], - "results": [ - { - "properties": { - "applicability": "Not Covered", - "fixedVersion": "[4.19.2], [5.0.0-beta.3]" - }, - "ruleId": "CVE-2024-29041_express_4.18.2", - "ruleIndex": 10, - "level": "warning", - "message": { - "text": "[CVE-2024-29041] express 4.18.2" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Covered", - "fixedVersion": "No fix available" - }, - "ruleId": "CVE-2024-39249_async_3.2.4", - "ruleIndex": 5, - "level": "none", - "message": { - "text": "[CVE-2024-39249] ejs 3.1.6" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.21]" - }, - "ruleId": "CVE-2020-28500_lodash_4.17.0", - "ruleIndex": 6, - "level": "warning", - "message": { - "text": "[CVE-2020-28500] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Covered", - "fixedVersion": "[4.17.5]" - }, - "ruleId": "CVE-2018-3721_lodash_4.17.0", - "ruleIndex": 0, - "level": "warning", - "message": { - "text": "[CVE-2018-3721] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.21]" - }, - "ruleId": "CVE-2021-23337_lodash_4.17.0", - "ruleIndex": 1, - "level": "error", - "message": { - "text": "[CVE-2021-23337] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.11]" - }, - "ruleId": "CVE-2018-16487_lodash_4.17.0", - "ruleIndex": 11, - "level": "warning", - "message": { - "text": "[CVE-2018-16487] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.19]" - }, - "ruleId": "CVE-2020-8203_lodash_4.17.0", - "ruleIndex": 7, - "level": "error", - "message": { - "text": "[CVE-2020-8203] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.12]" - }, - "ruleId": "CVE-2019-10744_lodash_4.17.0", - "ruleIndex": 8, - "level": "error", - "message": { - "text": "[CVE-2019-10744] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Covered", - "fixedVersion": "[4.17.11]" - }, - "ruleId": "CVE-2019-1010266_lodash_4.17.0", - "ruleIndex": 2, - "level": "warning", - "message": { - "text": "[CVE-2019-1010266] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[3.1.7]" - }, - "ruleId": "CVE-2022-29078_ejs_3.1.6", - "ruleIndex": 9, - "level": "error", - "message": { - "text": "[CVE-2022-29078] ejs 3.1.6" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[3.1.10]" - }, - "ruleId": "CVE-2024-33883_ejs_3.1.6", - "ruleIndex": 3, - "level": "warning", - "message": { - "text": "[CVE-2024-33883] ejs 3.1.6" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Applicable", - "fixedVersion": "No fix available" - }, - "ruleId": "CVE-2023-29827_ejs_3.1.6", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "[CVE-2023-29827] ejs 3.1.6" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Covered", - "fixedVersion": "[4.19.2], [5.0.0-beta.3]", - "watch": "Security_watch_1" - }, - "ruleId": "CVE-2024-29041_express_4.18.2", - "ruleIndex": 10, - "level": "warning", - "message": { - "text": "[CVE-2024-29041] express 4.18.2" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.11]", - "watch": "Security_watch_1" - }, - "ruleId": "CVE-2018-16487_lodash_4.17.0", - "ruleIndex": 11, - "level": "warning", - "message": { - "text": "[CVE-2018-16487] lodash 4.17.0" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } - } - } - ] - }, - { - "properties": { - "applicability": "Not Covered", - "fixedVersion": "[4.17.11]", - "watch": "Security_watch_1" - }, - "ruleId": "CVE-2019-1010266_lodash_4.17.0", - "ruleIndex": 2, - "level": "warning", - "message": { - "text": "[CVE-2019-1010266] lodash 4.17.0" + "text": "[MIT] in lodash 4.17.0 (license-watch)", + "markdown": "[MIT] in lodash 4.17.0 (license-watch)" }, "locations": [ { @@ -1029,46 +280,66 @@ } } ] - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[3.1.10]", - "watch": "Security_watch_1" - }, - "ruleId": "CVE-2024-33883_ejs_3.1.6", - "ruleIndex": 3, - "level": "warning", - "message": { - "text": "[CVE-2024-33883] ejs 3.1.6" - }, - "locations": [ + } + ] + }, + { + "tool": { + "driver": { + "name": "JFrog Secrets scanner", + "rules": [ { - "physicalLocation": { - "artifactLocation": { - "uri": "package.json" - } + "id": "REQ.SECRET.KEYS", + "shortDescription": { + "text": "Scanner for REQ.SECRET.KEYS", + "markdown": "Scanner for REQ.SECRET.KEYS" + }, + "fullDescription": { + "text": "The Scanner checks for REQ.SECRET.KEYS", + "markdown": "The Scanner checks for REQ.SECRET.KEYS" + }, + "help": { + "text": "The Scanner checks for REQ.SECRET.KEYS", + "markdown": "The Scanner checks for REQ.SECRET.KEYS" } } ] - }, + } + }, + "invocations": [ + { + "executionSuccessful": null, + "workingDirectory": { + "uri": "Users/user/project-with-issues" + } + } + ], + "results": [ { "properties": { - "applicability": "Applicable", - "fixedVersion": "No fix available", - "watch": "Security_watch_1" + "metadata": "active token", + "tokenValidation": "Active" }, - "ruleId": "CVE-2023-29827_ejs_3.1.6", - "ruleIndex": 4, - "level": "error", + "ruleId": "REQ.SECRET.KEYS", + "level": "info", "message": { - "text": "[CVE-2023-29827] ejs 3.1.6" + "text": "Secret REQ.SECRET.KEYS were found", + "markdown": "Secret REQ.SECRET.KEYS were found" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "package.json" + "uri": "fake-creds.txt" + }, + "region": { + "startLine": 2, + "startColumn": 1, + "endLine": 2, + "endColumn": 11, + "snippet": { + "text": "Sqc************" + } } } } @@ -1076,21 +347,29 @@ }, { "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.12]", - "watch": "Security_watch_1" + "metadata": "", + "tokenValidation": "Not a token" }, - "ruleId": "CVE-2019-10744_lodash_4.17.0", - "ruleIndex": 8, - "level": "error", + "ruleId": "REQ.SECRET.KEYS", + "level": "info", "message": { - "text": "[CVE-2019-10744] lodash 4.17.0" + "text": "Secret REQ.SECRET.KEYS were found", + "markdown": "Secret REQ.SECRET.KEYS were found" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "package.json" + "uri": "dir/server.js" + }, + "region": { + "startLine": 3, + "startColumn": 1, + "endLine": 3, + "endColumn": 11, + "snippet": { + "text": "gho************" + } } } } @@ -1098,21 +377,35 @@ }, { "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.21]", - "watch": "Security_watch_1" + "issueId": "sec-violation-1", + "metadata": "active token", + "policies": [ + "policy" + ], + "tokenValidation": "Active", + "watch": "watch" }, - "ruleId": "CVE-2020-28500_lodash_4.17.0", - "ruleIndex": 6, - "level": "warning", + "ruleId": "REQ.SECRET.KEYS", + "ruleIndex": 0, + "level": "info", "message": { - "text": "Security violation [CVE-2020-28500] lodash 4.17.0" + "text": "Secret REQ.SECRET.KEYS were found", + "markdown": "Security Violation Secret REQ.SECRET.KEYS were found" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "package.json" + "uri": "fake-creds.txt" + }, + "region": { + "startLine": 2, + "startColumn": 1, + "endLine": 2, + "endColumn": 11, + "snippet": { + "text": "Sqc************" + } } } } @@ -1120,65 +413,194 @@ }, { "properties": { - "applicability": "Not Covered", - "fixedVersion": "[4.17.5]", - "watch": "Security_watch_1" + "issueId": "sec-violation-2", + "metadata": "", + "policies": [ + "policy" + ], + "tokenValidation": "Not a token", + "watch": "watch" }, - "ruleId": "CVE-2018-3721_lodash_4.17.0", + "ruleId": "REQ.SECRET.KEYS", "ruleIndex": 0, - "level": "warning", + "level": "info", "message": { - "text": "[CVE-2018-3721] lodash 4.17.0" + "text": "Secret REQ.SECRET.KEYS were found", + "markdown": "Security Violation Secret REQ.SECRET.KEYS were found" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "package.json" + "uri": "dir/server.js" + }, + "region": { + "startLine": 3, + "startColumn": 1, + "endLine": 3, + "endColumn": 11, + "snippet": { + "text": "gho************" + } } } } ] - }, + } + ] + }, + { + "tool": { + "driver": { + "name": "JFrog Terraform scanner", + "rules": [ + { + "id": "aws_cloudfront_tls_only", + "shortDescription": { + "text": "Scanner for aws_cloudfront_tls_only", + "markdown": "Scanner for aws_cloudfront_tls_only" + }, + "fullDescription": { + "text": "The Scanner checks for aws_cloudfront_tls_only", + "markdown": "The Scanner checks for aws_cloudfront_tls_only" + }, + "help": { + "text": "The Scanner checks for aws_cloudfront_tls_only", + "markdown": "The Scanner checks for aws_cloudfront_tls_only" + } + } + ] + } + }, + "invocations": [ { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.21]", - "watch": "Security_watch_1" - }, - "ruleId": "CVE-2021-23337_lodash_4.17.0", - "ruleIndex": 1, + "executionSuccessful": null, + "workingDirectory": { + "uri": "Users/user/project-with-issues" + } + } + ], + "results": [ + { + "ruleId": "aws_cloudfront_tls_only", "level": "error", "message": { - "text": "[CVE-2021-23337] lodash 4.17.0" + "text": "Vulnerability aws_cloudfront_tls_only were found", + "markdown": "Vulnerability aws_cloudfront_tls_only were found" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "package.json" + "uri": "req_sw_terraform_aws_cloudfront_tls_only.tf" + }, + "region": { + "startLine": 2, + "startColumn": 1, + "endLine": 21, + "endColumn": 1, + "snippet": { + "text": "viewer_protocol_policy..." + } } } } ] - }, + } + ] + }, + { + "tool": { + "driver": { + "name": "🐸 JFrog SAST", + "rules": [ + { + "id": "aws_cloudfront_tls_only", + "shortDescription": { + "text": "Scanner for aws_cloudfront_tls_only", + "markdown": "Scanner for aws_cloudfront_tls_only" + }, + "fullDescription": { + "text": "The Scanner checks for aws_cloudfront_tls_only", + "markdown": "The Scanner checks for aws_cloudfront_tls_only" + }, + "help": { + "text": "The Scanner checks for aws_cloudfront_tls_only", + "markdown": "The Scanner checks for aws_cloudfront_tls_only" + } + }, + { + "id": "js-template-injection", + "shortDescription": { + "text": "[Security Violation] Scanner for js-template-injection", + "markdown": "Scanner for js-template-injection" + }, + "fullDescription": { + "text": "The Scanner checks for js-template-injection", + "markdown": "The Scanner checks for js-template-injection" + }, + "help": { + "text": "The Scanner checks for js-template-injection", + "markdown": "The Scanner checks for js-template-injection" + } + }, + { + "id": "js-insecure-random", + "shortDescription": { + "text": "[Security Violation] Scanner for js-insecure-random", + "markdown": "Scanner for js-insecure-random" + }, + "fullDescription": { + "text": "The Scanner checks for js-insecure-random", + "markdown": "The Scanner checks for js-insecure-random" + }, + "help": { + "text": "The Scanner checks for js-insecure-random", + "markdown": "The Scanner checks for js-insecure-random" + } + } + ] + } + }, + "invocations": [ + { + "executionSuccessful": null, + "workingDirectory": { + "uri": "Users/user/project-with-issues" + } + } + ], + "results": [ { "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[4.17.19]", - "watch": "Security_watch_1" + "issueId": "sast-violation-1", + "policies": [ + "policy", + "policy2" + ], + "watch": "watch" }, - "ruleId": "CVE-2020-8203_lodash_4.17.0", - "ruleIndex": 7, - "level": "error", + "ruleId": "js-insecure-random", + "ruleIndex": 2, + "level": "note", "message": { - "text": "[CVE-2020-8203] lodash 4.17.0" + "text": "Vulnerability js-insecure-random were found", + "markdown": "Security Violation Vulnerability js-insecure-random were found" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "package.json" + "uri": "public/js/bootstrap.bundle.js" + }, + "region": { + "startLine": 136, + "startColumn": 22, + "endLine": 136, + "endColumn": 35, + "snippet": { + "text": "Math.random()" + } } } } @@ -1186,24 +608,83 @@ }, { "properties": { - "applicability": "Not Applicable", - "fixedVersion": "[3.1.7]", - "watch": "Security_watch_1" + "issueId": "sast-violation-2", + "policies": [ + "policy", + "policy2" + ], + "watch": "watch" }, - "ruleId": "CVE-2022-29078_ejs_3.1.6", - "ruleIndex": 9, + "ruleId": "js-template-injection", + "ruleIndex": 1, "level": "error", "message": { - "text": "[CVE-2022-29078] ejs 3.1.6" + "text": "Vulnerability js-template-injection were found", + "markdown": "Security Violation Vulnerability js-template-injection were found" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "package.json" + "uri": "server.js" + }, + "region": { + "startLine": 26, + "startColumn": 28, + "endLine": 26, + "endColumn": 37, + "snippet": { + "text": "req.query" + } } } } + ], + "codeFlows": [ + { + "threadFlows": [ + { + "locations": [ + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "server.js" + }, + "region": { + "startLine": 27, + "startColumn": 28, + "endLine": 26, + "endColumn": 31, + "snippet": { + "text": "req" + } + } + } + } + }, + { + "location": { + "physicalLocation": { + "artifactLocation": { + "uri": "server.js" + }, + "region": { + "startLine": 26, + "startColumn": 28, + "endLine": 26, + "endColumn": 37, + "snippet": { + "text": "req.query" + } + } + } + } + } + ] + } + ] + } ] } ] diff --git a/tests/testdata/output/audit/audit_simple_json.json b/tests/testdata/output/audit/audit_simple_json.json index e710dc91..865b11cd 100644 --- a/tests/testdata/output/audit/audit_simple_json.json +++ b/tests/testdata/output/audit/audit_simple_json.json @@ -1,51 +1,37 @@ { - "multiScanId": "7d5e4733-3f93-11ef-8147-e610d09d7daa", "vulnerabilities": [ { - "severity": "Critical", - "impactedPackageName": "ejs", - "impactedPackageVersion": "3.1.6", + "severity": "High", + "impactedPackageName": "lodash", + "impactedPackageVersion": "4.17.0", "impactedPackageType": "npm", "components": [ { - "name": "ejs", - "version": "3.1.6", + "name": "lodash", + "version": "4.17.0", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" + "file": "Users/user/project-with-issues/package.json" } } ], - "summary": "ejs v3.1.9 is vulnerable to server-side template injection. If the ejs file is controllable, template injection can be implemented through the configuration settings of the closeDelimiter parameter. NOTE: this is disputed by the vendor because the render function is not intended to be used with untrusted input.", - "applicable": "Applicable", - "fixedVersions": null, + "summary": "Code Injection", + "applicable": "Not Applicable", + "fixedVersions": [ + "[4.17.19]" + ], "cves": [ { - "id": "CVE-2023-29827", - "cvssV2": "", - "cvssV3": "9.8", + "id": "CVE-2020-8203", + "cvssV2": "5.8", + "cvssV3": "7.4", "applicability": { - "status": "Applicable", - "scannerDescription": "The scanner checks whether any of the following conditions are met:\n\n1. The `ejs.renderFile` function is called with an unknown third argument.\n\n2. The `ejs.compile` function is called with an unknown second argument.\n\n3. The `express.set` function is called with any of the following arguments:\n\n* `express.set(\"view engine\", \"ejs\")`\n* `express.set(\"view engine\", {USER_INPUT})`\n* `express.set({USER_INPUT}, \"ejs\")`\n* `express.set({USER_INPUT}, {USER_INPUT})`", - "evidence": [ - { - "file": "server.js", - "startLine": 14, - "startColumn": 1, - "endLine": 14, - "endColumn": 30, - "snippet": "app.set('view engine', 'ejs')", - "reason": "The vulnerable functionality is triggered since express.set is called with 'view engine' as the first argument and 'ejs' as the second argument or both arguments with external input" - } - ] + "status": "Not Applicable", + "scannerDescription": "The Scanner checks for CVE-2020-8203" } } ], - "issueId": "XRAY-520200", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2023-29827", - "https://github.com/mde/ejs/issues/720", - "https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities" - ], + "issueId": "XRAY-114089", + "references": null, "impactPaths": [ [ { @@ -53,76 +39,47 @@ "version": "1.0.0" }, { - "name": "ejs", - "version": "3.1.6" + "name": "lodash", + "version": "4.17.0" } ] ], "jfrogResearchInformation": { - "severity": "Low", - "summary": "Insufficient input validation can lead to template injection in ejs when attackers can control both the rendered template and rendering options.", - "details": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to bypass ejs' template injection restrictions, by abusing the `closeDelimiter` rendering option, in the case when -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\n\nThe vulnerability was **rightfully disputed** due to the fact that a vulnerable configuration is extremely unlikely to exist in any real-world setup. As such, the maintainers will not provide a fix for this (non-)issue.\n\nExample of a vulnerable application -\n```js\nconst express = require('express')\nconst app = express()\nconst port = 3000\n\napp.set('view engine', 'ejs');\n\napp.get('/page', (req,res) =\u003e {\n res.render('page', req.query); // OPTS (2nd parameter) IS ATTACKER-CONTROLLED\n})\n\napp.listen(port, () =\u003e {\n console.log(\"Example app listening on port ${port}\")\n})\n```\n\nContents of `page.ejs` (very unlikely to be attacker controlled) -\n```js\n%%1\");process.mainModule.require('child_process').execSync('calc');//\n```\n\nIn this case, sending `closeDelimiter` with the same malicious code that already exists at `page.ejs` will trigger the injection -\n`http://127.0.0.1:3000/page?settings[view%20options][closeDelimiter]=1\")%3bprocess.mainModule.require('child_process').execSync('calc')%3b//`", - "severityReasons": [ - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The CVSS does not take into account the rarity of a vulnerable configuration to exist", - "isPositive": true - }, - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The vulnerability can be exploited only under the following conditions -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\nThis vulnerable configuration is extremely unlikely to exist in any real-world setup.", - "isPositive": true - }, - { - "name": "The issue has been disputed by the vendor", - "isPositive": true - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates template injection" - } - ] + "severity": "Low" } }, { - "severity": "Medium", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", + "severity": "High", + "impactedPackageName": "ejs", + "impactedPackageVersion": "3.1.6", "impactedPackageType": "npm", "components": [ { "name": "lodash", "version": "4.17.0", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" + "file": "Users/user/project-with-issues/package.json" } } ], - "summary": "lodash prior to 4.17.11 is affected by: CWE-400: Uncontrolled Resource Consumption. The impact is: Denial of service. The component is: Date handler. The attack vector is: Attacker provides very long strings, which the library attempts to match using a regular expression. The fixed version is: 4.17.11.", - "applicable": "Not Covered", + "summary": "Code Injection", + "applicable": "Not Applicable", "fixedVersions": [ - "[4.17.11]" + "[3.1.7]" ], "cves": [ { - "id": "CVE-2019-1010266", - "cvssV2": "4.0", - "cvssV3": "6.5", + "id": "CVE-2020-8203", + "cvssV2": "5.8", + "cvssV3": "7.4", "applicability": { - "status": "Not Covered" + "status": "Not Applicable", + "scannerDescription": "The Scanner checks for CVE-2020-8203" } } ], - "issueId": "XRAY-85049", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2019-1010266", - "https://github.com/lodash/lodash/wiki/Changelog", - "https://snyk.io/vuln/SNYK-JS-LODASH-73639", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/issues/3359", - "https://github.com/lodash/lodash/commit/5c08f18d365b64063bfbfa686cbb97cdd6267347" - ], + "issueId": "XRAY-114089", + "references": null, "impactPaths": [ [ { @@ -132,10 +89,16 @@ { "name": "lodash", "version": "4.17.0" + }, + { + "name": "ejs", + "version": "3.1.6" } ] ], - "jfrogResearchInformation": null + "jfrogResearchInformation": { + "severity": "Low" + } }, { "severity": "Medium", @@ -147,35 +110,39 @@ "name": "lodash", "version": "4.17.0", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" + "file": "Users/user/project-with-issues/package.json" } } ], - "summary": "lodash node module before 4.17.5 suffers from a Modification of Assumed-Immutable Data (MAID) vulnerability via defaultsDeep, merge, and mergeWith functions, which allows a malicious user to modify the prototype of \"Object\" via __proto__, causing the addition or modification of an existing property that will exist on all objects.", - "applicable": "Not Covered", + "summary": "Prototype Pollution", + "applicable": "Not Applicable", "fixedVersions": [ - "[4.17.5]" + "[4.17.11]" ], "cves": [ { - "id": "CVE-2018-3721", - "cvssV2": "4.0", - "cvssV3": "6.5", + "id": "CVE-2018-16487", + "cvssV2": "", + "cvssV3": "", "applicability": { - "status": "Not Covered" + "status": "Not Applicable", + "scannerDescription": "The Scanner checks for CVE-2018-16487", + "evidence": [ + { + "file": "file-C", + "startLine": 1, + "startColumn": 2, + "endLine": 3, + "endColumn": 4, + "snippet": "snippet3", + "reason": "ca msg" + } + ] } } ], - "issueId": "XRAY-72918", - "references": [ - "https://www.npmjs.com/advisories/577", - "https://hackerone.com/reports/310443", - "https://github.com/advisories/GHSA-fvqr-27wr-82fm", - "https://nvd.nist.gov/vuln/detail/CVE-2018-3721", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/commit/d8e069cc3410082e44eb18fcf8e7f3d08ebe1d4a" - ], + "issueId": "XRAY-75300", + "references": null, "impactPaths": [ [ { @@ -188,49 +155,43 @@ } ] ], - "jfrogResearchInformation": null + "jfrogResearchInformation": { + "severity": "", + "remediation": "Some remediation" + } }, { "severity": "Medium", - "impactedPackageName": "express", - "impactedPackageVersion": "4.18.2", + "impactedPackageName": "lodash", + "impactedPackageVersion": "4.17.0", "impactedPackageType": "npm", "components": [ { - "name": "express", - "version": "4.18.2", + "name": "lodash", + "version": "4.17.0", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" + "file": "Users/user/project-with-issues/package.json" } } ], - "summary": "Express.js minimalist web framework for node. Versions of Express.js prior to 4.19.0 and all pre-release alpha and beta versions of 5.0 are affected by an open redirect vulnerability using malformed URLs. When a user of Express performs a redirect using a user-provided URL Express performs an encode [using `encodeurl`](https://github.com/pillarjs/encodeurl) on the contents before passing it to the `location` header. This can cause malformed URLs to be evaluated in unexpected ways by common redirect allow list implementations in Express applications, leading to an Open Redirect via bypass of a properly implemented allow list. The main method impacted is `res.location()` but this is also called from within `res.redirect()`. The vulnerability is fixed in 4.19.2 and 5.0.0-beta.3.", + "summary": "Improperly Controlled Modification of Object", "applicable": "Not Covered", "fixedVersions": [ - "[4.19.2]", - "[5.0.0-beta.3]" + "[4.17.5]" ], "cves": [ { - "id": "CVE-2024-29041", + "id": "CVE-2018-3721", "cvssV2": "", - "cvssV3": "6.1", + "cvssV3": "", "applicability": { - "status": "Not Covered" + "status": "Not Covered", + "scannerDescription": "The Scanner checks for CVE-2018-3721" } } ], - "issueId": "XRAY-594935", - "references": [ - "https://github.com/koajs/koa/issues/1800", - "https://github.com/expressjs/express/pull/5539", - "https://github.com/expressjs/express/commit/0b746953c4bd8e377123527db11f9cd866e39f94", - "https://github.com/expressjs/express/commit/0867302ddbde0e9463d0564fea5861feb708c2dd", - "https://github.com/advisories/GHSA-rv95-896h-c2vc", - "https://expressjs.com/en/4x/api.html#res.location", - "https://nvd.nist.gov/vuln/detail/CVE-2024-29041", - "https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc" - ], + "issueId": "XRAY-72918", + "references": null, "impactPaths": [ [ { @@ -238,8 +199,8 @@ "version": "1.0.0" }, { - "name": "express", - "version": "4.18.2" + "name": "lodash", + "version": "4.17.0" } ] ], @@ -252,15 +213,15 @@ "impactedPackageType": "npm", "components": [ { - "name": "ejs", - "version": "3.1.6", + "name": "jake", + "version": "10.8.7", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" + "file": "Users/user/project-with-issues/package.json" } } ], - "summary": "Async \u003c= 2.6.4 and \u003c= 3.2.5 are vulnerable to ReDoS (Regular Expression Denial of Service) while parsing function in autoinject function. NOTE: this is disputed by the supplier because there is no realistic threat model: regular expressions are not used with untrusted input.", - "applicable": "Not Covered", + "summary": "Async vulnerable to ReDoS", + "applicable": "Applicable", "fixedVersions": null, "cves": [ { @@ -268,19 +229,26 @@ "cvssV2": "", "cvssV3": "", "applicability": { - "status": "Not Covered", - "scannerDescription": "Never applicable. The vulnerability is exploitable only if an attacker has access to the source code." + "status": "Applicable", + "scannerDescription": "The Scanner checks for CVE-2024-39249", + "evidence": [ + { + "file": "file-A", + "startLine": 1, + "startColumn": 2, + "endLine": 3, + "endColumn": 4, + "snippet": "snippet", + "reason": "ca msg" + } + ] } } ], "issueId": "XRAY-609848", "references": [ "https://github.com/zunak/CVE-2024-39249", - "https://github.com/caolan/async/blob/v3.2.5/lib/autoInject.js#L41", - "https://nvd.nist.gov/vuln/detail/CVE-2024-39249", - "https://github.com/caolan/async/blob/v3.2.5/lib/autoInject.js#L6", - "https://github.com/caolan/async/issues/1975#issuecomment-2204528153", - "https://github.com/zunak/CVE-2024-39249/issues/1" + "https://nvd.nist.gov/vuln/detail/CVE-2024-39249" ], "impactPaths": [ [ @@ -288,10 +256,6 @@ "name": "froghome", "version": "1.0.0" }, - { - "name": "ejs", - "version": "3.1.6" - }, { "name": "jake", "version": "10.8.7" @@ -304,32 +268,20 @@ ], "jfrogResearchInformation": { "severity": "Low", - "summary": "ReDoS in Async may lead to denial of service while parsing malformed source code.", + "summary": "ReDoS in Async may lead to denial of service while parsing", "severityReasons": [ { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The reported CVSS does not reflect the severity of the vulnerability.", - "isPositive": true - }, - { - "name": "The issue cannot result in a severe impact (such as remote code execution)", - "description": "To exploit this issue an attacker must change the source code of the application. In cases where an attacker can already modify (or fully control) the source code, the attacker can immediately achieve arbitrary code execution - thus this issue has almost no security impact.", - "isPositive": true - }, - { - "name": "The issue has an exploit published", - "description": "A proof-of-concept has been published in the advisory." - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The issue requires the use of the `async.autoInject` function to be vulnerable.", + "name": "The reported CVSS was either wrongly calculated", + "description": "The reported CVSS does not reflect the severity of the vulnerability", "isPositive": true } ] } - }, + } + ], + "securityViolations": [ { - "severity": "Critical", + "severity": "Medium", "impactedPackageName": "lodash", "impactedPackageVersion": "4.17.0", "impactedPackageType": "npm", @@ -338,51 +290,32 @@ "name": "lodash", "version": "4.17.0", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" + "file": "Users/user/project-with-issues/package.json" } } ], - "summary": "Versions of lodash lower than 4.17.12 are vulnerable to Prototype Pollution. The function defaultsDeep could be tricked into adding or modifying properties of Object.prototype using a constructor payload.", - "applicable": "Not Applicable", + "watch": "security-watch", + "policies": [ + "npm-security" + ], + "summary": "Improperly Controlled Modification of Object", + "applicable": "Not Covered", "fixedVersions": [ - "[4.17.12]" + "[4.17.5]" ], "cves": [ { - "id": "CVE-2019-10744", - "cvssV2": "6.4", - "cvssV3": "9.1", + "id": "CVE-2018-3721", + "cvssV2": "", + "cvssV3": "", "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `defaultsDeep` is called with external input to its 2nd (`sources`) argument, and the `Object.freeze()` remediation is not present.", - "evidence": [ - { - "file": "server.js", - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": "Object.freeze(Object.prototype)", - "reason": "Prototype pollution `Object.freeze` remediation was detected" - } - ] + "status": "Not Covered", + "scannerDescription": "The Scanner checks for CVE-2018-3721" } } ], - "issueId": "XRAY-85679", - "references": [ - "https://www.npmjs.com/advisories/1065", - "https://github.com/lodash/lodash/pull/4336", - "https://www.oracle.com/security-alerts/cpujan2021.html", - "https://security.netapp.com/advisory/ntap-20191004-0005/", - "https://snyk.io/vuln/SNYK-JS-LODASH-450202", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp;utm_medium=RSS", - "https://access.redhat.com/errata/RHSA-2019:3024", - "https://www.oracle.com/security-alerts/cpuoct2020.html", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp%3Butm_medium=RSS", - "https://github.com/advisories/GHSA-jf85-cpcp-j695", - "https://nvd.nist.gov/vuln/detail/CVE-2019-10744" - ], + "issueId": "XRAY-72918", + "references": null, "impactPaths": [ [ { @@ -395,81 +328,53 @@ } ] ], - "jfrogResearchInformation": { - "severity": "High", - "summary": "Insufficient input validation in lodash defaultsDeep() leads to prototype pollution.", - "details": "[lodash](https://www.npmjs.com/package/lodash) is a modern JavaScript utility library delivering modularity, performance, \u0026 extras.\n\nThe function `defaultsDeep` was found to be vulnerable to prototype pollution, when accepting arbitrary source objects from untrusted input\n\nExample of code vulnerable to this issue - \n```js\nconst lodash = require('lodash'); \nconst evilsrc = {constructor: {prototype: {evilkey: \"evilvalue\"}}};\nlodash.defaultsDeep({}, evilsrc)\n```", - "severityReasons": [ - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrates exploitation of this issue" - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "isPositive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into the `defaultsDeep` method (2nd arg)", - "isPositive": true - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } + "jfrogResearchInformation": null }, { - "severity": "Critical", - "impactedPackageName": "ejs", - "impactedPackageVersion": "3.1.6", + "severity": "Unknown", + "impactedPackageName": "async", + "impactedPackageVersion": "3.2.4", "impactedPackageType": "npm", "components": [ { - "name": "ejs", - "version": "3.1.6", + "name": "jake", + "version": "10.8.7", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" + "file": "Users/user/project-with-issues/package.json" } } ], - "summary": "The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).", - "applicable": "Not Applicable", - "fixedVersions": [ - "[3.1.7]" + "watch": "security-watch", + "policies": [ + "npm-security" ], + "summary": "Async vulnerable to ReDoS", + "applicable": "Applicable", + "fixedVersions": null, "cves": [ { - "id": "CVE-2022-29078", - "cvssV2": "7.5", - "cvssV3": "9.8", + "id": "CVE-2024-39249", + "cvssV2": "", + "cvssV3": "", "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks for two vulnerable flows:\n\n1. Whether the `express.set` function is called with the arguments: `view engine` and `ejs`, or external input and if it's followed by a call to the vulnerable function `render` with an unknown second argument.\n\n2. Whether the `renderFile` function is called with an unknown second argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.", + "status": "Applicable", + "scannerDescription": "The Scanner checks for CVE-2024-39249", "evidence": [ { - "file": "server.js", - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": "Object.freeze(Object.prototype)", - "reason": "Prototype pollution `Object.freeze` remediation was detected" + "file": "file-A", + "startLine": 1, + "startColumn": 2, + "endLine": 3, + "endColumn": 4, + "snippet": "snippet", + "reason": "ca msg" } ] } } ], - "issueId": "XRAY-209002", - "references": [ - "https://github.com/mde/ejs/commit/15ee698583c98dadc456639d6245580d17a24baf", - "https://eslam.io/posts/ejs-server-side-template-injection-rce/", - "https://security.netapp.com/advisory/ntap-20220804-0001", - "https://github.com/mde/ejs/releases", - "https://nvd.nist.gov/vuln/detail/CVE-2022-29078", - "https://eslam.io/posts/ejs-server-side-template-injection-rce", - "https://github.com/mde/ejs", - "https://security.netapp.com/advisory/ntap-20220804-0001/" - ], + "issueId": "XRAY-609848", + "references": null, "impactPaths": [ [ { @@ -477,33 +382,21 @@ "version": "1.0.0" }, { - "name": "ejs", - "version": "3.1.6" + "name": "jake", + "version": "10.8.7" + }, + { + "name": "async", + "version": "3.2.4" } ] ], "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Insufficient input validation in EJS enables attackers to perform template injection when attacker can control the rendering options.", - "details": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to perform template injection on the `opts.outputFunctionName` variable, since the variable is injected into the template body without any escaping. Although it is unlikely that the attacker can directly control the `outputFunctionName` property, it is possible that it can be influenced in conjunction with a prototype pollution vulnerability.\n\nOnce template injection is achieved, the attacker can immediately perform remote code execution since the template engine (EJS) allows executing arbitrary JavaScript code.\n\nExample of a vulnerable Node.js application -\n```js\nconst express = require('express');\nconst bodyParser = require('body-parser');\nconst lodash = require('lodash');\nconst ejs = require('ejs');\n\nconst app = express();\n\napp\n .use(bodyParser.urlencoded({extended: true}))\n .use(bodyParser.json());\n\napp.set('views', './');\napp.set('view engine', 'ejs');\n\napp.get(\"/\", (req, res) =\u003e {\n res.render('index');\n});\n\napp.post(\"/\", (req, res) =\u003e {\n let data = {};\n let input = JSON.parse(req.body.content);\n lodash.defaultsDeep(data, input);\n res.json({message: \"OK\"});\n});\n\nlet server = app.listen(8086, '0.0.0.0', function() {\n console.log('Listening on port %d', server.address().port);\n});\n```\n\nExploiting the above example for RCE -\n`curl 127.0.0.1:8086 -v --data 'content={\"constructor\": {\"prototype\": {\"outputFunctionName\": \"a; return global.process.mainModule.constructor._load(\\\"child_process\\\").execSync(\\\"whoami\\\"); //\"}}}'\n`\n\nDue to the prototype pollution in the `lodash.defaultsDeep` call, an attacker can inject the `outputFunctionName` property with an arbitrary value. The chosen value executes an arbitrary process via the `child_process` module.", - "severityReasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The attacker has to find a way to get their malicious input to `opts.outputFunctionName`, which will usually require exploitation of a prototype pollution vulnerability somewhere else in the code. However, there could be cases where the attacker can pass malicious data to the render function directly because of design problems in other code using EJS.", - "isPositive": true - }, - { - "name": "The issue has an exploit published", - "description": "There are multiple examples of exploits for this vulnerability online." - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Successful exploitation of this vulnerability leads to remote code execution." - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\nNote that this mitigation is supposed to stop any prototype pollution attacks which can allow an attacker to control the `opts.outputFunctionName` parameter indirectly.\n\nThe mitigation will not stop any (extremely unlikely) scenarios where the JavaScript code allows external input to directly affect `opts.outputFunctionName`." + "severity": "Low" } - }, + } + ], + "licensesViolations": [ { "severity": "High", "impactedPackageName": "lodash", @@ -514,87 +407,22 @@ "name": "lodash", "version": "4.17.0", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.21]" - ], - "cves": [ - { - "id": "CVE-2021-23337", - "cvssV2": "6.5", - "cvssV3": "7.2", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `lodash.template` is called with external input to its 2nd (`options`) argument." + "file": "Users/user/project-with-issues/package.json" } } ], - "issueId": "XRAY-140575", - "references": [ - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074929", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JS-LODASH-1040724", - "https://security.netapp.com/advisory/ntap-20210312-0006", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c", - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074928", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074932", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js%23L14851", - "https://github.com/advisories/GHSA-35jh-r3h4-6jhm", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074930", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074931", - "https://nvd.nist.gov/vuln/detail/CVE-2021-23337", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js#L14851" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Improper sanitization in the lodash template function leads to JavaScript code injection through the options argument.", - "details": "JavaScript-based applications (both frontend and backend) that use the [template function](https://lodash.com/docs/4.17.15#template) -`_.template([string=''], [options={}])` from the [lodash](https://lodash.com/) utility library and provide the `options` argument (specifically the `variable` option) from untrusted user input, are vulnerable to JavaScript code injection. This issue can be easily exploited, and an exploitation example is [publicly available](https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c#diff-a561630bb56b82342bc66697aee2ad96efddcbc9d150665abd6fb7ecb7c0ab2fR22303) in the fix tests that was introduced in version 4.17.21 - \n```js\nlodash.template('', { variable: '){console.log(process.env)}; with(obj' })()\n```", - "severityReasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "It is highly unlikely that a JS program will accept arbitrary remote input into the template's `options` argument", - "isPositive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The attacker must find remote input that propagates into the `options` argument of a `template` call", - "isPositive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Leads to remote code execution through JS code injection" - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates arbitrary JS code execution" - } - ] - } - }, + "licenseKey": "MIT", + "licenseName": "MIT full name", + "impactPaths": null, + "watch": "license-watch", + "policies": [ + "npm-license" + ] + } + ], + "licenses": [ { - "severity": "High", + "severity": "", "impactedPackageName": "lodash", "impactedPackageVersion": "4.17.0", "impactedPackageType": "npm", @@ -603,45 +431,11 @@ "name": "lodash", "version": "4.17.0", "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Prototype pollution attack when using _.zipObjectDeep in lodash before 4.17.20.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.19]" - ], - "cves": [ - { - "id": "CVE-2020-8203", - "cvssV2": "5.8", - "cvssV3": "7.4", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `zipObjectDeep` is called with external input to its 1st (`props`) and 2nd (`values`) arguments, and the `Object.freeze()` remediation is not present." + "file": "Users/user/project-with-issues/package.json" } } ], - "issueId": "XRAY-114089", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2020-8203", - "https://www.oracle.com/security-alerts/cpuapr2022.html", - "https://hackerone.com/reports/864701", - "https://hackerone.com/reports/712065", - "https://github.com/advisories/GHSA-p6mc-m468-83gw", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuApr2021.html", - "https://github.com/github/advisory-database/pull/2884", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/c84fe82760fb2d3e03a63379b297a1cc1a2fce12", - "https://security.netapp.com/advisory/ntap-20200724-0006/", - "https://web.archive.org/web/20210914001339/https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://github.com/lodash/lodash/issues/4874", - "https://github.com/lodash/lodash/wiki/Changelog#v41719" - ], + "licenseKey": "MIT", "impactPaths": [ [ { @@ -653,1161 +447,124 @@ "version": "4.17.0" } ] - ], - "jfrogResearchInformation": { - "severity": "Critical", - "summary": "Prototype pollution in lodash object merging and zipping functions leads to code injection.", - "details": "[lodash](https://lodash.com/) is a JavaScript library which provides utility functions for common programming tasks.\n\nJavaScript frontend and Node.js-based backend applications that merge or zip objects using the lodash functions `mergeWith`, `merge` and `zipObjectDeep` are vulnerable to [prototype pollution](https://medium.com/node-modules/what-is-prototype-pollution-and-why-is-it-such-a-big-deal-2dd8d89a93c) if one or more of the objects it receives as arguments are obtained from user input. \nAn attacker controlling this input given to the vulnerable functions can inject properties to JavaScript special objects such as [Object.prototype](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes) from which all JavaScript objects inherit properties and methods. Any change on `Object.prototype` properties will then propagate through the prototype chain inheritance to all of the objects in a JavaScript application. This in turn would allow an attacker to add new properties or modify existing properties which will have application specific implications that could lead to DoS (denial of service), authentication bypass, privilege escalation and even RCE (remote code execution) in [some cases](https://youtu.be/LUsiFV3dsK8?t=1152). \nAs an example for privilege escalation, consider a JavaScript application that has a `user` object which has a Boolean property of `user.isAdmin` which is used to decide which actions the user may take. If an attacker can modify or add the `isAdmin` property through prototype pollution, it can escalate the privileges of its own user to those of an admin. \nAs exploitation is usually application specific, successful exploitation is much more likely if an attacker have access to the JavaScript application code. As such, frontend applications are more vulnerable to this vulnerability than Node.js backend applications.", - "severityReasons": [ - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "isPositive": true - }, - { - "name": "The issue can be exploited by attackers over the network" - }, - { - "name": "The issue is trivial to exploit and does not require a published writeup or PoC" - } - ], - "remediation": "##### Deployment mitigations\n\nAs general guidelines against prototype pollution, first consider not merging objects originating from user input or using a Map structure instead of an object. If merging objects is needed, look into creating objects without a prototype with `Object.create(null)` or into freezing `Object.prototype` with `Object.freeze()`. Finally, it is always best to perform input validation with a a [JSON schema validator](https://github.com/ajv-validator/ajv), which could mitigate this issue entirely in many cases." - } - }, - { - "severity": "Medium", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "A prototype pollution vulnerability was found in lodash \u003c4.17.11 where the functions merge, mergeWith, and defaultsDeep can be tricked into adding or modifying properties of Object.prototype.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.11]" - ], - "cves": [ - { - "id": "CVE-2018-16487", - "cvssV2": "6.8", - "cvssV3": "5.6", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.merge` with external input to its 2nd (`sources`) argument.\n* `lodash.mergeWith` with external input to its 2nd (`sources`) argument.\n* `lodash.defaultsDeep` with external input to its 2nd (`sources`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.", - "evidence": [ - { - "file": "server.js", - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": "Object.freeze(Object.prototype)", - "reason": "Prototype pollution `Object.freeze` remediation was detected" - } - ] - } - } - ], - "issueId": "XRAY-75300", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2018-16487", - "https://www.npmjs.com/advisories/782", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/advisories/GHSA-4xc9-xhrj-v574", - "https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://hackerone.com/reports/380873" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "High", - "summary": "Insufficient input validation in the Lodash library leads to prototype pollution.", - "details": "The [Lodash](https://lodash.com/) library is an open-source JavaScript project that simplifies operations on string, arrays, numbers, and other objects. It is widely used in connected devices. \n\nThe `merge`, `mergeWith`, and `defaultsDeep` methods in Lodash are vulnerable to [prototype pollution](https://shieldfy.io/security-wiki/prototype-pollution/introduction-to-prototype-pollution/). Attackers can exploit this vulnerability by specifying a crafted `sources` parameter to any of these methods, which can modify the prototype properties of the `Object`, `Function`, `Array`, `String`, `Number`, and `Boolean` objects. A public [exploit](https://hackerone.com/reports/380873) exists which performs the prototype pollution with an arbitrary key and value.\n\nThe library implementation has a bug in the `safeGet()` function in the `lodash.js` module that allows for adding or modifying `prototype` properties of various objects. The official [solution](https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad) fixes the bug by explicitly forbidding the addition or modification of `prototype` properties.\n\nA related CVE (CVE-2018-3721) covers the same issue prior to Lodash version 4.17.5, but the fix for that was incomplete.", - "severityReasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into one of the following methods - \n* `merge` - 2nd argument\n* `mergeWith` - 2nd argument\n* `defaultsDeep` - 2nd argument", - "isPositive": true - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "isPositive": true - }, - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrated exploitation by injecting an attacker controlled key and value into the prototype" - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } - }, - { - "severity": "Medium", - "impactedPackageName": "ejs", - "impactedPackageVersion": "3.1.6", - "impactedPackageType": "npm", - "components": [ - { - "name": "ejs", - "version": "3.1.6", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "The ejs (aka Embedded JavaScript templates) package before 3.1.10 for Node.js lacks certain pollution protection.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[3.1.10]" - ], - "cves": [ - { - "id": "CVE-2024-33883", - "cvssV2": "", - "cvssV3": "4.0", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `ejs.compile()` is called." - } - } - ], - "issueId": "XRAY-599735", - "references": [ - "https://security.netapp.com/advisory/ntap-20240605-0003/", - "https://security.netapp.com/advisory/ntap-20240605-0003", - "https://github.com/mde/ejs/commit/e469741dca7df2eb400199e1cdb74621e3f89aa5", - "https://github.com/mde/ejs/compare/v3.1.9...v3.1.10", - "https://github.com/advisories/GHSA-ghr5-ch3p-vcr6", - "https://nvd.nist.gov/vuln/detail/CVE-2024-33883" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "ejs", - "version": "3.1.6" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Insufficient input validation in EJS may lead to prototype pollution.", - "details": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as `EJS`, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nA prototype pollution gadget within the EJS template engine could potentially be leveraged by attackers to achieve remote code execution or DoS via prototype pollution.\n\n```\nfunction Template(text, opts) {\n opts = opts || utils.createNullProtoObjWherePossible();\n```\n\nWhen checking for the presence of a property within an object variable, the lookup scope isn't explicitly defined. In JavaScript, the absence of a defined lookup scope prompts a search up to the root prototype (`Object.prototype`). This could potentially be under the control of an attacker if another prototype pollution vulnerability is present within the application.\n\nIf the application server is using the EJS as the backend template engine, and there is another prototype pollution vulnerability in the application, then the attacker could leverage the found gadgets in the EJS template engine to escalate the prototype pollution to remote code execution or DoS.\n\nThe following code will execute a command on the server by polluting `opts.escapeFunction`:\n \n```\nconst express = require('express');\nconst app = express();\nconst port = 8008;\nconst ejs = require('ejs');\n\n// Set EJS as the view engine\napp.set('view engine', 'ejs');\n\napp.get('/', (req, res) =\u003e {\n \n const data = {title: 'Welcome', message: 'Hello'};\n\n // Sample EJS template string\n const templateString = `\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\u003c%= title %\u003e\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003ch1\u003e\u003c%= message %\u003e\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e`;\n\n const { exec } = require('child_process');\n\n function myFunc() {\n exec('bash -c \"echo 123\"', (error, stdout, stderr) =\u003e {\n if (error) {\n console.error(`exec error: ${error}`);\n return;\n }\n if (stderr){\n console.log(`stderr : ${stderr}`);\n return;\n }\n // Handle success\n console.log(`Command executed successfully. Output: ${stdout}`);\n });\n }\n\n const options = {client:false};\n\n Object.prototype.escapeFunction = myFunc;\n \n const compiledTemplate = ejs.compile(templateString, options);\n const renderedHtml = compiledTemplate(data);\n res.send(renderedHtml);\n});\n\n// Start the server\napp.listen(port, () =\u003e {\n console.log(`Server is running on http://localhost:${port}`);\n});\n```", - "severityReasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "Attackers can only leverage this vulnerability when the application server is using the EJS as the backend template engine. Moreover, there must be a second prototype pollution vulnerability in the application.", - "isPositive": true - }, - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "CVSS does not take into account the unlikely prerequisites necessary for exploitation.", - "isPositive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "A prototype pollution attack allows the attacker to inject new properties into all JavaScript objects.\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable." - } - ] - } - }, - { - "severity": "Medium", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Regular Expression Denial of Service (ReDoS) via the toNumber, trim and trimEnd functions.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.21]" - ], - "cves": [ - { - "id": "CVE-2020-28500", - "cvssV2": "5.0", - "cvssV3": "5.3", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.trim` with external input to its 1st (`string`) argument.\n* `lodash.toNumber` with external input to its 1st (`value`) argument.\n* `lodash.trimEnd` with external input to its 1st (`string`) argument." - } - } - ], - "issueId": "XRAY-140562", - "references": [ - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://github.com/lodash/lodash/commit/c4847ebe7d14540bb28a8b932a9ce1b9ecbfee1a", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074894", - "https://github.com/lodash/lodash/blob/npm/trimEnd.js%23L8", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074893", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074892", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://nvd.nist.gov/vuln/detail/CVE-2020-28500", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074895", - "https://github.com/lodash/lodash/pull/5065/commits/02906b8191d3c100c193fe6f7b27d1c40f200bb7", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/advisories/GHSA-29mw-wpgm-hmr9", - "https://github.com/lodash/lodash/pull/5065", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074896", - "https://snyk.io/vuln/SNYK-JS-LODASH-1018905" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "ReDoS in lodash could lead to a denial of service when handling untrusted strings.", - "details": "JavaScript-based applications that use [lodash](https://github.com/lodash/lodash) and specifically the [_.toNumber](https://lodash.com/docs/4.17.15#toNumber), [_.trim](https://lodash.com/docs/4.17.15#trim) and [_.trimEnd](https://lodash.com/docs/4.17.15#trimEnd) functions, could be vulnerable to DoS (Denial of Service) through a faulty regular expression that introduces a ReDoS (Regular Expression DoS) vulnerability. This vulnerability is only triggered if untrusted user input flows into these vulnerable functions and the attacker can supply arbitrary long strings (over 50kB) that contain whitespaces. \n\nOn a modern Core i7-based system, calling the vulnerable functions with a 50kB string could take between 2 to 3 seconds to execute and 4.5 minutes for a longer 500kB string. The fix improved the regular expression performance so it took only a few milliseconds on the same Core i7-based system. This vulnerability is easily exploitable as all is required is to build a string that triggers it as can be seen in this PoC reproducing code - \n\n```js\nvar untrusted_user_input_50k = \"a\" + ' '.repeat(50000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_50k); // should take a few seconds to run\nvar untrusted_user_input_500k = \"a\" + ' '.repeat(500000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_500k); // should take a few minutes to run\n```", - "severityReasons": [ - { - "name": "The issue has an exploit published", - "description": "Public exploit demonstrated ReDoS" - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "Exploitation depends on parsing user input by the `.toNumber`, `.trim` or `.trimEnd` `lodash` functions, and requires the input to contain whitespaces and be very long (over 50KB)", - "isPositive": true - } - ], - "remediation": "##### Deployment mitigations\n\nTrim untrusted strings based on size before providing it to the vulnerable functions by using the `substring` function to with a fixed maximum size like so - ```js untrusted_user_input.substring(0, max_string_size_less_than_50kB); ```" - } - } - ], - "securityViolations": [ - { - "severity": "Critical", - "impactedPackageName": "ejs", - "impactedPackageVersion": "3.1.6", - "impactedPackageType": "npm", - "components": [ - { - "name": "ejs", - "version": "3.1.6", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "ejs v3.1.9 is vulnerable to server-side template injection. If the ejs file is controllable, template injection can be implemented through the configuration settings of the closeDelimiter parameter. NOTE: this is disputed by the vendor because the render function is not intended to be used with untrusted input.", - "applicable": "Applicable", - "fixedVersions": null, - "cves": [ - { - "id": "CVE-2023-29827", - "cvssV2": "", - "cvssV3": "9.8", - "applicability": { - "status": "Applicable", - "scannerDescription": "The scanner checks whether any of the following conditions are met:\n\n1. The `ejs.renderFile` function is called with an unknown third argument.\n\n2. The `ejs.compile` function is called with an unknown second argument.\n\n3. The `express.set` function is called with any of the following arguments:\n\n* `express.set(\"view engine\", \"ejs\")`\n* `express.set(\"view engine\", {USER_INPUT})`\n* `express.set({USER_INPUT}, \"ejs\")`\n* `express.set({USER_INPUT}, {USER_INPUT})`", - "evidence": [ - { - "file": "server.js", - "startLine": 14, - "startColumn": 1, - "endLine": 14, - "endColumn": 30, - "snippet": "app.set('view engine', 'ejs')", - "reason": "The vulnerable functionality is triggered since express.set is called with 'view engine' as the first argument and 'ejs' as the second argument or both arguments with external input" - } - ] - } - } - ], - "issueId": "XRAY-520200", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2023-29827", - "https://github.com/mde/ejs/issues/720", - "https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "ejs", - "version": "3.1.6" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Low", - "summary": "Insufficient input validation can lead to template injection in ejs when attackers can control both the rendered template and rendering options.", - "details": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to bypass ejs' template injection restrictions, by abusing the `closeDelimiter` rendering option, in the case when -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\n\nThe vulnerability was **rightfully disputed** due to the fact that a vulnerable configuration is extremely unlikely to exist in any real-world setup. As such, the maintainers will not provide a fix for this (non-)issue.\n\nExample of a vulnerable application -\n```js\nconst express = require('express')\nconst app = express()\nconst port = 3000\n\napp.set('view engine', 'ejs');\n\napp.get('/page', (req,res) =\u003e {\n res.render('page', req.query); // OPTS (2nd parameter) IS ATTACKER-CONTROLLED\n})\n\napp.listen(port, () =\u003e {\n console.log(\"Example app listening on port ${port}\")\n})\n```\n\nContents of `page.ejs` (very unlikely to be attacker controlled) -\n```js\n%%1\");process.mainModule.require('child_process').execSync('calc');//\n```\n\nIn this case, sending `closeDelimiter` with the same malicious code that already exists at `page.ejs` will trigger the injection -\n`http://127.0.0.1:3000/page?settings[view%20options][closeDelimiter]=1\")%3bprocess.mainModule.require('child_process').execSync('calc')%3b//`", - "severityReasons": [ - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The CVSS does not take into account the rarity of a vulnerable configuration to exist", - "isPositive": true - }, - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The vulnerability can be exploited only under the following conditions -\n1. The template itself can be partially controlled by the attacker\n2. The template rendering options can be fully controlled by the attacker\nThis vulnerable configuration is extremely unlikely to exist in any real-world setup.", - "isPositive": true - }, - { - "name": "The issue has been disputed by the vendor", - "isPositive": true - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates template injection" - } - ] - } - }, - { - "severity": "Medium", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "lodash prior to 4.17.11 is affected by: CWE-400: Uncontrolled Resource Consumption. The impact is: Denial of service. The component is: Date handler. The attack vector is: Attacker provides very long strings, which the library attempts to match using a regular expression. The fixed version is: 4.17.11.", - "applicable": "Not Covered", - "fixedVersions": [ - "[4.17.11]" - ], - "cves": [ - { - "id": "CVE-2019-1010266", - "cvssV2": "4.0", - "cvssV3": "6.5", - "applicability": { - "status": "Not Covered" - } - } - ], - "issueId": "XRAY-85049", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2019-1010266", - "https://github.com/lodash/lodash/wiki/Changelog", - "https://snyk.io/vuln/SNYK-JS-LODASH-73639", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/issues/3359", - "https://github.com/lodash/lodash/commit/5c08f18d365b64063bfbfa686cbb97cdd6267347" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": null - }, - { - "severity": "Medium", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "lodash node module before 4.17.5 suffers from a Modification of Assumed-Immutable Data (MAID) vulnerability via defaultsDeep, merge, and mergeWith functions, which allows a malicious user to modify the prototype of \"Object\" via __proto__, causing the addition or modification of an existing property that will exist on all objects.", - "applicable": "Not Covered", - "fixedVersions": [ - "[4.17.5]" - ], - "cves": [ - { - "id": "CVE-2018-3721", - "cvssV2": "4.0", - "cvssV3": "6.5", - "applicability": { - "status": "Not Covered" - } - } - ], - "issueId": "XRAY-72918", - "references": [ - "https://www.npmjs.com/advisories/577", - "https://hackerone.com/reports/310443", - "https://github.com/advisories/GHSA-fvqr-27wr-82fm", - "https://nvd.nist.gov/vuln/detail/CVE-2018-3721", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/lodash/lodash/commit/d8e069cc3410082e44eb18fcf8e7f3d08ebe1d4a" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": null - }, - { - "severity": "Medium", - "impactedPackageName": "express", - "impactedPackageVersion": "4.18.2", - "impactedPackageType": "npm", - "components": [ - { - "name": "express", - "version": "4.18.2", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Express.js minimalist web framework for node. Versions of Express.js prior to 4.19.0 and all pre-release alpha and beta versions of 5.0 are affected by an open redirect vulnerability using malformed URLs. When a user of Express performs a redirect using a user-provided URL Express performs an encode [using `encodeurl`](https://github.com/pillarjs/encodeurl) on the contents before passing it to the `location` header. This can cause malformed URLs to be evaluated in unexpected ways by common redirect allow list implementations in Express applications, leading to an Open Redirect via bypass of a properly implemented allow list. The main method impacted is `res.location()` but this is also called from within `res.redirect()`. The vulnerability is fixed in 4.19.2 and 5.0.0-beta.3.", - "applicable": "Not Covered", - "fixedVersions": [ - "[4.19.2]", - "[5.0.0-beta.3]" - ], - "cves": [ - { - "id": "CVE-2024-29041", - "cvssV2": "", - "cvssV3": "6.1", - "applicability": { - "status": "Not Covered" - } - } - ], - "issueId": "XRAY-594935", - "references": [ - "https://github.com/koajs/koa/issues/1800", - "https://github.com/expressjs/express/pull/5539", - "https://github.com/expressjs/express/commit/0b746953c4bd8e377123527db11f9cd866e39f94", - "https://github.com/expressjs/express/commit/0867302ddbde0e9463d0564fea5861feb708c2dd", - "https://github.com/advisories/GHSA-rv95-896h-c2vc", - "https://expressjs.com/en/4x/api.html#res.location", - "https://nvd.nist.gov/vuln/detail/CVE-2024-29041", - "https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "express", - "version": "4.18.2" - } - ] - ], - "jfrogResearchInformation": null - }, - { - "severity": "Critical", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Versions of lodash lower than 4.17.12 are vulnerable to Prototype Pollution. The function defaultsDeep could be tricked into adding or modifying properties of Object.prototype using a constructor payload.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.12]" - ], - "cves": [ - { - "id": "CVE-2019-10744", - "cvssV2": "6.4", - "cvssV3": "9.1", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `defaultsDeep` is called with external input to its 2nd (`sources`) argument, and the `Object.freeze()` remediation is not present.", - "evidence": [ - { - "file": "server.js", - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": "Object.freeze(Object.prototype)", - "reason": "Prototype pollution `Object.freeze` remediation was detected" - } - ] - } - } - ], - "issueId": "XRAY-85679", - "references": [ - "https://www.npmjs.com/advisories/1065", - "https://github.com/lodash/lodash/pull/4336", - "https://www.oracle.com/security-alerts/cpujan2021.html", - "https://security.netapp.com/advisory/ntap-20191004-0005/", - "https://snyk.io/vuln/SNYK-JS-LODASH-450202", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp;utm_medium=RSS", - "https://access.redhat.com/errata/RHSA-2019:3024", - "https://www.oracle.com/security-alerts/cpuoct2020.html", - "https://support.f5.com/csp/article/K47105354?utm_source=f5support\u0026amp%3Butm_medium=RSS", - "https://github.com/advisories/GHSA-jf85-cpcp-j695", - "https://nvd.nist.gov/vuln/detail/CVE-2019-10744" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "High", - "summary": "Insufficient input validation in lodash defaultsDeep() leads to prototype pollution.", - "details": "[lodash](https://www.npmjs.com/package/lodash) is a modern JavaScript utility library delivering modularity, performance, \u0026 extras.\n\nThe function `defaultsDeep` was found to be vulnerable to prototype pollution, when accepting arbitrary source objects from untrusted input\n\nExample of code vulnerable to this issue - \n```js\nconst lodash = require('lodash'); \nconst evilsrc = {constructor: {prototype: {evilkey: \"evilvalue\"}}};\nlodash.defaultsDeep({}, evilsrc)\n```", - "severityReasons": [ - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrates exploitation of this issue" - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "isPositive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into the `defaultsDeep` method (2nd arg)", - "isPositive": true - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } - }, - { - "severity": "Critical", - "impactedPackageName": "ejs", - "impactedPackageVersion": "3.1.6", - "impactedPackageType": "npm", - "components": [ - { - "name": "ejs", - "version": "3.1.6", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "The ejs (aka Embedded JavaScript templates) package 3.1.6 for Node.js allows server-side template injection in settings[view options][outputFunctionName]. This is parsed as an internal option, and overwrites the outputFunctionName option with an arbitrary OS command (which is executed upon template compilation).", - "applicable": "Not Applicable", - "fixedVersions": [ - "[3.1.7]" - ], - "cves": [ - { - "id": "CVE-2022-29078", - "cvssV2": "7.5", - "cvssV3": "9.8", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks for two vulnerable flows:\n\n1. Whether the `express.set` function is called with the arguments: `view engine` and `ejs`, or external input and if it's followed by a call to the vulnerable function `render` with an unknown second argument.\n\n2. Whether the `renderFile` function is called with an unknown second argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.", - "evidence": [ - { - "file": "server.js", - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": "Object.freeze(Object.prototype)", - "reason": "Prototype pollution `Object.freeze` remediation was detected" - } - ] - } - } - ], - "issueId": "XRAY-209002", - "references": [ - "https://github.com/mde/ejs/commit/15ee698583c98dadc456639d6245580d17a24baf", - "https://eslam.io/posts/ejs-server-side-template-injection-rce/", - "https://security.netapp.com/advisory/ntap-20220804-0001", - "https://github.com/mde/ejs/releases", - "https://nvd.nist.gov/vuln/detail/CVE-2022-29078", - "https://eslam.io/posts/ejs-server-side-template-injection-rce", - "https://github.com/mde/ejs", - "https://security.netapp.com/advisory/ntap-20220804-0001/" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "ejs", - "version": "3.1.6" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Insufficient input validation in EJS enables attackers to perform template injection when attacker can control the rendering options.", - "details": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as EJS, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nWhen rendering views using EJS, it is possible to perform template injection on the `opts.outputFunctionName` variable, since the variable is injected into the template body without any escaping. Although it is unlikely that the attacker can directly control the `outputFunctionName` property, it is possible that it can be influenced in conjunction with a prototype pollution vulnerability.\n\nOnce template injection is achieved, the attacker can immediately perform remote code execution since the template engine (EJS) allows executing arbitrary JavaScript code.\n\nExample of a vulnerable Node.js application -\n```js\nconst express = require('express');\nconst bodyParser = require('body-parser');\nconst lodash = require('lodash');\nconst ejs = require('ejs');\n\nconst app = express();\n\napp\n .use(bodyParser.urlencoded({extended: true}))\n .use(bodyParser.json());\n\napp.set('views', './');\napp.set('view engine', 'ejs');\n\napp.get(\"/\", (req, res) =\u003e {\n res.render('index');\n});\n\napp.post(\"/\", (req, res) =\u003e {\n let data = {};\n let input = JSON.parse(req.body.content);\n lodash.defaultsDeep(data, input);\n res.json({message: \"OK\"});\n});\n\nlet server = app.listen(8086, '0.0.0.0', function() {\n console.log('Listening on port %d', server.address().port);\n});\n```\n\nExploiting the above example for RCE -\n`curl 127.0.0.1:8086 -v --data 'content={\"constructor\": {\"prototype\": {\"outputFunctionName\": \"a; return global.process.mainModule.constructor._load(\\\"child_process\\\").execSync(\\\"whoami\\\"); //\"}}}'\n`\n\nDue to the prototype pollution in the `lodash.defaultsDeep` call, an attacker can inject the `outputFunctionName` property with an arbitrary value. The chosen value executes an arbitrary process via the `child_process` module.", - "severityReasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The attacker has to find a way to get their malicious input to `opts.outputFunctionName`, which will usually require exploitation of a prototype pollution vulnerability somewhere else in the code. However, there could be cases where the attacker can pass malicious data to the render function directly because of design problems in other code using EJS.", - "isPositive": true - }, - { - "name": "The issue has an exploit published", - "description": "There are multiple examples of exploits for this vulnerability online." - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Successful exploitation of this vulnerability leads to remote code execution." - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks.\n\nNote that this mitigation is supposed to stop any prototype pollution attacks which can allow an attacker to control the `opts.outputFunctionName` parameter indirectly.\n\nThe mitigation will not stop any (extremely unlikely) scenarios where the JavaScript code allows external input to directly affect `opts.outputFunctionName`." - } - }, - { - "severity": "High", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.21]" - ], - "cves": [ - { - "id": "CVE-2021-23337", - "cvssV2": "6.5", - "cvssV3": "7.2", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `lodash.template` is called with external input to its 2nd (`options`) argument." - } - } - ], - "issueId": "XRAY-140575", - "references": [ - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074929", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JS-LODASH-1040724", - "https://security.netapp.com/advisory/ntap-20210312-0006", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c", - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074928", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074932", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js%23L14851", - "https://github.com/advisories/GHSA-35jh-r3h4-6jhm", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074930", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074931", - "https://nvd.nist.gov/vuln/detail/CVE-2021-23337", - "https://github.com/lodash/lodash/blob/ddfd9b11a0126db2302cb70ec9973b66baec0975/lodash.js#L14851" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Improper sanitization in the lodash template function leads to JavaScript code injection through the options argument.", - "details": "JavaScript-based applications (both frontend and backend) that use the [template function](https://lodash.com/docs/4.17.15#template) -`_.template([string=''], [options={}])` from the [lodash](https://lodash.com/) utility library and provide the `options` argument (specifically the `variable` option) from untrusted user input, are vulnerable to JavaScript code injection. This issue can be easily exploited, and an exploitation example is [publicly available](https://github.com/lodash/lodash/commit/3469357cff396a26c363f8c1b5a91dde28ba4b1c#diff-a561630bb56b82342bc66697aee2ad96efddcbc9d150665abd6fb7ecb7c0ab2fR22303) in the fix tests that was introduced in version 4.17.21 - \n```js\nlodash.template('', { variable: '){console.log(process.env)}; with(obj' })()\n```", - "severityReasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "It is highly unlikely that a JS program will accept arbitrary remote input into the template's `options` argument", - "isPositive": true - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The attacker must find remote input that propagates into the `options` argument of a `template` call", - "isPositive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Leads to remote code execution through JS code injection" - }, - { - "name": "The issue has an exploit published", - "description": "Published exploit demonstrates arbitrary JS code execution" - } - ] - } - }, - { - "severity": "High", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Prototype pollution attack when using _.zipObjectDeep in lodash before 4.17.20.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.19]" - ], - "cves": [ - { - "id": "CVE-2020-8203", - "cvssV2": "5.8", - "cvssV3": "7.4", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `zipObjectDeep` is called with external input to its 1st (`props`) and 2nd (`values`) arguments, and the `Object.freeze()` remediation is not present." - } - } - ], - "issueId": "XRAY-114089", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2020-8203", - "https://www.oracle.com/security-alerts/cpuapr2022.html", - "https://hackerone.com/reports/864701", - "https://hackerone.com/reports/712065", - "https://github.com/advisories/GHSA-p6mc-m468-83gw", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuApr2021.html", - "https://github.com/github/advisory-database/pull/2884", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/lodash/lodash/commit/c84fe82760fb2d3e03a63379b297a1cc1a2fce12", - "https://security.netapp.com/advisory/ntap-20200724-0006/", - "https://web.archive.org/web/20210914001339/https://github.com/lodash/lodash/issues/4744", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://github.com/lodash/lodash/issues/4874", - "https://github.com/lodash/lodash/wiki/Changelog#v41719" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Critical", - "summary": "Prototype pollution in lodash object merging and zipping functions leads to code injection.", - "details": "[lodash](https://lodash.com/) is a JavaScript library which provides utility functions for common programming tasks.\n\nJavaScript frontend and Node.js-based backend applications that merge or zip objects using the lodash functions `mergeWith`, `merge` and `zipObjectDeep` are vulnerable to [prototype pollution](https://medium.com/node-modules/what-is-prototype-pollution-and-why-is-it-such-a-big-deal-2dd8d89a93c) if one or more of the objects it receives as arguments are obtained from user input. \nAn attacker controlling this input given to the vulnerable functions can inject properties to JavaScript special objects such as [Object.prototype](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes) from which all JavaScript objects inherit properties and methods. Any change on `Object.prototype` properties will then propagate through the prototype chain inheritance to all of the objects in a JavaScript application. This in turn would allow an attacker to add new properties or modify existing properties which will have application specific implications that could lead to DoS (denial of service), authentication bypass, privilege escalation and even RCE (remote code execution) in [some cases](https://youtu.be/LUsiFV3dsK8?t=1152). \nAs an example for privilege escalation, consider a JavaScript application that has a `user` object which has a Boolean property of `user.isAdmin` which is used to decide which actions the user may take. If an attacker can modify or add the `isAdmin` property through prototype pollution, it can escalate the privileges of its own user to those of an admin. \nAs exploitation is usually application specific, successful exploitation is much more likely if an attacker have access to the JavaScript application code. As such, frontend applications are more vulnerable to this vulnerability than Node.js backend applications.", - "severityReasons": [ - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "isPositive": true - }, - { - "name": "The issue can be exploited by attackers over the network" - }, - { - "name": "The issue is trivial to exploit and does not require a published writeup or PoC" - } - ], - "remediation": "##### Deployment mitigations\n\nAs general guidelines against prototype pollution, first consider not merging objects originating from user input or using a Map structure instead of an object. If merging objects is needed, look into creating objects without a prototype with `Object.create(null)` or into freezing `Object.prototype` with `Object.freeze()`. Finally, it is always best to perform input validation with a a [JSON schema validator](https://github.com/ajv-validator/ajv), which could mitigate this issue entirely in many cases." - } - }, - { - "severity": "Medium", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "A prototype pollution vulnerability was found in lodash \u003c4.17.11 where the functions merge, mergeWith, and defaultsDeep can be tricked into adding or modifying properties of Object.prototype.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.11]" - ], - "cves": [ - { - "id": "CVE-2018-16487", - "cvssV2": "6.8", - "cvssV3": "5.6", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.merge` with external input to its 2nd (`sources`) argument.\n* `lodash.mergeWith` with external input to its 2nd (`sources`) argument.\n* `lodash.defaultsDeep` with external input to its 2nd (`sources`) argument.\n\nThe scanner also checks whether the `Object.freeze()` remediation is not present.", - "evidence": [ - { - "file": "server.js", - "startLine": 4, - "startColumn": 1, - "endLine": 4, - "endColumn": 32, - "snippet": "Object.freeze(Object.prototype)", - "reason": "Prototype pollution `Object.freeze` remediation was detected" - } - ] - } - } - ], - "issueId": "XRAY-75300", - "references": [ - "https://nvd.nist.gov/vuln/detail/CVE-2018-16487", - "https://www.npmjs.com/advisories/782", - "https://security.netapp.com/advisory/ntap-20190919-0004/", - "https://github.com/advisories/GHSA-4xc9-xhrj-v574", - "https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad", - "https://security.netapp.com/advisory/ntap-20190919-0004", - "https://hackerone.com/reports/380873" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "High", - "summary": "Insufficient input validation in the Lodash library leads to prototype pollution.", - "details": "The [Lodash](https://lodash.com/) library is an open-source JavaScript project that simplifies operations on string, arrays, numbers, and other objects. It is widely used in connected devices. \n\nThe `merge`, `mergeWith`, and `defaultsDeep` methods in Lodash are vulnerable to [prototype pollution](https://shieldfy.io/security-wiki/prototype-pollution/introduction-to-prototype-pollution/). Attackers can exploit this vulnerability by specifying a crafted `sources` parameter to any of these methods, which can modify the prototype properties of the `Object`, `Function`, `Array`, `String`, `Number`, and `Boolean` objects. A public [exploit](https://hackerone.com/reports/380873) exists which performs the prototype pollution with an arbitrary key and value.\n\nThe library implementation has a bug in the `safeGet()` function in the `lodash.js` module that allows for adding or modifying `prototype` properties of various objects. The official [solution](https://github.com/lodash/lodash/commit/90e6199a161b6445b01454517b40ef65ebecd2ad) fixes the bug by explicitly forbidding the addition or modification of `prototype` properties.\n\nA related CVE (CVE-2018-3721) covers the same issue prior to Lodash version 4.17.5, but the fix for that was incomplete.", - "severityReasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "An attacker must find remote input that propagates into one of the following methods - \n* `merge` - 2nd argument\n* `mergeWith` - 2nd argument\n* `defaultsDeep` - 2nd argument", - "isPositive": true - }, - { - "name": "The impact of exploiting the issue depends on the context of surrounding software. A severe impact such as RCE is not guaranteed.", - "description": "A prototype pollution attack allows the attacker to inject new properties to all JavaScript objects (but not set existing properties).\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable.", - "isPositive": true - }, - { - "name": "The issue has an exploit published", - "description": "A public PoC demonstrated exploitation by injecting an attacker controlled key and value into the prototype" - } - ], - "remediation": "##### Development mitigations\n\nAdd the `Object.freeze(Object.prototype);` directive once at the beginning of your main JS source code file (ex. `index.js`), preferably after all your `require` directives. This will prevent any changes to the prototype object, thus completely negating prototype pollution attacks." - } - }, + ] + } + ], + "operationalRiskViolations": null, + "secrets": [ { "severity": "Medium", - "impactedPackageName": "ejs", - "impactedPackageVersion": "3.1.6", - "impactedPackageType": "npm", - "components": [ - { - "name": "ejs", - "version": "3.1.6", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "The ejs (aka Embedded JavaScript templates) package before 3.1.10 for Node.js lacks certain pollution protection.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[3.1.10]" - ], - "cves": [ - { - "id": "CVE-2024-33883", - "cvssV2": "", - "cvssV3": "4.0", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `ejs.compile()` is called." - } - } - ], - "issueId": "XRAY-599735", - "references": [ - "https://security.netapp.com/advisory/ntap-20240605-0003/", - "https://security.netapp.com/advisory/ntap-20240605-0003", - "https://github.com/mde/ejs/commit/e469741dca7df2eb400199e1cdb74621e3f89aa5", - "https://github.com/mde/ejs/compare/v3.1.9...v3.1.10", - "https://github.com/advisories/GHSA-ghr5-ch3p-vcr6", - "https://nvd.nist.gov/vuln/detail/CVE-2024-33883" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "ejs", - "version": "3.1.6" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Insufficient input validation in EJS may lead to prototype pollution.", - "details": "[Embedded JavaScript templates](https://github.com/mde/ejs), also known as `EJS`, is one of the most popular Node.js templating engines, which is compiled with the Express JS view system.\n\nA prototype pollution gadget within the EJS template engine could potentially be leveraged by attackers to achieve remote code execution or DoS via prototype pollution.\n\n```\nfunction Template(text, opts) {\n opts = opts || utils.createNullProtoObjWherePossible();\n```\n\nWhen checking for the presence of a property within an object variable, the lookup scope isn't explicitly defined. In JavaScript, the absence of a defined lookup scope prompts a search up to the root prototype (`Object.prototype`). This could potentially be under the control of an attacker if another prototype pollution vulnerability is present within the application.\n\nIf the application server is using the EJS as the backend template engine, and there is another prototype pollution vulnerability in the application, then the attacker could leverage the found gadgets in the EJS template engine to escalate the prototype pollution to remote code execution or DoS.\n\nThe following code will execute a command on the server by polluting `opts.escapeFunction`:\n \n```\nconst express = require('express');\nconst app = express();\nconst port = 8008;\nconst ejs = require('ejs');\n\n// Set EJS as the view engine\napp.set('view engine', 'ejs');\n\napp.get('/', (req, res) =\u003e {\n \n const data = {title: 'Welcome', message: 'Hello'};\n\n // Sample EJS template string\n const templateString = `\u003chtml\u003e\u003chead\u003e\u003ctitle\u003e\u003c%= title %\u003e\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\u003ch1\u003e\u003c%= message %\u003e\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e`;\n\n const { exec } = require('child_process');\n\n function myFunc() {\n exec('bash -c \"echo 123\"', (error, stdout, stderr) =\u003e {\n if (error) {\n console.error(`exec error: ${error}`);\n return;\n }\n if (stderr){\n console.log(`stderr : ${stderr}`);\n return;\n }\n // Handle success\n console.log(`Command executed successfully. Output: ${stdout}`);\n });\n }\n\n const options = {client:false};\n\n Object.prototype.escapeFunction = myFunc;\n \n const compiledTemplate = ejs.compile(templateString, options);\n const renderedHtml = compiledTemplate(data);\n res.send(renderedHtml);\n});\n\n// Start the server\napp.listen(port, () =\u003e {\n console.log(`Server is running on http://localhost:${port}`);\n});\n```", - "severityReasons": [ - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "Attackers can only leverage this vulnerability when the application server is using the EJS as the backend template engine. Moreover, there must be a second prototype pollution vulnerability in the application.", - "isPositive": true - }, - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "CVSS does not take into account the unlikely prerequisites necessary for exploitation.", - "isPositive": true - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "A prototype pollution attack allows the attacker to inject new properties into all JavaScript objects.\nTherefore, the impact of a prototype pollution attack depends on the way the JavaScript code uses any object properties after the attack is triggered.\nUsually, a DoS attack is possible since invalid properties quickly lead to an exception being thrown. In more severe cases, RCE may be achievable." - } - ] + "ruleId": "REQ.SECRET.KEYS", + "scannerShortDescription": "Scanner for REQ.SECRET.KEYS", + "scannerDescription": "The Scanner checks for REQ.SECRET.KEYS", + "file": "fake-creds.txt", + "startLine": 2, + "startColumn": 1, + "endLine": 2, + "endColumn": 11, + "snippet": "Sqc************", + "finding": "Secret REQ.SECRET.KEYS were found", + "applicability": { + "status": "Active", + "scannerDescription": "active token" } }, { "severity": "Medium", - "impactedPackageName": "lodash", - "impactedPackageVersion": "4.17.0", - "impactedPackageType": "npm", - "components": [ - { - "name": "lodash", - "version": "4.17.0", - "location": { - "file": "/Users/user/ejs-frog-demo/package.json" - } - } - ], - "summary": "Lodash versions prior to 4.17.21 are vulnerable to Regular Expression Denial of Service (ReDoS) via the toNumber, trim and trimEnd functions.", - "applicable": "Not Applicable", - "fixedVersions": [ - "[4.17.21]" - ], - "cves": [ - { - "id": "CVE-2020-28500", - "cvssV2": "5.0", - "cvssV3": "5.3", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n* `lodash.trim` with external input to its 1st (`string`) argument.\n* `lodash.toNumber` with external input to its 1st (`value`) argument.\n* `lodash.trimEnd` with external input to its 1st (`string`) argument." - } - } - ], - "issueId": "XRAY-140562", - "references": [ - "https://cert-portal.siemens.com/productcert/pdf/ssa-637483.pdf", - "https://github.com/lodash/lodash/commit/c4847ebe7d14540bb28a8b932a9ce1b9ecbfee1a", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARS-1074894", - "https://github.com/lodash/lodash/blob/npm/trimEnd.js%23L8", - "https://security.netapp.com/advisory/ntap-20210312-0006/", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1074893", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWER-1074892", - "https://www.oracle.com//security-alerts/cpujul2021.html", - "https://www.oracle.com/security-alerts/cpuoct2021.html", - "https://nvd.nist.gov/vuln/detail/CVE-2020-28500", - "https://www.oracle.com/security-alerts/cpujul2022.html", - "https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSBOWERGITHUBLODASH-1074895", - "https://github.com/lodash/lodash/pull/5065/commits/02906b8191d3c100c193fe6f7b27d1c40f200bb7", - "https://www.oracle.com/security-alerts/cpujan2022.html", - "https://github.com/advisories/GHSA-29mw-wpgm-hmr9", - "https://github.com/lodash/lodash/pull/5065", - "https://snyk.io/vuln/SNYK-JAVA-ORGFUJIONWEBJARS-1074896", - "https://snyk.io/vuln/SNYK-JS-LODASH-1018905" - ], - "impactPaths": [ - [ - { - "name": "froghome", - "version": "1.0.0" - }, - { - "name": "lodash", - "version": "4.17.0" - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "ReDoS in lodash could lead to a denial of service when handling untrusted strings.", - "details": "JavaScript-based applications that use [lodash](https://github.com/lodash/lodash) and specifically the [_.toNumber](https://lodash.com/docs/4.17.15#toNumber), [_.trim](https://lodash.com/docs/4.17.15#trim) and [_.trimEnd](https://lodash.com/docs/4.17.15#trimEnd) functions, could be vulnerable to DoS (Denial of Service) through a faulty regular expression that introduces a ReDoS (Regular Expression DoS) vulnerability. This vulnerability is only triggered if untrusted user input flows into these vulnerable functions and the attacker can supply arbitrary long strings (over 50kB) that contain whitespaces. \n\nOn a modern Core i7-based system, calling the vulnerable functions with a 50kB string could take between 2 to 3 seconds to execute and 4.5 minutes for a longer 500kB string. The fix improved the regular expression performance so it took only a few milliseconds on the same Core i7-based system. This vulnerability is easily exploitable as all is required is to build a string that triggers it as can be seen in this PoC reproducing code - \n\n```js\nvar untrusted_user_input_50k = \"a\" + ' '.repeat(50000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_50k); // should take a few seconds to run\nvar untrusted_user_input_500k = \"a\" + ' '.repeat(500000) + \"z\"; // assume this is provided over the network\nlo.trimEnd(untrusted_user_input_500k); // should take a few minutes to run\n```", - "severityReasons": [ - { - "name": "The issue has an exploit published", - "description": "Public exploit demonstrated ReDoS" - }, - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "Exploitation depends on parsing user input by the `.toNumber`, `.trim` or `.trimEnd` `lodash` functions, and requires the input to contain whitespaces and be very long (over 50KB)", - "isPositive": true - } - ], - "remediation": "##### Deployment mitigations\n\nTrim untrusted strings based on size before providing it to the vulnerable functions by using the `substring` function to with a fixed maximum size like so - ```js untrusted_user_input.substring(0, max_string_size_less_than_50kB); ```" + "ruleId": "REQ.SECRET.KEYS", + "scannerShortDescription": "Scanner for REQ.SECRET.KEYS", + "scannerDescription": "The Scanner checks for REQ.SECRET.KEYS", + "file": "dir/server.js", + "startLine": 3, + "startColumn": 1, + "endLine": 3, + "endColumn": 11, + "snippet": "gho************", + "finding": "Secret REQ.SECRET.KEYS were found", + "applicability": { + "status": "Not a token" } } ], - "licensesViolations": null, - "licenses": null, - "operationalRiskViolations": null, - "secrets": [ + "iac": [ { - "severity": "Medium", - "file": "server.js", - "startLine": 11, - "startColumn": 14, - "endLine": 11, - "endColumn": 24, - "snippet": "Sqc************", - "finding": "Secret keys were found", - "scannerDescription": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\u0026tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" - }, + "severity": "High", + "ruleId": "aws_cloudfront_tls_only", + "scannerShortDescription": "Scanner for aws_cloudfront_tls_only", + "scannerDescription": "The Scanner checks for aws_cloudfront_tls_only", + "file": "req_sw_terraform_aws_cloudfront_tls_only.tf", + "startLine": 2, + "startColumn": 1, + "endLine": 21, + "endColumn": 1, + "snippet": "viewer_protocol_policy...", + "finding": "Vulnerability aws_cloudfront_tls_only were found" + } + ], + "sast": null, + "secretsViolations": [ { "severity": "Medium", + "watch": "watch", + "issueId": "sec-violation-1", + "ruleId": "REQ.SECRET.KEYS", + "scannerShortDescription": "Scanner for REQ.SECRET.KEYS", + "scannerDescription": "The Scanner checks for REQ.SECRET.KEYS", "file": "fake-creds.txt", "startLine": 2, "startColumn": 1, "endLine": 2, "endColumn": 11, "snippet": "Sqc************", - "finding": "Secret keys were found", - "scannerDescription": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\u0026tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" + "finding": "Secret REQ.SECRET.KEYS were found", + "applicability": { + "status": "Active", + "scannerDescription": "active token" + } }, { "severity": "Medium", - "file": "fake-creds.txt", + "watch": "watch", + "issueId": "sec-violation-2", + "ruleId": "REQ.SECRET.KEYS", + "scannerShortDescription": "Scanner for REQ.SECRET.KEYS", + "scannerDescription": "The Scanner checks for REQ.SECRET.KEYS", + "file": "dir/server.js", "startLine": 3, "startColumn": 1, "endLine": 3, "endColumn": 11, "snippet": "gho************", - "finding": "Secret keys were found", - "scannerDescription": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\u0026tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" + "finding": "Secret REQ.SECRET.KEYS were found", + "applicability": { + "status": "Not a token" + } } ], "iacViolations": null, "sastViolations": [ { "severity": "High", + "watch": "watch", + "issueId": "sast-violation-2", + "ruleId": "js-template-injection", + "cwe": [ + "73" + ], + "scannerShortDescription": "Scanner for js-template-injection", + "scannerDescription": "The Scanner checks for js-template-injection", "file": "server.js", "startLine": 26, "startColumn": 28, "endLine": 26, "endColumn": 37, "snippet": "req.query", - "finding": "Template Object Injection", - "scannerDescription": "\n### Overview\nTemplate Object Injection (TOI) is a vulnerability that can occur in\nweb applications that use template engines to render dynamic content.\nTemplate engines are commonly used to generate HTML pages, emails, or\nother types of documents that include variable data. TOI happens when\nuntrusted user input is included as part of the template rendering\nprocess, and the template engine evaluates the input as a code\nexpression, leading to potential code injection or data tampering\nattacks. To prevent TOI vulnerabilities, it's important to sanitize and\nvalidate all user input that is used as part of the template rendering\nprocess.\n\n### Query operation\nIn this query we look for user inputs that flow directly to a\nrequest render.\n\n### Vulnerable example\n```javascript\nvar app = require('express')();\napp.set('view engine', 'hbs');\n\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n res.render('template', bodyParameter);\n});\n```\nIn this example, a user-provided data is injected directly into the\n`render` command, leading to potential code injection or data\ntampering attacks.\n\n### Remediation\n```diff\n+ const sanitizeHtml = require('sanitize-html');\nvar app = require('express')();\napp.set('view engine', 'hbs');\napp.use(require('body-parser').json());\napp.use(require('body-parser').urlencoded({ extended: false }));\napp.post('/path', function(req, res) {\n var bodyParameter = req.body.bodyParameter;\n var queryParameter = req.query.queryParameter;\n\n- res.render('template', bodyParameter);\n+ res.render('template', sanitizeHtml(bodyParameter));\n});\nUsing `sanitize-html`, the user-provided data is sanitized, before\nrendering to the response.\n```\n", + "finding": "Vulnerability js-template-injection were found", "codeFlow": [ [ { "file": "server.js", - "startLine": 21, - "startColumn": 23, - "endLine": 21, - "endColumn": 26, - "snippet": "req" - }, - { - "file": "server.js", - "startLine": 26, + "startLine": 27, "startColumn": 28, "endLine": 26, "endColumn": 31, @@ -1826,37 +583,30 @@ }, { "severity": "Low", - "file": "server.js", - "startLine": 8, - "startColumn": 11, - "endLine": 8, - "endColumn": 20, - "snippet": "express()", - "finding": "Express Not Using Helmet", - "scannerDescription": "\n### Overview\nHelmet library should be used when using Express in order to properly configure\nHTTP header settings to mitigate a range of well-known vulnerabilities.\n\n### Remediation\n```javascript\nconst helmet = require(\"helmet\");\nconst app = express()\n\napp.use(helmet())\n```\n\n### References\n[Best practices for Express](https://expressjs.com/en/advanced/best-practice-security.html)\n" - }, - { - "severity": "Low", - "file": "public/js/bootstrap.js", + "watch": "watch", + "issueId": "sast-violation-1", + "ruleId": "js-insecure-random", + "cwe": [ + "338" + ], + "scannerShortDescription": "Scanner for js-insecure-random", + "scannerDescription": "The Scanner checks for js-insecure-random", + "file": "public/js/bootstrap.bundle.js", "startLine": 136, "startColumn": 22, "endLine": 136, "endColumn": 35, "snippet": "Math.random()", - "finding": "Use of Insecure Random", - "scannerDescription": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n" - }, - { - "severity": "Low", - "file": "public/js/bootstrap.bundle.js", - "startLine": 135, - "startColumn": 22, - "endLine": 135, - "endColumn": 35, - "snippet": "Math.random()", - "finding": "Use of Insecure Random", - "scannerDescription": "\n### Overview\nA use of insecure random vulnerability is a type of security flaw that is\ncaused by the use of inadequate or predictable random numbers in a program\nor system. Random numbers are used in many security-related applications,\nsuch as generating cryptographic keys and if the numbers are not truly\nrandom, an attacker may be able to predict or recreate them, potentially\ncompromising the security of the system.\n\n### Vulnerable example\n```javascript\nvar randomNum = Math.random();\n```\n`Math.random` is not secured, as it creates predictable random numbers.\n\n### Remediation\n```diff\nvar randomNum = crypto.randomInt(0, 100)\n```\n`crypto.randomInt` is secured, and creates much less predictable random\nnumbers.\n" + "finding": "Vulnerability js-insecure-random were found" } ], - "errors": null -} + "errors": null, + "scansStatus": { + "scaScanStatusCode": 0, + "sastScanStatusCode": 0, + "iacScanStatusCode": 0, + "secretsScanStatusCode": 0, + "ContextualAnalysisScanStatusCode": 0 + }, + "multiScanId": "7d5e4733-3f93-11ef-8147-e610d09d7daa" +} \ No newline at end of file diff --git a/tests/testdata/output/audit/audit_summary.json b/tests/testdata/output/audit/audit_summary.json index 58a813af..69baab23 100644 --- a/tests/testdata/output/audit/audit_summary.json +++ b/tests/testdata/output/audit/audit_summary.json @@ -1,67 +1,75 @@ { - "scans": [ - { - "target": "", - "vulnerabilities": { - "sca": { - "scan_ids": [ - "711851ce-68c4-4dfd-7afb-c29737ebcb96" - ], - "security": { - "Critical": { - "Applicable": 1, - "Not Applicable": 2 - }, - "High": { - "Not Applicable": 2 - }, - "Medium": { - "Not Applicable": 3, - "Not Covered": 3 - }, - "Unknown": { - "Not Covered": 1 - } - } - }, - "iac": {}, - "secrets": { - "Medium": { - "": 3 - } - }, - "sast": { - "High": { - "": 1 - }, - "Low": { - "": 3 - } - } + "scans": [ + { + "target": "/Users/user/project-with-issues", + "vulnerabilities": { + "sca": { + "scan_ids": [ + "711851ce-68c4-4dfd-7afb-c29737ebcb96" + ], + "security": { + "High": { + "Undetermined": 1 }, - "violations": { - "watches": [ - "Security_watch_1" - ], - "sca": { - "scan_ids": [ - "711851ce-68c4-4dfd-7afb-c29737ebcb96" - ], - "security": { - "Critical": { - "Applicable": 1, - "Not Applicable": 2 - }, - "High": { - "Not Applicable": 2 - }, - "Medium": { - "Not Applicable": 3, - "Not Covered": 3 - } - } - } + "Medium": { + "Applicable": 1, + "Undetermined": 1 + }, + "Unknown": { + "Applicable": 1 + } + } + }, + "iac": { + "High": { + "": 1 + } + }, + "secrets": { + "Medium": { + "": 2 + } + }, + "sast": {} + }, + "violations": { + "watches": [ + "security-watch", + "license-watch" + ], + "sca": { + "scan_ids": [ + "711851ce-68c4-4dfd-7afb-c29737ebcb96" + ], + "security": { + "Medium": { + "Undetermined": 1 + }, + "Unknown": { + "Applicable": 1 + } + }, + "license": { + "High": { + "": 1 } + } + }, + "iac": {}, + "secrets": { + "Medium": { + "": 2 + } + }, + "sast": { + "High": { + "": 1 + }, + "Low": { + "": 1 + } } - ] + } + } + ] } \ No newline at end of file diff --git a/tests/testdata/output/dockerscan/docker_results.json b/tests/testdata/output/dockerscan/docker_results.json deleted file mode 100644 index 55bf97cf..00000000 --- a/tests/testdata/output/dockerscan/docker_results.json +++ /dev/null @@ -1,909 +0,0 @@ -{ - "xray_version": "3.104.8", - "jas_entitled": true, - "command_type": "docker_image", - "targets": [ - { - "target": "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210535-1985298017/image.tar", - "name": "platform.jfrog.io/swamp-docker/swamp:latest", - "technology": "oci", - "sca_scans": { - "xray_scan": [ - { - "scan_id": "27da9106-88ea-416b-799b-bc7d15783473", - "vulnerabilities": [ - { - "cves": [ - { - "cve": "CVE-2024-6119" - } - ], - "summary": "Issue summary: Applications performing certificate name checks (e.g., TLS\nclients checking server certificates) may attempt to read an invalid memory\naddress resulting in abnormal termination of the application process.\n\nImpact summary: Abnormal termination of an application can a cause a denial of\nservice.\n\nApplications performing certificate name checks (e.g., TLS clients checking\nserver certificates) may attempt to read an invalid memory address when\ncomparing the expected name with an `otherName` subject alternative name of an\nX.509 certificate. This may result in an exception that terminates the\napplication program.\n\nNote that basic certificate chain validation (signatures, dates, ...) is not\naffected, the denial of service can occur only when the application also\nspecifies an expected DNS name, Email address or IP address.\n\nTLS servers rarely solicit client certificates, and even when they do, they\ngenerally don't perform a name check against a reference identifier (expected\nidentity), but rather extract the presented identity after checking the\ncertificate chain. So TLS servers are generally not affected and the severity\nof the issue is Moderate.\n\nThe FIPS modules in 3.3, 3.2, 3.1 and 3.0 are not affected by this issue.", - "severity": "Unknown", - "components": { - "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", - "full_path": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - }, - { - "component_id": "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1", - "full_path": "libssl3:3.0.13-1~deb12u1" - } - ] - ] - }, - "deb://debian:bookworm:openssl:3.0.13-1~deb12u1": { - "fixed_versions": [ - "[3.0.14-1~deb12u2]" - ], - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", - "full_path": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - }, - { - "component_id": "deb://debian:bookworm:openssl:3.0.13-1~deb12u1", - "full_path": "openssl:3.0.13-1~deb12u1" - } - ] - ] - } - }, - "issue_id": "XRAY-632747", - "references": [ - "https://openssl-library.org/news/secadv/20240903.txt", - "https://github.com/openssl/openssl/commit/621f3729831b05ee828a3203eddb621d014ff2b2", - "https://github.com/openssl/openssl/commit/05f360d9e849a1b277db628f1f13083a7f8dd04f", - "https://security-tracker.debian.org/tracker/CVE-2024-6119", - "https://github.com/openssl/openssl/commit/7dfcee2cd2a63b2c64b9b4b0850be64cb695b0a0", - "https://github.com/openssl/openssl/commit/06d1dc3fa96a2ba5a3e22735a033012aadc9f0d6" - ], - "extended_information": { - "short_description": "Out of bounds read in OpenSSL clients can lead to denial of service when using non-default TLS verification options and connecting to malicious TLS servers", - "jfrog_research_severity": "Medium", - "jfrog_research_severity_reasons": [ - { - "name": "The issue has an exploit published", - "description": "The fix commit contains PoC certificates that trigger the denial of service issue" - }, - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The attacker must make the victim client connect to their malicious TLS server, in order to serve the malformed TLS certificate. The victim client must use OpenSSL and must enable non-default certificate verification options, either -\n\n* DNS verification - by using `X509_VERIFY_PARAM_set1_host` or `X509_check_host`\n* Email verification - by using ` X509_VERIFY_PARAM_set1_email` or `X509_check_email`", - "is_positive": true - }, - { - "name": "The issue cannot result in a severe impact (such as remote code execution)", - "description": "Denial of service of a TLS clients only. This out of bounds read cannot lead to data disclosure.", - "is_positive": true - } - ] - } - }, - { - "cves": [ - { - "cve": "CVE-2024-38428", - "cvss_v3_score": "9.1", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", - "cwe": [ - "CWE-436" - ], - "cwe_details": { - "CWE-436": { - "name": "Interpretation Conflict", - "description": "Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state." - } - } - } - ], - "summary": "url.c in GNU Wget through 1.24.5 mishandles semicolons in the userinfo subcomponent of a URI, and thus there may be insecure behavior in which data that was supposed to be in the userinfo subcomponent is misinterpreted to be part of the host subcomponent.", - "severity": "Critical", - "components": { - "deb://debian:bookworm:wget:1.21.3-1+b1": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", - "full_path": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - }, - { - "component_id": "deb://debian:bookworm:wget:1.21.3-1+b1", - "full_path": "wget:1.21.3-1+b1" - } - ] - ] - } - }, - "issue_id": "XRAY-606103", - "references": [ - "https://git.savannah.gnu.org/cgit/wget.git/commit/?id=ed0c7c7e0e8f7298352646b2fd6e06a11e242ace", - "https://lists.gnu.org/archive/html/bug-wget/2024-06/msg00005.html", - "https://security-tracker.debian.org/tracker/CVE-2024-38428" - ] - }, - { - "summary": "Malicious package cors.js for Node.js", - "severity": "Critical", - "components": { - "npm://cors.js:0.0.1-security": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f/sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar", - "full_path": "sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar" - }, - { - "component_id": "npm://cors.js:0.0.1-security", - "full_path": "usr/src/app/node_modules/cors.js/package.json" - } - ] - ] - } - }, - "issue_id": "XRAY-264729", - "references": [ - "https://registry.npmjs.com" - ], - "extended_information": { - "short_description": "Malicious package cors.js for Node.js", - "full_description": "The package cors.js for Node.js contains malicious code that installs a persistent connectback shell. The package is typosquatting the popular `cors` package. When installed, the package opens a connectback shell to the hardcoded host `107.175.32.229` on TCP port 56173. The malicious payload achieves persistency by installing a cron job that repeats every 10 seconds - `*/10 * * * * *`", - "jfrog_research_severity": "Critical", - "remediation": "As with any malware, the malicious package must be completely removed, and steps must be taken care to remediate the damage that was done by the malicious package -\n\n##### Removing the malicious package\n\nRun `npm uninstall cors.js`\n\n##### Refreshing stolen credentials\n\nMany malicious packages steal stored user credentials, focusing on the following -\n\n* [Browser autocomplete](https://jfrog.com/blog/malicious-pypi-packages-stealing-credit-cards-injecting-code/) data, such as saved passwords and credit cards\n* [Environment variables](https://jfrog.com/blog/malicious-npm-packages-are-after-your-discord-tokens-17-new-packages-disclosed/) passed to the malicious code\n* [Stored Discord tokens](https://jfrog.com/blog/malicious-npm-packages-are-after-your-discord-tokens-17-new-packages-disclosed/)\n* AWS / GitHub credentials stored in cleartext files\n\nIt is highly recommended to change or revoke data that is stored in the infected machine at those locations\n\n##### Stopping malicious processes\n\nMany malicious packages start malicious processes such as [connectback shells](https://jfrog.com/blog/jfrog-discloses-3-remote-access-trojans-in-pypi/) or crypto-miners. Search for any unfamiliar processes that consume a large amount of CPU or a large amount of network traffic, and stop them. On Windows, this can be facilitated with [Sysinternals Process Explorer](https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer).\n\n##### Removing installed backdoors\n\nMany malicious packages install themselves as a [persistent backdoor](https://jfrog.com/blog/npm-supply-chain-attack-targets-german-based-companies/), in order to guarantee the malicious code survives a reboot. Search for any unfamiliar binaries set to be run on startup, and remove them. On Windows, this can be facilitated with [Sysinternals Autoruns](https://docs.microsoft.com/en-us/sysinternals/downloads/autoruns).\n\n##### Defining an Xray policy that blocks downloads of Artifacts with malicious packages\n\nIt is possible to [create an Xray policy](https://www.jfrog.com/confluence/display/JFROG/Creating+Xray+Policies+and+Rules) that will not allow artifacts with identified malicious packages to be downloaded from Artifactory. To create such a policy, add a new `Security` policy and set `Minimal Severity` to `Critical`. Under `Automatic Actions` check the `Block Download` action.\n\n##### Contacting the JFrog Security Research team for additional information\n\nOptionally, if you are unsure of the full impact of the malicious package and wish to get more details, the JFrog Security Research team can help you assess the potential damage from the installed malicious package.\n\nPlease contact us at research@jfrog.com with details of the affected artifact and the name of the identified malicious package." - } - }, - { - "cves": [ - { - "cve": "CVE-2024-45490", - "cvss_v3_score": "9.8", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "cwe": [ - "CWE-611" - ], - "cwe_details": { - "CWE-611": { - "name": "Improper Restriction of XML External Entity Reference", - "description": "The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output." - } - } - } - ], - "summary": "An issue was discovered in libexpat before 2.6.3. xmlparse.c does not reject a negative length for XML_ParseBuffer.", - "severity": "Critical", - "components": { - "deb://debian:bookworm:libexpat1:2.5.0-1": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1/sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "full_path": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - }, - { - "component_id": "deb://debian:bookworm:libexpat1:2.5.0-1", - "full_path": "libexpat1:2.5.0-1" - } - ] - ] - } - }, - "issue_id": "XRAY-632613", - "references": [ - "https://github.com/libexpat/libexpat/issues/887", - "https://security-tracker.debian.org/tracker/CVE-2024-45490", - "https://github.com/libexpat/libexpat/pull/890" - ] - }, - { - "cves": [ - { - "cve": "CVE-2024-45492", - "cvss_v3_score": "9.8", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "cwe": [ - "CWE-190" - ], - "cwe_details": { - "CWE-190": { - "name": "Integer Overflow or Wraparound", - "description": "The product performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.", - "categories": [ - { - "category": "2023 CWE Top 25", - "rank": "14" - } - ] - } - } - } - ], - "summary": "An issue was discovered in libexpat before 2.6.3. nextScaffoldPart in xmlparse.c can have an integer overflow for m_groupSize on 32-bit platforms (where UINT_MAX equals SIZE_MAX).", - "severity": "Critical", - "components": { - "deb://debian:bookworm:libexpat1:2.5.0-1": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1/sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "full_path": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - }, - { - "component_id": "deb://debian:bookworm:libexpat1:2.5.0-1", - "full_path": "libexpat1:2.5.0-1" - } - ] - ] - } - }, - "issue_id": "XRAY-632612", - "references": [ - "https://github.com/libexpat/libexpat/issues/889", - "https://security-tracker.debian.org/tracker/CVE-2024-45492", - "https://github.com/libexpat/libexpat/pull/892" - ] - }, - { - "cves": [ - { - "cve": "CVE-2023-51767", - "cvss_v3_score": "7.0", - "cvss_v3_vector": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H", - "cwe": [ - "NVD-CWE-Other" - ] - } - ], - "summary": "OpenSSH through 9.6, when common types of DRAM are used, might allow row hammer attacks (for authentication bypass) because the integer value of authenticated in mm_answer_authpassword does not resist flips of a single bit. NOTE: this is applicable to a certain threat model of attacker-victim co-location in which the attacker has user privileges.", - "severity": "Low", - "components": { - "deb://debian:bookworm:openssh-client:1:9.2p1-2+deb12u3": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1/sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "full_path": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - }, - { - "component_id": "deb://debian:bookworm:openssh-client:1:9.2p1-2+deb12u3", - "full_path": "openssh-client:1:9.2p1-2+deb12u3" - } - ] - ] - } - }, - "issue_id": "XRAY-585612", - "references": [ - "https://arxiv.org/abs/2309.02545", - "https://github.com/openssh/openssh-portable/blob/8241b9c0529228b4b86d88b1a6076fb9f97e4a99/monitor.c#L878", - "https://github.com/openssh/openssh-portable/blob/8241b9c0529228b4b86d88b1a6076fb9f97e4a99/auth-passwd.c#L77", - "https://bugzilla.redhat.com/show_bug.cgi?id=2255850", - "https://security-tracker.debian.org/tracker/CVE-2023-51767", - "https://ubuntu.com/security/CVE-2023-51767", - "https://security.netapp.com/advisory/ntap-20240125-0006/", - "https://access.redhat.com/security/cve/CVE-2023-51767" - ], - "extended_information": { - "short_description": "The RowHammer fault injection attack can theoretically lead to local authentication bypass in OpenSSH.", - "full_description": "[OpenSSH](https://www.openssh.com/) is a popular open-source implementation of the SSH (Secure Shell) protocol, providing encrypted communication over a network.\nIt was discovered that the OpenSSH authentication logic can be susceptible in some cases to a side-channel fault injection attack. The attack can theoretically be carried out by a local attacker which eventually bypass OpenSSH authentication mechanism.\n\nThis vulnerability currently lacks widely known published exploits, and its exploitation is considered highly complex. The intricacies of the attack, combined with the absence of well-documented exploits, contribute to the difficulty in achieving successful exploitation. Furthermore, it's essential to note that the susceptibility to this vulnerability is hardware-dependent, and the success of an attack relies on probabilities associated with the specific hardware configuration. \n\nThe vulnerability is theoretically exploitable by several different ways, the only two published ways are:\n\nIn the OpenSSH function `mm_answer_authpassword()`, a stack variable `authenticated`, is assigned to the value of the function `auth_password()` which returns 1/0 and then returned. If the value of `authenticated` is 1, the SSH connection will be established. Since `authenticated` is stored on the stack, therefore in DRAM, a local attacker could flip this 32-bit integer least significant bit, thus, bypass authentication.\n\nAnother possible exploit is the `result` stack variable in `auth_password()` function. It is initialized to 0 and set to 1 if the password is correct. \nSimilarly to the previous method, this attack requires a single bit flip of the `result` variable in order for the function to return 1 and bypass the authentication.\n\nAttackers can trigger the vulnerability via a RowHammer fault injection. The Rowhammer bug is a hardware reliability issue in which an attacker repeatedly accesses (hammers) DRAM cells to cause unauthorized changes in physically adjacent memory locations.\nSimply put:\n\n* A specific register value(`authenticated`/`result` value) is pushed onto the stack during program execution. \n* The stack, where the register value is stored, is identified to be located in a memory row susceptible to bit flips (flippable row) due to the RowHammer vulnerability in DRAM.\n* The attacker performs a series of rapid and repeated memory accesses to the adjacent rows of the flippable row in the DRAM. This repeated access exploits the RowHammer vulnerability, inducing bit flips in the targeted flippable row.\n* Due to the RowHammer effect, bit flips occur in the flippable row, potentially corrupting the data stored there.\n* After inducing bit flips in the flippable row, the attacker manipulates the program's control flow to pop the corrupted value from the stack into a register.\n* The register now holds a value that has been corrupted through the RowHammer attack. Now the `authenticated`/`result` variables hold this corrupted value thus it can lead to authentication bypass, as it may impact the control flow in a way advantageous to the attacker.", - "jfrog_research_severity": "Low", - "jfrog_research_severity_reasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The vulnerability depends on the OS and hardware. It was only evaluated in one test environment, therefore results for other conditions might differ. The attacker must be extremely familiar with the details of the exploited system (ex. know the exact hardware which is running the OS).", - "is_positive": true - }, - { - "name": "The issue can only be exploited by an attacker that can execute code on the vulnerable machine (excluding exceedingly rare circumstances)", - "is_positive": true - }, - { - "name": "No high-impact exploit or technical writeup were published, and exploitation of the issue with high impact is either non-trivial or completely unproven", - "description": "Exploitation is extremely non-trivial (even theoretically), no public exploits have been published.", - "is_positive": true - }, - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The vulnerability's attack complexity is significantly higher than what the CVSS represents.", - "is_positive": true - } - ] - } - }, - { - "cves": [ - { - "cve": "CVE-2011-3374", - "cvss_v2_score": "4.3", - "cvss_v2_vector": "CVSS:2.0/AV:N/AC:M/Au:N/C:N/I:P/A:N", - "cvss_v3_score": "3.7", - "cvss_v3_vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N", - "cwe": [ - "CWE-347" - ], - "cwe_details": { - "CWE-347": { - "name": "Improper Verification of Cryptographic Signature", - "description": "The product does not verify, or incorrectly verifies, the cryptographic signature for data." - } - } - } - ], - "summary": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.", - "severity": "Low", - "components": { - "deb://debian:bookworm:apt:2.6.1": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e/sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar", - "full_path": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - }, - { - "component_id": "deb://debian:bookworm:apt:2.6.1", - "full_path": "apt:2.6.1" - } - ] - ] - }, - "deb://debian:bookworm:libapt-pkg6.0:2.6.1": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e/sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar", - "full_path": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - }, - { - "component_id": "deb://debian:bookworm:libapt-pkg6.0:2.6.1", - "full_path": "libapt-pkg6.0:2.6.1" - } - ] - ] - } - }, - "issue_id": "XRAY-34417", - "references": [ - "https://people.canonical.com/~ubuntu-security/cve/2011/CVE-2011-3374.html", - "https://seclists.org/fulldisclosure/2011/Sep/221", - "https://ubuntu.com/security/CVE-2011-3374", - "https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=642480", - "https://access.redhat.com/security/cve/cve-2011-3374", - "https://snyk.io/vuln/SNYK-LINUX-APT-116518", - "https://security-tracker.debian.org/tracker/CVE-2011-3374" - ], - "extended_information": { - "short_description": "Improper signature validation in apt-key may enable Man-in-the-Middle attacks and result in code execution.", - "full_description": "`apt-key` is [`apt`](https://github.com/Debian/apt)'s key management utility, and is used to manage the keys that are used by `apt` to authenticate packages.\n\nA vulnerability in `apt-key`'s `net-update` function exists, in which [`GPG`](https://www.gnupg.org/) keys, that are used for signing packages and validating their authenticity, aren't validated correctly. The `net-update` function pulls the signing keys that should be added from an insecure location (`http://...`), exposing it to a Man-in-the-Middle attack in which malicious signing keys could be added to the system's keyring. This issue happens due to a vulnerability in the `add_keys_with_veirfy_against_master_keyring()` function, which allows adding signing keys without proper signature validation. \n\nThis vulnerability then potentially allows a malicious actor to perform a Man-in-the-Middle attack on a target, by making it validate malicious packages that were signed with the `GPG` signing key used by the attacker. Effectively, this means that `apt` can be duped to install malicious services and daemons with root privileges.\n\nThe conditions for this vulnerability to be applicable:\n \n1. A valid URI should be configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`. This is the URI that an attacker would need to target in a Man In The Middle attack.\n2. The command `apt-key net-update` should be executed on the affected system, or alternatively `apt.auth.net_update()` function from [python-apt](https://pypi.org/project/python-apt/) Python module should be called. This is for the malicious keys download.\n3. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine.\n\nDo note that `apt-key` is **deprecated** and shouldn't be used, and in most Debian versions `ARCHIVE_KEYRING_URI` is not defined, making this vulnerability unexploitable in most Debian systems.", - "jfrog_research_severity": "High", - "jfrog_research_severity_reasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The conditions for this vulnerability to be applicable:\n \n1. A valid URI should be configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n2. The command `apt-key net-update` should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the python-apt Python module should be called. This is for the malicious keys download.\n3. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine.", - "is_positive": true - }, - { - "name": "The issue can be exploited by attackers over the network", - "description": "This vulnerability is remotely exploitable when the applicability conditions apply." - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Remote code execution is possible when the applicability conditions apply." - }, - { - "name": "The issue has an exploit published", - "description": "The reporter of this issue has provided a GPG key that can be used for an actual attack, as well as a simple PoC example." - } - ], - "remediation": "##### Deployment mitigations\n\n* Dot not execute `apt-key` command, as it is deprecated.\n* Remove the URI configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`." - } - }, - { - "cves": [ - { - "cve": "CVE-2024-4741" - } - ], - "summary": "CVE-2024-4741", - "severity": "Unknown", - "components": { - "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1": { - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", - "full_path": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - }, - { - "component_id": "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1", - "full_path": "libssl3:3.0.13-1~deb12u1" - } - ] - ] - }, - "deb://debian:bookworm:openssl:3.0.13-1~deb12u1": { - "fixed_versions": [ - "[3.0.14-1~deb12u1]" - ], - "impact_paths": [ - [ - { - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest" - }, - { - "component_id": "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", - "full_path": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - }, - { - "component_id": "deb://debian:bookworm:openssl:3.0.13-1~deb12u1", - "full_path": "openssl:3.0.13-1~deb12u1" - } - ] - ] - } - }, - "issue_id": "XRAY-603657", - "references": [ - "https://security-tracker.debian.org/tracker/CVE-2024-4741" - ] - } - ], - "component_id": "docker://platform.jfrog.io/swamp-docker/swamp:latest", - "package_type": "oci", - "status": "completed" - } - ] - }, - "jas_scans": { - "contextual_analysis": [ - { - "tool": { - "driver": { - "informationUri": "https://jfrog.com/help/r/jfrog-security-documentation/jfrog-advanced-security", - "name": "JFrog Applicability Scanner", - "rules": [ - { - "id": "applic_CVE-2024-6119", - "name": "CVE-2024-6119", - "shortDescription": { - "text": "Scanner for CVE-2024-6119" - }, - "fullDescription": { - "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `X509_VERIFY_PARAM_set1_email`\n\n- `X509_check_email`\n\n- `X509_VERIFY_PARAM_set1_host`\n\n- `X509_check_host`", - "markdown": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `X509_VERIFY_PARAM_set1_email`\n\n- `X509_check_email`\n\n- `X509_VERIFY_PARAM_set1_host`\n\n- `X509_check_host`" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2024-45490", - "name": "CVE-2024-45490", - "shortDescription": { - "text": "Scanner for CVE-2024-45490" - }, - "fullDescription": { - "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `XML_Parse()`\n- `XML_ParseBuffer()`\n\nAn additional condition, which the scanner currently does not check, is that the `len` parameter which is passed to those functions is user-controlled.", - "markdown": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `XML_Parse()`\n- `XML_ParseBuffer()`\n\nAn additional condition, which the scanner currently does not check, is that the `len` parameter which is passed to those functions is user-controlled." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2024-38428", - "name": "CVE-2024-38428", - "shortDescription": { - "text": "Scanner for CVE-2024-38428" - }, - "fullDescription": { - "text": "", - "markdown": "" - }, - "properties": { - "applicability": "undetermined", - "conclusion": "private" - } - }, - { - "id": "applic_CVE-2024-45492", - "name": "CVE-2024-45492", - "shortDescription": { - "text": "Scanner for CVE-2024-45492" - }, - "fullDescription": { - "text": "The scanner checks whether the current binary was compiled with 32-bit architecture and if any of the vulnerable functions are called:\n\n- `XML_ParseBuffer()`\n- `XML_Parse()`\n\nNote - the vulnerability occurs when certain inputs are passed to those functions.", - "markdown": "The scanner checks whether the current binary was compiled with 32-bit architecture and if any of the vulnerable functions are called:\n\n- `XML_ParseBuffer()`\n- `XML_Parse()`\n\nNote - the vulnerability occurs when certain inputs are passed to those functions." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2023-51767", - "name": "CVE-2023-51767", - "shortDescription": { - "text": "Scanner for CVE-2023-51767" - }, - "fullDescription": { - "text": "The CVE is always applicable.\n\nNote - The vulnerability is hardware-dependent.", - "markdown": "The CVE is always applicable.\n\nNote - The vulnerability is hardware-dependent." - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative" - } - }, - { - "id": "applic_CVE-2011-3374", - "name": "CVE-2011-3374", - "shortDescription": { - "text": "Scanner for CVE-2011-3374" - }, - "fullDescription": { - "text": "The scanner checks if the vulnerable variable `ARCHIVE_KEYRING_URI` in `/usr/bin/apt-key` is not empty and not commented out. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n\nThe below prerequisites are also crucial for exploitability but are not checked in the scanner:\n\n1. The command apt-key net-update should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the `python-apt` Python module should be called. This is for the malicious keys download.\n\n2. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine.", - "markdown": "The scanner checks if the vulnerable variable `ARCHIVE_KEYRING_URI` in `/usr/bin/apt-key` is not empty and not commented out. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n\nThe below prerequisites are also crucial for exploitability but are not checked in the scanner:\n\n1. The command apt-key net-update should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the `python-apt` Python module should be called. This is for the malicious keys download.\n\n2. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine." - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive", - "security-severity": "6.9" - } - }, - { - "id": "applic_CVE-2024-4741", - "name": "CVE-2024-4741", - "shortDescription": { - "text": "Scanner for CVE-2024-4741" - }, - "fullDescription": { - "text": "The scanner checks whether the vulnerable function `SSL_free_buffers` is called.", - "markdown": "The scanner checks whether the vulnerable function `SSL_free_buffers` is called." - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - } - ], - "version": "1.0" - } - }, - "invocations": [ - { - "arguments": [ - "/Users/user/.jfrog/dependencies/analyzerManager/jas_scanner/jas_scanner", - "scan", - "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210780-681556384/Applicability_1726210780/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210535-1985298017" - } - } - ], - "results": [ - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2024-4741", - "message": { - "text": "References to the vulnerable functions were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///usr/local/bin/node" - }, - "region": { - "snippet": { - "text": "" - } - } - } - } - ] - }, - { - "ruleId": "applic_CVE-2024-45490", - "kind": "pass", - "message": { - "text": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `XML_Parse()`\n- `XML_ParseBuffer()`\n\nAn additional condition, which the scanner currently does not check, is that the `len` parameter which is passed to those functions is user-controlled." - } - }, - { - "ruleId": "applic_CVE-2011-3374", - "kind": "pass", - "message": { - "text": "The scanner checks if the vulnerable variable `ARCHIVE_KEYRING_URI` in `/usr/bin/apt-key` is not empty and not commented out. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n\nThe below prerequisites are also crucial for exploitability but are not checked in the scanner:\n\n1. The command apt-key net-update should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the `python-apt` Python module should be called. This is for the malicious keys download.\n\n2. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine." - } - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2024-6119", - "message": { - "text": "References to the vulnerable functions were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///usr/local/bin/node" - }, - "region": { - "snippet": { - "text": "" - } - } - } - } - ] - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "applic_CVE-2024-6119", - "message": { - "text": "References to the vulnerable functions were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///usr/local/bin/node" - }, - "region": { - "snippet": { - "text": "" - } - } - } - } - ] - }, - { - "ruleId": "applic_CVE-2024-45492", - "kind": "pass", - "message": { - "text": "The scanner checks whether the current binary was compiled with 32-bit architecture and if any of the vulnerable functions are called:\n\n- `XML_ParseBuffer()`\n- `XML_Parse()`\n\nNote - the vulnerability occurs when certain inputs are passed to those functions." - } - } - ] - } - ], - "secrets": [ - { - "tool": { - "driver": { - "informationUri": "https://jfrog.com/help/r/jfrog-security-documentation/jfrog-advanced-security", - "name": "JFrog Secrets scanner", - "rules": [ - { - "id": "REQ.SECRET.GENERIC.TEXT", - "name": "REQ.SECRET.GENERIC.TEXT", - "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.TEXT" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive" - } - }, - { - "id": "REQ.SECRET.GENERIC.CODE", - "name": "REQ.SECRET.GENERIC.CODE", - "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.CODE" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - }, - { - "id": "REQ.SECRET.KEYS", - "name": "REQ.SECRET.KEYS", - "shortDescription": { - "text": "Scanner for REQ.SECRET.KEYS" - }, - "fullDescription": { - "text": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\u0026tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n", - "markdown": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\u0026tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - }, - { - "id": "REQ.CRED.PUBLIC-ONLY", - "name": "REQ.CRED.PUBLIC-ONLY", - "shortDescription": { - "text": "Scanner for REQ.CRED.PUBLIC-ONLY" - }, - "fullDescription": { - "text": "", - "markdown": "" - }, - "properties": { - "applicability": "undetermined", - "conclusion": "private" - } - }, - { - "id": "REQ.SECRET.GENERIC.URL", - "name": "REQ.SECRET.GENERIC.URL", - "shortDescription": { - "text": "Scanner for REQ.SECRET.GENERIC.URL" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - } - ], - "version": "1.0" - } - }, - "invocations": [ - { - "arguments": [ - "/Users/user/.jfrog/dependencies/analyzerManager/jas_scanner/jas_scanner", - "scan", - "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210780-681556384/Secrets_1726210839/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210535-1985298017" - } - } - ], - "results": [ - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.GENERIC.CODE", - "message": { - "text": "Hardcoded secrets were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///private/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/tmpsfyn_3d1/unpacked/filesystem/blobs/sha256/9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0/usr/src/app/server/index.js" - }, - "region": { - "startLine": 5, - "startColumn": 7, - "endLine": 5, - "endColumn": 57, - "snippet": { - "text": "tok************" - } - } - } - } - ] - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.KEYS", - "message": { - "text": "Secret keys were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///private/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/tmpsfyn_3d1/unpacked/filesystem/blobs/sha256/9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0/usr/src/app/server/index.js" - }, - "region": { - "startLine": 6, - "startColumn": 14, - "endLine": 6, - "endColumn": 24, - "snippet": { - "text": "eyJ************" - } - } - } - } - ] - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.GENERIC.URL", - "message": { - "text": "Hardcoded secrets were found" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "file:///usr/src/app/server/scripts/__pycache__/fetch_github_repo.cpython-311.pyc" - }, - "region": { - "snippet": { - "text": "htt************" - } - } - } - } - ] - } - ] - } - ] - } - } - ] -} \ No newline at end of file diff --git a/tests/testdata/output/dockerscan/docker_sarif.json b/tests/testdata/output/dockerscan/docker_sarif.json index 7d50c474..5d663358 100644 --- a/tests/testdata/output/dockerscan/docker_sarif.json +++ b/tests/testdata/output/dockerscan/docker_sarif.json @@ -2,240 +2,6 @@ "version": "2.1.0", "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "runs": [ - { - "tool": { - "driver": { - "informationUri": "https://jfrog.com/help/r/jfrog-security-documentation/jfrog-advanced-security", - "name": "JFrog Binary Secrets Scanner", - "rules": [ - { - "id": "REQ.SECRET.GENERIC.TEXT", - "name": "REQ.SECRET.GENERIC.TEXT", - "shortDescription": { - "text": "[Secret in Binary found] Scanner for REQ.SECRET.GENERIC.TEXT" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "help": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "not_applicable", - "conclusion": "positive" - } - }, - { - "id": "REQ.SECRET.GENERIC.CODE", - "name": "REQ.SECRET.GENERIC.CODE", - "shortDescription": { - "text": "[Secret in Binary found] Scanner for REQ.SECRET.GENERIC.CODE" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "help": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - }, - { - "id": "REQ.SECRET.KEYS", - "name": "REQ.SECRET.KEYS", - "shortDescription": { - "text": "[Secret in Binary found] Scanner for REQ.SECRET.KEYS" - }, - "fullDescription": { - "text": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n", - "markdown": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" - }, - "help": { - "text": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n", - "markdown": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - }, - { - "id": "REQ.CRED.PUBLIC-ONLY", - "name": "REQ.CRED.PUBLIC-ONLY", - "shortDescription": { - "text": "[Secret in Binary found] Scanner for REQ.CRED.PUBLIC-ONLY" - }, - "fullDescription": { - "text": "", - "markdown": "" - }, - "help": { - "text": "", - "markdown": "" - }, - "properties": { - "applicability": "undetermined", - "conclusion": "private" - } - }, - { - "id": "REQ.SECRET.GENERIC.URL", - "name": "REQ.SECRET.GENERIC.URL", - "shortDescription": { - "text": "[Secret in Binary found] Scanner for REQ.SECRET.GENERIC.URL" - }, - "fullDescription": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "help": { - "text": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n", - "markdown": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - "properties": { - "applicability": "applicable", - "conclusion": "negative", - "security-severity": "6.9" - } - } - ], - "version": "1.0" - } - }, - "invocations": [ - { - "arguments": [ - "/Users/user/.jfrog/dependencies/analyzerManager/jas_scanner/jas_scanner", - "scan", - "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210780-681556384/Secrets_1726210839/config.yaml" - ], - "executionSuccessful": true, - "workingDirectory": { - "uri": "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210535-1985298017" - } - } - ], - "results": [ - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.GENERIC.CODE", - "message": { - "text": "Hardcoded secrets were found", - "markdown": "🔒 Found Secrets in Binary docker scanning:\nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): 9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0\nFilepath: usr/src/app/server/index.js\nEvidence: tok************" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "usr/src/app/server/index.js" - }, - "region": { - "startLine": 5, - "startColumn": 7, - "endLine": 5, - "endColumn": 57, - "snippet": { - "text": "tok************" - } - } - }, - "logicalLocations": [ - { - "name": "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", - "kind": "layer", - "properties": { - "algorithm": "sha256" - } - } - ] - } - ], - "fingerprints": { - "jfrogFingerprintHash": "00436fac1d19ea36302f14e892926efb" - } - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.KEYS", - "message": { - "text": "Secret keys were found", - "markdown": "🔒 Found Secrets in Binary docker scanning:\nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): 9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0\nFilepath: usr/src/app/server/index.js\nEvidence: eyJ************" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "usr/src/app/server/index.js" - }, - "region": { - "startLine": 6, - "startColumn": 14, - "endLine": 6, - "endColumn": 24, - "snippet": { - "text": "eyJ************" - } - } - }, - "logicalLocations": [ - { - "name": "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", - "kind": "layer", - "properties": { - "algorithm": "sha256" - } - } - ] - } - ], - "fingerprints": { - "jfrogFingerprintHash": "2550dbdb124696ae8fcc5cfd6f2b65b8" - } - }, - { - "properties": { - "metadata": "", - "tokenValidation": "" - }, - "ruleId": "REQ.SECRET.GENERIC.URL", - "message": { - "text": "Hardcoded secrets were found", - "markdown": "🔒 Found Secrets in Binary docker scanning:\nImage: platform.jfrog.io/swamp-docker/swamp:latest\nFilepath: usr/src/app/server/scripts/__pycache__/fetch_github_repo.cpython-311.pyc\nEvidence: htt************" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "usr/src/app/server/scripts/__pycache__/fetch_github_repo.cpython-311.pyc" - }, - "region": { - "snippet": { - "text": "htt************" - } - } - } - } - ], - "fingerprints": { - "jfrogFingerprintHash": "9164423e88bbec9d1216bc5600eb7f9b" - } - } - ] - }, { "tool": { "driver": { @@ -243,116 +9,25 @@ "name": "JFrog Xray Scanner", "rules": [ { - "id": "CVE-2024-45490_debian:bookworm:libexpat1_2.5.0-1", - "shortDescription": { - "text": "[CVE-2024-45490] debian:bookworm:libexpat1 2.5.0-1" - }, - "help": { - "text": "An issue was discovered in libexpat before 2.6.3. xmlparse.c does not reject a negative length for XML_ParseBuffer.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 9.8 | Not Applicable | `sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar ` | No fix available |" - }, - "properties": { - "security-severity": "9.8" - } - }, - { - "id": "CVE-2011-3374_debian:bookworm:apt_2.6.1", - "shortDescription": { - "text": "[CVE-2011-3374] debian:bookworm:apt 2.6.1" - }, - "help": { - "text": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 3.7 | Not Applicable | `sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar ` | No fix available |" - }, - "properties": { - "security-severity": "3.7" - } - }, - { - "id": "CVE-2024-38428_debian:bookworm:wget_1.21.3-1+b1", - "shortDescription": { - "text": "[CVE-2024-38428] debian:bookworm:wget 1.21.3-1+b1" - }, - "help": { - "text": "url.c in GNU Wget through 1.24.5 mishandles semicolons in the userinfo subcomponent of a URI, and thus there may be insecure behavior in which data that was supposed to be in the userinfo subcomponent is misinterpreted to be part of the host subcomponent.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 9.1 | Undetermined | `sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ` | No fix available |" - }, - "properties": { - "security-severity": "9.1" - } - }, - { - "id": "XRAY-264729_cors.js_0.0.1-security", + "id": "CVE-2024-38428_debian:bookworm:openssl_3.0.13-1~deb12u1", "shortDescription": { - "text": "[XRAY-264729] cors.js 0.0.1-security" + "text": "[CVE-2024-38428] debian:bookworm:openssl 3.0.13-1~deb12u1" }, "help": { - "text": "Malicious package cors.js for Node.js", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 10.0 | Not Covered | `sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar ` | No fix available |" + "text": "Interpretation Conflict", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 10.0 | Undetermined | `sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ` | [3.0.14-1~deb12u2] |" }, "properties": { "security-severity": "10.0" } }, - { - "id": "CVE-2024-45492_debian:bookworm:libexpat1_2.5.0-1", - "shortDescription": { - "text": "[CVE-2024-45492] debian:bookworm:libexpat1 2.5.0-1" - }, - "help": { - "text": "An issue was discovered in libexpat before 2.6.3. nextScaffoldPart in xmlparse.c can have an integer overflow for m_groupSize on 32-bit platforms (where UINT_MAX equals SIZE_MAX).", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 9.8 | Not Applicable | `sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar ` | No fix available |" - }, - "properties": { - "security-severity": "9.8" - } - }, - { - "id": "CVE-2023-51767_debian:bookworm:openssh-client:1_9.2p1-2+deb12u3", - "shortDescription": { - "text": "[CVE-2023-51767] debian:bookworm:openssh-client:1 9.2p1-2+deb12u3" - }, - "help": { - "text": "OpenSSH through 9.6, when common types of DRAM are used, might allow row hammer attacks (for authentication bypass) because the integer value of authenticated in mm_answer_authpassword does not resist flips of a single bit. NOTE: this is applicable to a certain threat model of attacker-victim co-location in which the attacker has user privileges.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 7.0 | Applicable | `sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar ` | No fix available |" - }, - "properties": { - "security-severity": "7.0" - } - }, - { - "id": "CVE-2011-3374_debian:bookworm:libapt-pkg6.0_2.6.1", - "shortDescription": { - "text": "[CVE-2011-3374] debian:bookworm:libapt-pkg6.0 2.6.1" - }, - "help": { - "text": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 3.7 | Not Applicable | `sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar ` | No fix available |" - }, - "properties": { - "security-severity": "3.7" - } - }, - { - "id": "CVE-2024-4741_debian:bookworm:openssl_3.0.13-1~deb12u1", - "shortDescription": { - "text": "[CVE-2024-4741] debian:bookworm:openssl 3.0.13-1~deb12u1" - }, - "help": { - "text": "CVE-2024-4741", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 0.0 | Applicable | `sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ` | [3.0.14-1~deb12u1] |" - }, - "properties": { - "security-severity": "0.0" - } - }, { "id": "CVE-2024-6119_debian:bookworm:libssl3_3.0.13-1~deb12u1", "shortDescription": { "text": "[CVE-2024-6119] debian:bookworm:libssl3 3.0.13-1~deb12u1" }, "help": { - "text": "Issue summary: Applications performing certificate name checks (e.g., TLS\nclients checking server certificates) may attempt to read an invalid memory\naddress resulting in abnormal termination of the application process.\n\nImpact summary: Abnormal termination of an application can a cause a denial of\nservice.\n\nApplications performing certificate name checks (e.g., TLS clients checking\nserver certificates) may attempt to read an invalid memory address when\ncomparing the expected name with an `otherName` subject alternative name of an\nX.509 certificate. This may result in an exception that terminates the\napplication program.\n\nNote that basic certificate chain validation (signatures, dates, ...) is not\naffected, the denial of service can occur only when the application also\nspecifies an expected DNS name, Email address or IP address.\n\nTLS servers rarely solicit client certificates, and even when they do, they\ngenerally don't perform a name check against a reference identifier (expected\nidentity), but rather extract the presented identity after checking the\ncertificate chain. So TLS servers are generally not affected and the severity\nof the issue is Moderate.\n\nThe FIPS modules in 3.3, 3.2, 3.1 and 3.0 are not affected by this issue.", + "text": "Issue summary", "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 0.0 | Applicable | `sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ` | No fix available |" }, "properties": { @@ -360,40 +35,27 @@ } }, { - "id": "CVE-2024-6119_debian:bookworm:openssl_3.0.13-1~deb12u1", - "shortDescription": { - "text": "[CVE-2024-6119] debian:bookworm:openssl 3.0.13-1~deb12u1" - }, - "help": { - "text": "Issue summary: Applications performing certificate name checks (e.g., TLS\nclients checking server certificates) may attempt to read an invalid memory\naddress resulting in abnormal termination of the application process.\n\nImpact summary: Abnormal termination of an application can a cause a denial of\nservice.\n\nApplications performing certificate name checks (e.g., TLS clients checking\nserver certificates) may attempt to read an invalid memory address when\ncomparing the expected name with an `otherName` subject alternative name of an\nX.509 certificate. This may result in an exception that terminates the\napplication program.\n\nNote that basic certificate chain validation (signatures, dates, ...) is not\naffected, the denial of service can occur only when the application also\nspecifies an expected DNS name, Email address or IP address.\n\nTLS servers rarely solicit client certificates, and even when they do, they\ngenerally don't perform a name check against a reference identifier (expected\nidentity), but rather extract the presented identity after checking the\ncertificate chain. So TLS servers are generally not affected and the severity\nof the issue is Moderate.\n\nThe FIPS modules in 3.3, 3.2, 3.1 and 3.0 are not affected by this issue.", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 0.0 | Applicable | `sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ` | [3.0.14-1~deb12u2] |" - }, - "properties": { - "security-severity": "0.0" - } - }, - { - "id": "CVE-2024-4741_debian:bookworm:libssl3_3.0.13-1~deb12u1", + "id": "CVE-2024-38428_debian:bookworm:libssl3_3.0.13-1~deb12u1", "shortDescription": { - "text": "[CVE-2024-4741] debian:bookworm:libssl3 3.0.13-1~deb12u1" + "text": "[CVE-2024-38428] debian:bookworm:libssl3 3.0.13-1~deb12u1" }, "help": { - "text": "CVE-2024-4741", - "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 0.0 | Applicable | `sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ` | No fix available |" + "text": "Interpretation Conflict", + "markdown": "| Severity Score | Contextual Analysis | Direct Dependencies | Fixed Versions |\n| :---: | :---: | :---: | :---: |\n| 10.0 | Undetermined | `sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ` | No fix available |" }, "properties": { - "security-severity": "0.0" + "security-severity": "10.0" } } ], - "version": "3.104.8" + "version": "3.107.13" } }, "invocations": [ { "executionSuccessful": true, "workingDirectory": { - "uri": "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210535-1985298017" + "uri": "temp/folders/T/jfrog.cli.temp.-11-11" } } ], @@ -404,7 +66,7 @@ "fixedVersion": "No fix available" }, "ruleId": "CVE-2024-6119_debian:bookworm:libssl3_3.0.13-1~deb12u1", - "ruleIndex": 8, + "ruleIndex": 1, "level": "none", "message": { "text": "[CVE-2024-6119] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ", @@ -432,46 +94,12 @@ "jfrogFingerprintHash": "5b5d2ba57a2eddf58f4579b7ebe42599" } }, - { - "properties": { - "applicability": "Applicable", - "fixedVersion": "[3.0.14-1~deb12u2]" - }, - "ruleId": "CVE-2024-6119_debian:bookworm:openssl_3.0.13-1~deb12u1", - "ruleIndex": 9, - "level": "none", - "message": { - "text": "[CVE-2024-6119] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ", - "markdown": "[CVE-2024-6119] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - } - }, - "logicalLocations": [ - { - "name": "f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595", - "kind": "layer", - "properties": { - "algorithm": "sha256" - } - } - ] - } - ], - "fingerprints": { - "jfrogFingerprintHash": "bd5908946de9c082f96e15217590eebc" - } - }, { "properties": { "applicability": "Undetermined", "fixedVersion": "No fix available" }, - "ruleId": "CVE-2024-38428_debian:bookworm:wget_1.21.3-1+b1", + "ruleId": "CVE-2024-38428_debian:bookworm:libssl3_3.0.13-1~deb12u1", "ruleIndex": 2, "level": "error", "message": { @@ -497,65 +125,31 @@ } ], "fingerprints": { - "jfrogFingerprintHash": "db89861310f80a270a0a81f48d7dc974" - } - }, - { - "properties": { - "applicability": "Not Covered", - "fixedVersion": "No fix available" - }, - "ruleId": "XRAY-264729_cors.js_0.0.1-security", - "ruleIndex": 3, - "level": "error", - "message": { - "text": "[XRAY-264729] sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar ", - "markdown": "[XRAY-264729] sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar" - } - }, - "logicalLocations": [ - { - "name": "ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f", - "kind": "layer", - "properties": { - "algorithm": "sha256" - } - } - ] - } - ], - "fingerprints": { - "jfrogFingerprintHash": "d653c414ef56560432b122358961104a" + "jfrogFingerprintHash": "e4ad8f8a303959f43d395a9967290329" } }, { "properties": { - "applicability": "Not Applicable", - "fixedVersion": "No fix available" + "applicability": "Undetermined", + "fixedVersion": "[3.0.14-1~deb12u2]" }, - "ruleId": "CVE-2024-45490_debian:bookworm:libexpat1_2.5.0-1", + "ruleId": "CVE-2024-38428_debian:bookworm:openssl_3.0.13-1~deb12u1", "ruleIndex": 0, "level": "error", "message": { - "text": "[CVE-2024-45490] sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar ", - "markdown": "[CVE-2024-45490] sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): 20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1" + "text": "[CVE-2024-38428] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ", + "markdown": "[CVE-2024-38428] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" + "uri": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" } }, "logicalLocations": [ { - "name": "20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1", + "name": "f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595", "kind": "layer", "properties": { "algorithm": "sha256" @@ -565,167 +159,73 @@ } ], "fingerprints": { - "jfrogFingerprintHash": "61be5170151428187e85ff7b27fd65b4" + "jfrogFingerprintHash": "fde53f1fba5bffb4d79b03735c86ee2d" } - }, - { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "No fix available" - }, - "ruleId": "CVE-2024-45492_debian:bookworm:libexpat1_2.5.0-1", - "ruleIndex": 4, - "level": "error", - "message": { - "text": "[CVE-2024-45492] sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar ", - "markdown": "[CVE-2024-45492] sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): 20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1" - }, - "locations": [ + } + ] + }, + { + "tool": { + "driver": { + "name": "JFrog Binary Secrets Scanner", + "rules": [ { - "physicalLocation": { - "artifactLocation": { - "uri": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } + "id": "REQ.SECRET.GENERIC.CODE", + "shortDescription": { + "text": "[Secret in Binary found] Scanner for REQ.SECRET.GENERIC.CODE", + "markdown": "Scanner for REQ.SECRET.GENERIC.CODE" }, - "logicalLocations": [ - { - "name": "20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1", - "kind": "layer", - "properties": { - "algorithm": "sha256" - } - } - ] - } - ], - "fingerprints": { - "jfrogFingerprintHash": "e47bb0a94451ed5111fabcf0ccaaeee6" - } - }, - { - "properties": { - "applicability": "Applicable", - "fixedVersion": "No fix available" - }, - "ruleId": "CVE-2023-51767_debian:bookworm:openssh-client:1_9.2p1-2+deb12u3", - "ruleIndex": 5, - "level": "note", - "message": { - "text": "[CVE-2023-51767] sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar ", - "markdown": "[CVE-2023-51767] sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): 20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } + "fullDescription": { + "text": "The Scanner checks for REQ.SECRET.GENERIC.CODE", + "markdown": "The Scanner checks for REQ.SECRET.GENERIC.CODE" }, - "logicalLocations": [ - { - "name": "20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1", - "kind": "layer", - "properties": { - "algorithm": "sha256" - } - } - ] + "help": { + "text": "The Scanner checks for REQ.SECRET.GENERIC.CODE", + "markdown": "The Scanner checks for REQ.SECRET.GENERIC.CODE" + } } - ], - "fingerprints": { - "jfrogFingerprintHash": "fe7c1c90b3e7d340890027344468b42d" - } - }, + ] + } + }, + "invocations": [ { - "properties": { - "applicability": "Not Applicable", - "fixedVersion": "No fix available" - }, - "ruleId": "CVE-2011-3374_debian:bookworm:apt_2.6.1", - "ruleIndex": 1, - "level": "note", - "message": { - "text": "[CVE-2011-3374] sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar ", - "markdown": "[CVE-2011-3374] sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - } - }, - "logicalLocations": [ - { - "name": "cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e", - "kind": "layer", - "properties": { - "algorithm": "sha256" - } - } - ] - } - ], - "fingerprints": { - "jfrogFingerprintHash": "81f98a6fd77d17d7647c0ae81410b506" + "executionSuccessful": null, + "workingDirectory": { + "uri": "temp/folders/T/jfrog.cli.temp.-11-11" } - }, + } + ], + "results": [ { "properties": { - "applicability": "Not Applicable", - "fixedVersion": "No fix available" + "metadata": "expired", + "tokenValidation": "Inactive" }, - "ruleId": "CVE-2011-3374_debian:bookworm:libapt-pkg6.0_2.6.1", - "ruleIndex": 6, - "level": "note", + "ruleId": "REQ.SECRET.GENERIC.CODE", + "level": "info", "message": { - "text": "[CVE-2011-3374] sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar ", - "markdown": "[CVE-2011-3374] sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e" + "text": "Secret REQ.SECRET.GENERIC.CODE were found", + "markdown": "🔒 Found Secrets in Binary docker scanning:\nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): 9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0\nFilepath: usr/src/app/server/index.js\nEvidence: tok************\nToken Validation Inactive" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - } - }, - "logicalLocations": [ - { - "name": "cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e", - "kind": "layer", - "properties": { - "algorithm": "sha256" + "uri": "usr/src/app/server/index.js" + }, + "region": { + "startLine": 5, + "startColumn": 7, + "endLine": 5, + "endColumn": 57, + "snippet": { + "text": "tok************" } } - ] - } - ], - "fingerprints": { - "jfrogFingerprintHash": "7933bf1c7b4635012e7571e82e619db6" - } - }, - { - "properties": { - "applicability": "Applicable", - "fixedVersion": "[3.0.14-1~deb12u1]" - }, - "ruleId": "CVE-2024-4741_debian:bookworm:openssl_3.0.13-1~deb12u1", - "ruleIndex": 7, - "level": "none", - "message": { - "text": "[CVE-2024-4741] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ", - "markdown": "[CVE-2024-4741] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595" - }, - "locations": [ - { - "physicalLocation": { - "artifactLocation": { - "uri": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - } }, "logicalLocations": [ { - "name": "f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595", + "name": "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", "kind": "layer", "properties": { "algorithm": "sha256" @@ -735,31 +235,45 @@ } ], "fingerprints": { - "jfrogFingerprintHash": "a374e04992f42ee827634927edd7e8d4" + "jfrogFingerprintHash": "6b99fa4445061b0eea53dc99803c8eca" } }, { "properties": { - "applicability": "Applicable", - "fixedVersion": "No fix available" + "issueId": "sec-violation-1", + "metadata": "expired", + "policies": [ + "policy" + ], + "tokenValidation": "Inactive", + "watch": "watch" }, - "ruleId": "CVE-2024-4741_debian:bookworm:libssl3_3.0.13-1~deb12u1", - "ruleIndex": 10, - "level": "none", + "ruleId": "REQ.SECRET.GENERIC.CODE", + "ruleIndex": 0, + "level": "info", "message": { - "text": "[CVE-2024-4741] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar ", - "markdown": "[CVE-2024-4741] sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar \nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595" + "text": "Secret REQ.SECRET.GENERIC.CODE were found", + "markdown": "Security Violation 🔒 Found Secrets in Binary docker scanning:\nImage: platform.jfrog.io/swamp-docker/swamp:latest\nLayer (sha256): 9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0\nFilepath: usr/src/app/server/index.js\nEvidence: tok************\nToken Validation Inactive" }, "locations": [ { "physicalLocation": { "artifactLocation": { - "uri": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" + "uri": "usr/src/app/server/index.js" + }, + "region": { + "startLine": 5, + "startColumn": 7, + "endLine": 5, + "endColumn": 57, + "snippet": { + "text": "tok************" + } } }, "logicalLocations": [ { - "name": "f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595", + "name": "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", "kind": "layer", "properties": { "algorithm": "sha256" @@ -769,10 +283,10 @@ } ], "fingerprints": { - "jfrogFingerprintHash": "2c34553d9c75460bf14243ff13ba84c8" + "jfrogFingerprintHash": "7d0f57f0cc66fc6f3373fe1965297533" } } ] } ] -} +} \ No newline at end of file diff --git a/tests/testdata/output/dockerscan/docker_simple_json.json b/tests/testdata/output/dockerscan/docker_simple_json.json index 0fbd8a5a..acf41b3c 100644 --- a/tests/testdata/output/dockerscan/docker_simple_json.json +++ b/tests/testdata/output/dockerscan/docker_simple_json.json @@ -2,202 +2,6 @@ "vulnerabilities": [ { "severity": "Critical", - "impactedPackageName": "debian:bookworm:wget", - "impactedPackageVersion": "1.21.3-1+b1", - "impactedPackageType": "Debian", - "components": [ - { - "name": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", - "version": "", - "location": { - "file": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - } - } - ], - "summary": "url.c in GNU Wget through 1.24.5 mishandles semicolons in the userinfo subcomponent of a URI, and thus there may be insecure behavior in which data that was supposed to be in the userinfo subcomponent is misinterpreted to be part of the host subcomponent.", - "applicable": "Undetermined", - "fixedVersions": null, - "cves": [ - { - "id": "CVE-2024-38428", - "cvssV2": "", - "cvssV3": "9.1", - "applicability": { - "status": "Undetermined" - } - } - ], - "issueId": "XRAY-606103", - "references": [ - "https://git.savannah.gnu.org/cgit/wget.git/commit/?id=ed0c7c7e0e8f7298352646b2fd6e06a11e242ace", - "https://lists.gnu.org/archive/html/bug-wget/2024-06/msg00005.html", - "https://security-tracker.debian.org/tracker/CVE-2024-38428" - ], - "impactPaths": [ - [ - { - "name": "platform.jfrog.io/swamp-docker/swamp", - "version": "latest" - }, - { - "name": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", - "version": "", - "location": { - "file": "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar" - } - }, - { - "name": "debian:bookworm:wget", - "version": "1.21.3-1+b1", - "location": { - "file": "wget:1.21.3-1+b1" - } - } - ] - ], - "jfrogResearchInformation": null - }, - { - "severity": "Critical", - "impactedPackageName": "cors.js", - "impactedPackageVersion": "0.0.1-security", - "impactedPackageType": "npm", - "components": [ - { - "name": "sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar", - "version": "", - "location": { - "file": "sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar" - } - } - ], - "summary": "Malicious package cors.js for Node.js", - "applicable": "Not Covered", - "fixedVersions": null, - "cves": null, - "issueId": "XRAY-264729", - "references": [ - "https://registry.npmjs.com" - ], - "impactPaths": [ - [ - { - "name": "platform.jfrog.io/swamp-docker/swamp", - "version": "latest" - }, - { - "name": "sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar", - "version": "", - "location": { - "file": "sha256__ab1c0a95b2970fb44e2a4046c5c00f37a5b061e74d72b254a8975beb7d09f74f.tar" - } - }, - { - "name": "cors.js", - "version": "0.0.1-security", - "location": { - "file": "usr/src/app/node_modules/cors.js/package.json" - } - } - ] - ], - "jfrogResearchInformation": { - "severity": "Critical", - "summary": "Malicious package cors.js for Node.js", - "details": "The package cors.js for Node.js contains malicious code that installs a persistent connectback shell. The package is typosquatting the popular `cors` package. When installed, the package opens a connectback shell to the hardcoded host `107.175.32.229` on TCP port 56173. The malicious payload achieves persistency by installing a cron job that repeats every 10 seconds - `*/10 * * * * *`", - "remediation": "As with any malware, the malicious package must be completely removed, and steps must be taken care to remediate the damage that was done by the malicious package -\n\n##### Removing the malicious package\n\nRun `npm uninstall cors.js`\n\n##### Refreshing stolen credentials\n\nMany malicious packages steal stored user credentials, focusing on the following -\n\n* [Browser autocomplete](https://jfrog.com/blog/malicious-pypi-packages-stealing-credit-cards-injecting-code/) data, such as saved passwords and credit cards\n* [Environment variables](https://jfrog.com/blog/malicious-npm-packages-are-after-your-discord-tokens-17-new-packages-disclosed/) passed to the malicious code\n* [Stored Discord tokens](https://jfrog.com/blog/malicious-npm-packages-are-after-your-discord-tokens-17-new-packages-disclosed/)\n* AWS / GitHub credentials stored in cleartext files\n\nIt is highly recommended to change or revoke data that is stored in the infected machine at those locations\n\n##### Stopping malicious processes\n\nMany malicious packages start malicious processes such as [connectback shells](https://jfrog.com/blog/jfrog-discloses-3-remote-access-trojans-in-pypi/) or crypto-miners. Search for any unfamiliar processes that consume a large amount of CPU or a large amount of network traffic, and stop them. On Windows, this can be facilitated with [Sysinternals Process Explorer](https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer).\n\n##### Removing installed backdoors\n\nMany malicious packages install themselves as a [persistent backdoor](https://jfrog.com/blog/npm-supply-chain-attack-targets-german-based-companies/), in order to guarantee the malicious code survives a reboot. Search for any unfamiliar binaries set to be run on startup, and remove them. On Windows, this can be facilitated with [Sysinternals Autoruns](https://docs.microsoft.com/en-us/sysinternals/downloads/autoruns).\n\n##### Defining an Xray policy that blocks downloads of Artifacts with malicious packages\n\nIt is possible to [create an Xray policy](https://www.jfrog.com/confluence/display/JFROG/Creating+Xray+Policies+and+Rules) that will not allow artifacts with identified malicious packages to be downloaded from Artifactory. To create such a policy, add a new `Security` policy and set `Minimal Severity` to `Critical`. Under `Automatic Actions` check the `Block Download` action.\n\n##### Contacting the JFrog Security Research team for additional information\n\nOptionally, if you are unsure of the full impact of the malicious package and wish to get more details, the JFrog Security Research team can help you assess the potential damage from the installed malicious package.\n\nPlease contact us at research@jfrog.com with details of the affected artifact and the name of the identified malicious package." - } - }, - { - "severity": "Low", - "impactedPackageName": "debian:bookworm:openssh-client:1", - "impactedPackageVersion": "9.2p1-2+deb12u3", - "impactedPackageType": "Debian", - "components": [ - { - "name": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "version": "", - "location": { - "file": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } - } - ], - "summary": "OpenSSH through 9.6, when common types of DRAM are used, might allow row hammer attacks (for authentication bypass) because the integer value of authenticated in mm_answer_authpassword does not resist flips of a single bit. NOTE: this is applicable to a certain threat model of attacker-victim co-location in which the attacker has user privileges.", - "applicable": "Applicable", - "fixedVersions": null, - "cves": [ - { - "id": "CVE-2023-51767", - "cvssV2": "", - "cvssV3": "7.0", - "applicability": { - "status": "Applicable", - "scannerDescription": "The CVE is always applicable.\n\nNote - The vulnerability is hardware-dependent." - } - } - ], - "issueId": "XRAY-585612", - "references": [ - "https://arxiv.org/abs/2309.02545", - "https://github.com/openssh/openssh-portable/blob/8241b9c0529228b4b86d88b1a6076fb9f97e4a99/monitor.c#L878", - "https://github.com/openssh/openssh-portable/blob/8241b9c0529228b4b86d88b1a6076fb9f97e4a99/auth-passwd.c#L77", - "https://bugzilla.redhat.com/show_bug.cgi?id=2255850", - "https://security-tracker.debian.org/tracker/CVE-2023-51767", - "https://ubuntu.com/security/CVE-2023-51767", - "https://security.netapp.com/advisory/ntap-20240125-0006/", - "https://access.redhat.com/security/cve/CVE-2023-51767" - ], - "impactPaths": [ - [ - { - "name": "platform.jfrog.io/swamp-docker/swamp", - "version": "latest" - }, - { - "name": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "version": "", - "location": { - "file": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } - }, - { - "name": "debian:bookworm:openssh-client:1", - "version": "9.2p1-2+deb12u3", - "location": { - "file": "openssh-client:1:9.2p1-2+deb12u3" - } - } - ] - ], - "jfrogResearchInformation": { - "severity": "Low", - "summary": "The RowHammer fault injection attack can theoretically lead to local authentication bypass in OpenSSH.", - "details": "[OpenSSH](https://www.openssh.com/) is a popular open-source implementation of the SSH (Secure Shell) protocol, providing encrypted communication over a network.\nIt was discovered that the OpenSSH authentication logic can be susceptible in some cases to a side-channel fault injection attack. The attack can theoretically be carried out by a local attacker which eventually bypass OpenSSH authentication mechanism.\n\nThis vulnerability currently lacks widely known published exploits, and its exploitation is considered highly complex. The intricacies of the attack, combined with the absence of well-documented exploits, contribute to the difficulty in achieving successful exploitation. Furthermore, it's essential to note that the susceptibility to this vulnerability is hardware-dependent, and the success of an attack relies on probabilities associated with the specific hardware configuration. \n\nThe vulnerability is theoretically exploitable by several different ways, the only two published ways are:\n\nIn the OpenSSH function `mm_answer_authpassword()`, a stack variable `authenticated`, is assigned to the value of the function `auth_password()` which returns 1/0 and then returned. If the value of `authenticated` is 1, the SSH connection will be established. Since `authenticated` is stored on the stack, therefore in DRAM, a local attacker could flip this 32-bit integer least significant bit, thus, bypass authentication.\n\nAnother possible exploit is the `result` stack variable in `auth_password()` function. It is initialized to 0 and set to 1 if the password is correct. \nSimilarly to the previous method, this attack requires a single bit flip of the `result` variable in order for the function to return 1 and bypass the authentication.\n\nAttackers can trigger the vulnerability via a RowHammer fault injection. The Rowhammer bug is a hardware reliability issue in which an attacker repeatedly accesses (hammers) DRAM cells to cause unauthorized changes in physically adjacent memory locations.\nSimply put:\n\n* A specific register value(`authenticated`/`result` value) is pushed onto the stack during program execution. \n* The stack, where the register value is stored, is identified to be located in a memory row susceptible to bit flips (flippable row) due to the RowHammer vulnerability in DRAM.\n* The attacker performs a series of rapid and repeated memory accesses to the adjacent rows of the flippable row in the DRAM. This repeated access exploits the RowHammer vulnerability, inducing bit flips in the targeted flippable row.\n* Due to the RowHammer effect, bit flips occur in the flippable row, potentially corrupting the data stored there.\n* After inducing bit flips in the flippable row, the attacker manipulates the program's control flow to pop the corrupted value from the stack into a register.\n* The register now holds a value that has been corrupted through the RowHammer attack. Now the `authenticated`/`result` variables hold this corrupted value thus it can lead to authentication bypass, as it may impact the control flow in a way advantageous to the attacker.", - "severityReasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The vulnerability depends on the OS and hardware. It was only evaluated in one test environment, therefore results for other conditions might differ. The attacker must be extremely familiar with the details of the exploited system (ex. know the exact hardware which is running the OS).", - "isPositive": true - }, - { - "name": "The issue can only be exploited by an attacker that can execute code on the vulnerable machine (excluding exceedingly rare circumstances)", - "isPositive": true - }, - { - "name": "No high-impact exploit or technical writeup were published, and exploitation of the issue with high impact is either non-trivial or completely unproven", - "description": "Exploitation is extremely non-trivial (even theoretically), no public exploits have been published.", - "isPositive": true - }, - { - "name": "The reported CVSS was either wrongly calculated, downgraded by other vendors, or does not reflect the vulnerability's impact", - "description": "The vulnerability's attack complexity is significantly higher than what the CVSS represents.", - "isPositive": true - } - ] - } - }, - { - "severity": "Unknown", "impactedPackageName": "debian:bookworm:libssl3", "impactedPackageVersion": "3.0.13-1~deb12u1", "impactedPackageType": "Debian", @@ -210,30 +14,22 @@ } } ], - "summary": "CVE-2024-4741", - "applicable": "Applicable", + "summary": "Interpretation Conflict", + "applicable": "Undetermined", "fixedVersions": null, "cves": [ { - "id": "CVE-2024-4741", + "id": "CVE-2024-38428", "cvssV2": "", "cvssV3": "", "applicability": { - "status": "Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `SSL_free_buffers` is called.", - "evidence": [ - { - "file": "usr/local/bin/node", - "reason": "References to the vulnerable functions were found" - } - ] + "status": "Undetermined", + "scannerDescription": "The Scanner checks for CVE-2024-38428" } } ], - "issueId": "XRAY-603657", - "references": [ - "https://security-tracker.debian.org/tracker/CVE-2024-4741" - ], + "issueId": "XRAY-606103", + "references": null, "impactPaths": [ [ { @@ -256,10 +52,12 @@ } ] ], - "jfrogResearchInformation": null + "jfrogResearchInformation": { + "severity": "Critical" + } }, { - "severity": "Unknown", + "severity": "Critical", "impactedPackageName": "debian:bookworm:openssl", "impactedPackageVersion": "3.0.13-1~deb12u1", "impactedPackageType": "Debian", @@ -272,32 +70,24 @@ } } ], - "summary": "CVE-2024-4741", - "applicable": "Applicable", + "summary": "Interpretation Conflict", + "applicable": "Undetermined", "fixedVersions": [ - "[3.0.14-1~deb12u1]" + "[3.0.14-1~deb12u2]" ], "cves": [ { - "id": "CVE-2024-4741", + "id": "CVE-2024-38428", "cvssV2": "", "cvssV3": "", "applicability": { - "status": "Applicable", - "scannerDescription": "The scanner checks whether the vulnerable function `SSL_free_buffers` is called.", - "evidence": [ - { - "file": "usr/local/bin/node", - "reason": "References to the vulnerable functions were found" - } - ] + "status": "Undetermined", + "scannerDescription": "The Scanner checks for CVE-2024-38428" } } ], - "issueId": "XRAY-603657", - "references": [ - "https://security-tracker.debian.org/tracker/CVE-2024-4741" - ], + "issueId": "XRAY-606103", + "references": null, "impactPaths": [ [ { @@ -320,7 +110,9 @@ } ] ], - "jfrogResearchInformation": null + "jfrogResearchInformation": { + "severity": "Critical" + } }, { "severity": "Unknown", @@ -336,7 +128,7 @@ } } ], - "summary": "Issue summary: Applications performing certificate name checks (e.g., TLS\nclients checking server certificates) may attempt to read an invalid memory\naddress resulting in abnormal termination of the application process.\n\nImpact summary: Abnormal termination of an application can a cause a denial of\nservice.\n\nApplications performing certificate name checks (e.g., TLS clients checking\nserver certificates) may attempt to read an invalid memory address when\ncomparing the expected name with an `otherName` subject alternative name of an\nX.509 certificate. This may result in an exception that terminates the\napplication program.\n\nNote that basic certificate chain validation (signatures, dates, ...) is not\naffected, the denial of service can occur only when the application also\nspecifies an expected DNS name, Email address or IP address.\n\nTLS servers rarely solicit client certificates, and even when they do, they\ngenerally don't perform a name check against a reference identifier (expected\nidentity), but rather extract the presented identity after checking the\ncertificate chain. So TLS servers are generally not affected and the severity\nof the issue is Moderate.\n\nThe FIPS modules in 3.3, 3.2, 3.1 and 3.0 are not affected by this issue.", + "summary": "Issue summary", "applicable": "Applicable", "fixedVersions": null, "cves": [ @@ -346,25 +138,18 @@ "cvssV3": "", "applicability": { "status": "Applicable", - "scannerDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `X509_VERIFY_PARAM_set1_email`\n\n- `X509_check_email`\n\n- `X509_VERIFY_PARAM_set1_host`\n\n- `X509_check_host`", + "scannerDescription": "The Scanner checks for CVE-2024-6119", "evidence": [ { "file": "usr/local/bin/node", - "reason": "References to the vulnerable functions were found" + "reason": "ca msg" } ] } } ], "issueId": "XRAY-632747", - "references": [ - "https://openssl-library.org/news/secadv/20240903.txt", - "https://github.com/openssl/openssl/commit/621f3729831b05ee828a3203eddb621d014ff2b2", - "https://github.com/openssl/openssl/commit/05f360d9e849a1b277db628f1f13083a7f8dd04f", - "https://security-tracker.debian.org/tracker/CVE-2024-6119", - "https://github.com/openssl/openssl/commit/7dfcee2cd2a63b2c64b9b4b0850be64cb695b0a0", - "https://github.com/openssl/openssl/commit/06d1dc3fa96a2ba5a3e22735a033012aadc9f0d6" - ], + "references": null, "impactPaths": [ [ { @@ -388,29 +173,14 @@ ] ], "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Out of bounds read in OpenSSL clients can lead to denial of service when using non-default TLS verification options and connecting to malicious TLS servers", - "severityReasons": [ - { - "name": "The issue has an exploit published", - "description": "The fix commit contains PoC certificates that trigger the denial of service issue" - }, - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The attacker must make the victim client connect to their malicious TLS server, in order to serve the malformed TLS certificate. The victim client must use OpenSSL and must enable non-default certificate verification options, either -\n\n* DNS verification - by using `X509_VERIFY_PARAM_set1_host` or `X509_check_host`\n* Email verification - by using ` X509_VERIFY_PARAM_set1_email` or `X509_check_email`", - "isPositive": true - }, - { - "name": "The issue cannot result in a severe impact (such as remote code execution)", - "description": "Denial of service of a TLS clients only. This out of bounds read cannot lead to data disclosure.", - "isPositive": true - } - ] + "severity": "Medium" } - }, + } + ], + "securityViolations": [ { "severity": "Unknown", - "impactedPackageName": "debian:bookworm:openssl", + "impactedPackageName": "debian:bookworm:libssl3", "impactedPackageVersion": "3.0.13-1~deb12u1", "impactedPackageType": "Debian", "components": [ @@ -422,11 +192,13 @@ } } ], - "summary": "Issue summary: Applications performing certificate name checks (e.g., TLS\nclients checking server certificates) may attempt to read an invalid memory\naddress resulting in abnormal termination of the application process.\n\nImpact summary: Abnormal termination of an application can a cause a denial of\nservice.\n\nApplications performing certificate name checks (e.g., TLS clients checking\nserver certificates) may attempt to read an invalid memory address when\ncomparing the expected name with an `otherName` subject alternative name of an\nX.509 certificate. This may result in an exception that terminates the\napplication program.\n\nNote that basic certificate chain validation (signatures, dates, ...) is not\naffected, the denial of service can occur only when the application also\nspecifies an expected DNS name, Email address or IP address.\n\nTLS servers rarely solicit client certificates, and even when they do, they\ngenerally don't perform a name check against a reference identifier (expected\nidentity), but rather extract the presented identity after checking the\ncertificate chain. So TLS servers are generally not affected and the severity\nof the issue is Moderate.\n\nThe FIPS modules in 3.3, 3.2, 3.1 and 3.0 are not affected by this issue.", - "applicable": "Applicable", - "fixedVersions": [ - "[3.0.14-1~deb12u2]" + "watch": "security-watch", + "policies": [ + "debian-security" ], + "summary": "Issue summary", + "applicable": "Applicable", + "fixedVersions": null, "cves": [ { "id": "CVE-2024-6119", @@ -434,25 +206,18 @@ "cvssV3": "", "applicability": { "status": "Applicable", - "scannerDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `X509_VERIFY_PARAM_set1_email`\n\n- `X509_check_email`\n\n- `X509_VERIFY_PARAM_set1_host`\n\n- `X509_check_host`", + "scannerDescription": "The Scanner checks for CVE-2024-6119", "evidence": [ { "file": "usr/local/bin/node", - "reason": "References to the vulnerable functions were found" + "reason": "ca msg" } ] } } ], "issueId": "XRAY-632747", - "references": [ - "https://openssl-library.org/news/secadv/20240903.txt", - "https://github.com/openssl/openssl/commit/621f3729831b05ee828a3203eddb621d014ff2b2", - "https://github.com/openssl/openssl/commit/05f360d9e849a1b277db628f1f13083a7f8dd04f", - "https://security-tracker.debian.org/tracker/CVE-2024-6119", - "https://github.com/openssl/openssl/commit/7dfcee2cd2a63b2c64b9b4b0850be64cb695b0a0", - "https://github.com/openssl/openssl/commit/06d1dc3fa96a2ba5a3e22735a033012aadc9f0d6" - ], + "references": null, "impactPaths": [ [ { @@ -467,360 +232,71 @@ } }, { - "name": "debian:bookworm:openssl", + "name": "debian:bookworm:libssl3", "version": "3.0.13-1~deb12u1", "location": { - "file": "openssl:3.0.13-1~deb12u1" - } - } - ] - ], - "jfrogResearchInformation": { - "severity": "Medium", - "summary": "Out of bounds read in OpenSSL clients can lead to denial of service when using non-default TLS verification options and connecting to malicious TLS servers", - "severityReasons": [ - { - "name": "The issue has an exploit published", - "description": "The fix commit contains PoC certificates that trigger the denial of service issue" - }, - { - "name": "The prerequisites for exploiting the issue are extremely unlikely", - "description": "The attacker must make the victim client connect to their malicious TLS server, in order to serve the malformed TLS certificate. The victim client must use OpenSSL and must enable non-default certificate verification options, either -\n\n* DNS verification - by using `X509_VERIFY_PARAM_set1_host` or `X509_check_host`\n* Email verification - by using ` X509_VERIFY_PARAM_set1_email` or `X509_check_email`", - "isPositive": true - }, - { - "name": "The issue cannot result in a severe impact (such as remote code execution)", - "description": "Denial of service of a TLS clients only. This out of bounds read cannot lead to data disclosure.", - "isPositive": true - } - ] - } - }, - { - "severity": "Critical", - "impactedPackageName": "debian:bookworm:libexpat1", - "impactedPackageVersion": "2.5.0-1", - "impactedPackageType": "Debian", - "components": [ - { - "name": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "version": "", - "location": { - "file": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } - } - ], - "summary": "An issue was discovered in libexpat before 2.6.3. xmlparse.c does not reject a negative length for XML_ParseBuffer.", - "applicable": "Not Applicable", - "fixedVersions": null, - "cves": [ - { - "id": "CVE-2024-45490", - "cvssV2": "", - "cvssV3": "9.8", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether any of the following vulnerable functions are called:\n\n- `XML_Parse()`\n- `XML_ParseBuffer()`\n\nAn additional condition, which the scanner currently does not check, is that the `len` parameter which is passed to those functions is user-controlled." - } - } - ], - "issueId": "XRAY-632613", - "references": [ - "https://github.com/libexpat/libexpat/issues/887", - "https://security-tracker.debian.org/tracker/CVE-2024-45490", - "https://github.com/libexpat/libexpat/pull/890" - ], - "impactPaths": [ - [ - { - "name": "platform.jfrog.io/swamp-docker/swamp", - "version": "latest" - }, - { - "name": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "version": "", - "location": { - "file": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } - }, - { - "name": "debian:bookworm:libexpat1", - "version": "2.5.0-1", - "location": { - "file": "libexpat1:2.5.0-1" - } - } - ] - ], - "jfrogResearchInformation": null - }, - { - "severity": "Critical", - "impactedPackageName": "debian:bookworm:libexpat1", - "impactedPackageVersion": "2.5.0-1", - "impactedPackageType": "Debian", - "components": [ - { - "name": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "version": "", - "location": { - "file": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } - } - ], - "summary": "An issue was discovered in libexpat before 2.6.3. nextScaffoldPart in xmlparse.c can have an integer overflow for m_groupSize on 32-bit platforms (where UINT_MAX equals SIZE_MAX).", - "applicable": "Not Applicable", - "fixedVersions": null, - "cves": [ - { - "id": "CVE-2024-45492", - "cvssV2": "", - "cvssV3": "9.8", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks whether the current binary was compiled with 32-bit architecture and if any of the vulnerable functions are called:\n\n- `XML_ParseBuffer()`\n- `XML_Parse()`\n\nNote - the vulnerability occurs when certain inputs are passed to those functions." - } - } - ], - "issueId": "XRAY-632612", - "references": [ - "https://github.com/libexpat/libexpat/issues/889", - "https://security-tracker.debian.org/tracker/CVE-2024-45492", - "https://github.com/libexpat/libexpat/pull/892" - ], - "impactPaths": [ - [ - { - "name": "platform.jfrog.io/swamp-docker/swamp", - "version": "latest" - }, - { - "name": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar", - "version": "", - "location": { - "file": "sha256__20f026ae0a91ba4668a54b46f39853dd4c114a84cfedb4144ff24521d3e6dcb1.tar" - } - }, - { - "name": "debian:bookworm:libexpat1", - "version": "2.5.0-1", - "location": { - "file": "libexpat1:2.5.0-1" - } - } - ] - ], - "jfrogResearchInformation": null - }, - { - "severity": "Low", - "impactedPackageName": "debian:bookworm:apt", - "impactedPackageVersion": "2.6.1", - "impactedPackageType": "Debian", - "components": [ - { - "name": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar", - "version": "", - "location": { - "file": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - } - } - ], - "summary": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.", - "applicable": "Not Applicable", - "fixedVersions": null, - "cves": [ - { - "id": "CVE-2011-3374", - "cvssV2": "4.3", - "cvssV3": "3.7", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks if the vulnerable variable `ARCHIVE_KEYRING_URI` in `/usr/bin/apt-key` is not empty and not commented out. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n\nThe below prerequisites are also crucial for exploitability but are not checked in the scanner:\n\n1. The command apt-key net-update should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the `python-apt` Python module should be called. This is for the malicious keys download.\n\n2. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine." - } - } - ], - "issueId": "XRAY-34417", - "references": [ - "https://people.canonical.com/~ubuntu-security/cve/2011/CVE-2011-3374.html", - "https://seclists.org/fulldisclosure/2011/Sep/221", - "https://ubuntu.com/security/CVE-2011-3374", - "https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=642480", - "https://access.redhat.com/security/cve/cve-2011-3374", - "https://snyk.io/vuln/SNYK-LINUX-APT-116518", - "https://security-tracker.debian.org/tracker/CVE-2011-3374" - ], - "impactPaths": [ - [ - { - "name": "platform.jfrog.io/swamp-docker/swamp", - "version": "latest" - }, - { - "name": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar", - "version": "", - "location": { - "file": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - } - }, - { - "name": "debian:bookworm:apt", - "version": "2.6.1", - "location": { - "file": "apt:2.6.1" - } - } - ] - ], - "jfrogResearchInformation": { - "severity": "High", - "summary": "Improper signature validation in apt-key may enable Man-in-the-Middle attacks and result in code execution.", - "details": "`apt-key` is [`apt`](https://github.com/Debian/apt)'s key management utility, and is used to manage the keys that are used by `apt` to authenticate packages.\n\nA vulnerability in `apt-key`'s `net-update` function exists, in which [`GPG`](https://www.gnupg.org/) keys, that are used for signing packages and validating their authenticity, aren't validated correctly. The `net-update` function pulls the signing keys that should be added from an insecure location (`http://...`), exposing it to a Man-in-the-Middle attack in which malicious signing keys could be added to the system's keyring. This issue happens due to a vulnerability in the `add_keys_with_veirfy_against_master_keyring()` function, which allows adding signing keys without proper signature validation. \n\nThis vulnerability then potentially allows a malicious actor to perform a Man-in-the-Middle attack on a target, by making it validate malicious packages that were signed with the `GPG` signing key used by the attacker. Effectively, this means that `apt` can be duped to install malicious services and daemons with root privileges.\n\nThe conditions for this vulnerability to be applicable:\n \n1. A valid URI should be configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`. This is the URI that an attacker would need to target in a Man In The Middle attack.\n2. The command `apt-key net-update` should be executed on the affected system, or alternatively `apt.auth.net_update()` function from [python-apt](https://pypi.org/project/python-apt/) Python module should be called. This is for the malicious keys download.\n3. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine.\n\nDo note that `apt-key` is **deprecated** and shouldn't be used, and in most Debian versions `ARCHIVE_KEYRING_URI` is not defined, making this vulnerability unexploitable in most Debian systems.", - "severityReasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The conditions for this vulnerability to be applicable:\n \n1. A valid URI should be configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n2. The command `apt-key net-update` should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the python-apt Python module should be called. This is for the malicious keys download.\n3. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine.", - "isPositive": true - }, - { - "name": "The issue can be exploited by attackers over the network", - "description": "This vulnerability is remotely exploitable when the applicability conditions apply." - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Remote code execution is possible when the applicability conditions apply." - }, - { - "name": "The issue has an exploit published", - "description": "The reporter of this issue has provided a GPG key that can be used for an actual attack, as well as a simple PoC example." - } - ], - "remediation": "##### Deployment mitigations\n\n* Dot not execute `apt-key` command, as it is deprecated.\n* Remove the URI configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`." - } - }, - { - "severity": "Low", - "impactedPackageName": "debian:bookworm:libapt-pkg6.0", - "impactedPackageVersion": "2.6.1", - "impactedPackageType": "Debian", - "components": [ - { - "name": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar", - "version": "", - "location": { - "file": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - } - } - ], - "summary": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.", - "applicable": "Not Applicable", - "fixedVersions": null, - "cves": [ - { - "id": "CVE-2011-3374", - "cvssV2": "4.3", - "cvssV3": "3.7", - "applicability": { - "status": "Not Applicable", - "scannerDescription": "The scanner checks if the vulnerable variable `ARCHIVE_KEYRING_URI` in `/usr/bin/apt-key` is not empty and not commented out. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n\nThe below prerequisites are also crucial for exploitability but are not checked in the scanner:\n\n1. The command apt-key net-update should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the `python-apt` Python module should be called. This is for the malicious keys download.\n\n2. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine." - } - } - ], - "issueId": "XRAY-34417", - "references": [ - "https://people.canonical.com/~ubuntu-security/cve/2011/CVE-2011-3374.html", - "https://seclists.org/fulldisclosure/2011/Sep/221", - "https://ubuntu.com/security/CVE-2011-3374", - "https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=642480", - "https://access.redhat.com/security/cve/cve-2011-3374", - "https://snyk.io/vuln/SNYK-LINUX-APT-116518", - "https://security-tracker.debian.org/tracker/CVE-2011-3374" - ], - "impactPaths": [ - [ - { - "name": "platform.jfrog.io/swamp-docker/swamp", - "version": "latest" - }, - { - "name": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar", - "version": "", - "location": { - "file": "sha256__cedb364ef937c7e51179d8e514bdd98644bac5fdc82a45d784ef91afe4bc647e.tar" - } - }, - { - "name": "debian:bookworm:libapt-pkg6.0", - "version": "2.6.1", - "location": { - "file": "libapt-pkg6.0:2.6.1" + "file": "libssl3:3.0.13-1~deb12u1" } } ] ], "jfrogResearchInformation": { - "severity": "High", - "summary": "Improper signature validation in apt-key may enable Man-in-the-Middle attacks and result in code execution.", - "details": "`apt-key` is [`apt`](https://github.com/Debian/apt)'s key management utility, and is used to manage the keys that are used by `apt` to authenticate packages.\n\nA vulnerability in `apt-key`'s `net-update` function exists, in which [`GPG`](https://www.gnupg.org/) keys, that are used for signing packages and validating their authenticity, aren't validated correctly. The `net-update` function pulls the signing keys that should be added from an insecure location (`http://...`), exposing it to a Man-in-the-Middle attack in which malicious signing keys could be added to the system's keyring. This issue happens due to a vulnerability in the `add_keys_with_veirfy_against_master_keyring()` function, which allows adding signing keys without proper signature validation. \n\nThis vulnerability then potentially allows a malicious actor to perform a Man-in-the-Middle attack on a target, by making it validate malicious packages that were signed with the `GPG` signing key used by the attacker. Effectively, this means that `apt` can be duped to install malicious services and daemons with root privileges.\n\nThe conditions for this vulnerability to be applicable:\n \n1. A valid URI should be configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`. This is the URI that an attacker would need to target in a Man In The Middle attack.\n2. The command `apt-key net-update` should be executed on the affected system, or alternatively `apt.auth.net_update()` function from [python-apt](https://pypi.org/project/python-apt/) Python module should be called. This is for the malicious keys download.\n3. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine.\n\nDo note that `apt-key` is **deprecated** and shouldn't be used, and in most Debian versions `ARCHIVE_KEYRING_URI` is not defined, making this vulnerability unexploitable in most Debian systems.", - "severityReasons": [ - { - "name": "Exploitation of the issue is only possible when the vulnerable component is used in a specific manner. The attacker has to perform per-target research to determine the vulnerable attack vector", - "description": "The conditions for this vulnerability to be applicable:\n \n1. A valid URI should be configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`. This is the URI that an attacker would need to target in a Man-in-the-Middle attack.\n2. The command `apt-key net-update` should be executed on the affected system, or alternatively `apt.auth.net_update()` function from the python-apt Python module should be called. This is for the malicious keys download.\n3. After the execution of `apt-key net-update`, APT packages should be installed or updated on the machine.", - "isPositive": true - }, - { - "name": "The issue can be exploited by attackers over the network", - "description": "This vulnerability is remotely exploitable when the applicability conditions apply." - }, - { - "name": "The issue results in a severe impact (such as remote code execution)", - "description": "Remote code execution is possible when the applicability conditions apply." - }, - { - "name": "The issue has an exploit published", - "description": "The reporter of this issue has provided a GPG key that can be used for an actual attack, as well as a simple PoC example." - } - ], - "remediation": "##### Deployment mitigations\n\n* Dot not execute `apt-key` command, as it is deprecated.\n* Remove the URI configured in `ARCHIVE_KEYRING_URI` variable in the file `/usr/bin/apt-key`." + "severity": "Medium" } } ], - "securityViolations": null, "licensesViolations": null, "licenses": null, "operationalRiskViolations": null, "secrets": [ { "severity": "Medium", - "file": "usr/src/app/server/scripts/__pycache__/fetch_github_repo.cpython-311.pyc", - "snippet": "htt************", - "finding": "Hardcoded secrets were found", - "scannerDescription": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, - { - "severity": "Medium", - "file": "private/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/tmpsfyn_3d1/unpacked/filesystem/blobs/sha256/9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0/usr/src/app/server/index.js", + "ruleId": "REQ.SECRET.GENERIC.CODE", + "scannerShortDescription": "Scanner for REQ.SECRET.GENERIC.CODE", + "scannerDescription": "The Scanner checks for REQ.SECRET.GENERIC.CODE", + "file": "temp/folders/T/tmpsfyn_3d1/unpacked/sha256/9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0/usr/src/app/server/index.js", "startLine": 5, "startColumn": 7, "endLine": 5, "endColumn": 57, "snippet": "tok************", - "finding": "Hardcoded secrets were found", - "scannerDescription": "Storing hardcoded secrets in your source code or binary artifact could lead to several risks.\n\nIf the secret is associated with a wide scope of privileges, attackers could extract it from the source code or binary artifact and use it maliciously to attack many targets. For example, if the hardcoded password gives high-privilege access to an AWS account, the attackers may be able to query/modify company-wide sensitive data without per-user authentication.\n\n## Best practices\n\nUse safe storage when storing high-privilege secrets such as passwords and tokens, for example -\n\n* ### Environment Variables\n\nEnvironment variables are set outside of the application code, and can be dynamically passed to the application only when needed, for example -\n`SECRET_VAR=MySecret ./my_application`\nThis way, `MySecret` does not have to be hardcoded into `my_application`.\n\nNote that if your entire binary artifact is published (ex. a Docker container published to Docker Hub), the value for the environment variable must not be stored in the artifact itself (ex. inside the `Dockerfile` or one of the container's files) but rather must be passed dynamically, for example in the `docker run` call as an argument.\n\n* ### Secret management services\n\nExternal vendors offer cloud-based secret management services, that provide proper access control to each secret. The given access to each secret can be dynamically modified or even revoked. Some examples include -\n\n* [Hashicorp Vault](https://www.vaultproject.io)\n* [AWS KMS](https://aws.amazon.com/kms) (Key Management Service)\n* [Google Cloud KMS](https://cloud.google.com/security-key-management)\n\n## Least-privilege principle\n\nStoring a secret in a hardcoded manner can be made safer, by making sure the secret grants the least amount of privilege as needed by the application.\nFor example - if the application needs to read a specific table from a specific database, and the secret grants access to perform this operation **only** (meaning - no access to other tables, no write access at all) then the damage from any secret leaks is mitigated.\nThat being said, it is still not recommended to store secrets in a hardcoded manner, since this type of storage does not offer any way to revoke or moderate the usage of the secret.\n" - }, + "finding": "Secret REQ.SECRET.GENERIC.CODE were found", + "applicability": { + "status": "Inactive", + "scannerDescription": "expired" + } + } + ], + "iac": null, + "sast": null, + "secretsViolations": [ { "severity": "Medium", - "file": "private/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/tmpsfyn_3d1/unpacked/filesystem/blobs/sha256/9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0/usr/src/app/server/index.js", - "startLine": 6, - "startColumn": 14, - "endLine": 6, - "endColumn": 24, - "snippet": "eyJ************", - "finding": "Secret keys were found", - "scannerDescription": "\nStoring an API key in the image could lead to several risks.\n\nIf the key is associated with a wide scope of privileges, attackers could extract it from a single image or firmware and use it maliciously to attack many targets. For example, if the embedded key allows querying/modifying data for all cloud user accounts, without per-user authentication, the attackers who extract it would gain access to system-wide data.\n\nIf the cloud/SaaS provider bills by key usage - for example, every million queries cost the key's owner a fixed sum of money - attackers could use the keys for their own purposes (or just as a form of vandalism), incurring a large cost to the legitimate user or operator.\n\n## Best practices\n\nUse narrow scopes for stored API keys. As much as possible, API keys should be unique per host and require additional authentication with the user's individual credentials for any sensitive actions.\n\nAvoid placing keys whose use incurs costs directly in the image. Store the key with any software or hardware protection available on the host for key storage (such as operating system key-stores, hardware cryptographic storage mechanisms or cloud-managed secure storage services such as [AWS KMS](https://aws.amazon.com/kms/)).\n\nTokens that were detected as exposed should be revoked and replaced -\n\n* [AWS Key Revocation](https://aws.amazon.com/premiumsupport/knowledge-center/delete-access-key/#:~:text=If%20you%20see%20a%20warning,the%20confirmation%20box%2C%20choose%20Deactivate.)\n* [GCP Key Revocation](https://www.trendmicro.com/cloudoneconformity/knowledge-base/gcp/CloudIAM/delete-api-keys.html)\n* [Azure Key Revocation](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#revoke-a-pat)\n* [GitHub Key Revocation](https://docs.github.com/en/rest/apps/oauth-applications#delete-an-app-authorization)\n" + "watch": "watch", + "issueId": "sec-violation-1", + "ruleId": "REQ.SECRET.GENERIC.CODE", + "scannerShortDescription": "Scanner for REQ.SECRET.GENERIC.CODE", + "scannerDescription": "The Scanner checks for REQ.SECRET.GENERIC.CODE", + "file": "temp/folders/T/tmpsfyn_3d1/unpacked/sha256/9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0/usr/src/app/server/index.js", + "startLine": 5, + "startColumn": 7, + "endLine": 5, + "endColumn": 57, + "snippet": "tok************", + "finding": "Secret REQ.SECRET.GENERIC.CODE were found", + "applicability": { + "status": "Inactive", + "scannerDescription": "expired" + } } ], "iacViolations": null, "sastViolations": null, - "errors": null -} + "errors": null, + "scansStatus": { + "scaScanStatusCode": 0, + "secretsScanStatusCode": 0, + "ContextualAnalysisScanStatusCode": 0 + }, + "multiScanId": "7d5e4733-3f93-11ef-8147-e610d09d7daa" +} \ No newline at end of file diff --git a/tests/testdata/output/dockerscan/docker_summary.json b/tests/testdata/output/dockerscan/docker_summary.json index f325e059..2d56aca9 100644 --- a/tests/testdata/output/dockerscan/docker_summary.json +++ b/tests/testdata/output/dockerscan/docker_summary.json @@ -1,43 +1,44 @@ { - "scans": [ - { - "target": "/var/folders/xv/th4cksxn7jv9wjrdnn1h4tj00000gq/T/jfrog.cli.temp.-1726210535-1985298017/image.tar", - "name": "platform.jfrog.io/swamp-docker/swamp:latest", - "vulnerabilities": { - "sca": { - "scan_ids": [ - "27da9106-88ea-416b-799b-bc7d15783473" - ], - "security": { - "Critical": { - "Not Applicable": 2, - "Not Covered": 1, - "Undetermined": 1 - }, - "Low": { - "Applicable": 1, - "Not Applicable": 1 - }, - "Unknown": { - "Applicable": 2 - } + "scans": [ + { + "target": "temp/folders/T/jfrog.cli.temp.-11-11/image.tar", + "name": "platform.jfrog.io/swamp-docker/swamp:latest", + "vulnerabilities": { + "sca": { + "scan_ids": [ + "27da9106-88ea-416b-799b-bc7d15783473" + ], + "security": { + "Critical": { + "Undetermined": 1 + }, + "Unknown": { + "Applicable": 1 } - }, - "iac": {}, - "secrets": { - "Medium": { - "": 3 - } - }, - "sast": {} + } + }, + "secrets": { + "Medium": { + "Inactive": 1 + } + } + }, + "violations": { + "watches": [ + "security-watch", + "watch" + ], + "sca": { + "scan_ids": [ + "27da9106-88ea-416b-799b-bc7d15783473" + ] }, - "violations": { - "sca": { - "scan_ids": [ - "27da9106-88ea-416b-799b-bc7d15783473" - ] + "secrets": { + "Medium": { + "Inactive": 1 } } } - ] - } \ No newline at end of file + } + ] +} \ No newline at end of file diff --git a/tests/utils/integration/test_integrationutils.go b/tests/utils/integration/test_integrationutils.go index c95a3005..3b7f5711 100644 --- a/tests/utils/integration/test_integrationutils.go +++ b/tests/utils/integration/test_integrationutils.go @@ -68,7 +68,7 @@ func InitXscTest(t *testing.T, validations ...func()) (string, string, func()) { xrayVersion, err := testUtils.GetTestsXrayVersion() assert.NoError(t, err) // validate XSC is enabled at the given server - xscService, err := xsc.CreateXscService(xrayVersion.GetVersion(), configTests.XscDetails) + xscService, err := xsc.CreateXscServiceBackwardCompatible(xrayVersion.GetVersion(), configTests.XscDetails) assert.NoError(t, err) xscVersion, err := xscService.GetVersion() if err != nil { diff --git a/tests/utils/test_utils.go b/tests/utils/test_utils.go index e747bed7..d7e5771e 100644 --- a/tests/utils/test_utils.go +++ b/tests/utils/test_utils.go @@ -14,7 +14,6 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" - "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/owenrumney/go-sarif/v2/sarif" biutils "github.com/jfrog/build-info-go/utils" @@ -112,31 +111,6 @@ func ChangeWD(t *testing.T, newPath string) string { return prevDir } -func ReadCmdScanResults(t *testing.T, path string) *results.SecurityCommandResults { - content, err := os.ReadFile(path) - require.NoError(t, err) - var cmdResults *results.SecurityCommandResults - if !assert.NoError(t, json.Unmarshal(content, &cmdResults)) { - return &results.SecurityCommandResults{} - } - // replace paths separators - for _, targetResults := range cmdResults.Targets { - targetResults.Target = filepath.FromSlash(targetResults.Target) - if targetResults.ScaResults != nil { - for i, descriptor := range targetResults.ScaResults.Descriptors { - targetResults.ScaResults.Descriptors[i] = filepath.FromSlash(descriptor) - } - } - if targetResults.JasResults != nil { - convertSarifRunPathsForOS(targetResults.JasResults.ApplicabilityScanResults...) - convertSarifRunPathsForOS(targetResults.JasResults.SecretsScanResults...) - convertSarifRunPathsForOS(targetResults.JasResults.IacScanResults...) - convertSarifRunPathsForOS(targetResults.JasResults.SastScanResults...) - } - } - return cmdResults -} - func convertSarifRunPathsForOS(runs ...*sarif.Run) { for r := range runs { for i := range runs[r].Invocations { @@ -167,9 +141,7 @@ func ReadSimpleJsonResults(t *testing.T, path string) formats.SimpleJsonResults content, err := os.ReadFile(path) require.NoError(t, err) var results formats.SimpleJsonResults - if !assert.NoError(t, json.Unmarshal(content, &results)) { - return formats.SimpleJsonResults{} - } + require.NoError(t, json.Unmarshal(content, &results)) // replace paths separators for _, vulnerability := range results.Vulnerabilities { convertScaSimpleJsonPathsForOS(&vulnerability.Components, &vulnerability.ImpactPaths, &vulnerability.ImpactedDependencyDetails, &vulnerability.Cves) @@ -183,13 +155,22 @@ func ReadSimpleJsonResults(t *testing.T, path string) formats.SimpleJsonResults for _, orViolation := range results.OperationalRiskViolations { convertScaSimpleJsonPathsForOS(&orViolation.Components, nil, &orViolation.ImpactedDependencyDetails, nil) } - for _, secret := range results.Secrets { + for _, secret := range results.SecretsVulnerabilities { + convertJasSimpleJsonPathsForOS(&secret) + } + for _, sast := range results.SastVulnerabilities { + convertJasSimpleJsonPathsForOS(&sast) + } + for _, iac := range results.IacsVulnerabilities { + convertJasSimpleJsonPathsForOS(&iac) + } + for _, secret := range results.SecretsViolations { convertJasSimpleJsonPathsForOS(&secret) } - for _, sast := range results.Sast { + for _, sast := range results.SastViolations { convertJasSimpleJsonPathsForOS(&sast) } - for _, iac := range results.Iacs { + for _, iac := range results.IacsViolations { convertJasSimpleJsonPathsForOS(&iac) } return results @@ -255,9 +236,7 @@ func ReadSarifResults(t *testing.T, path string) *sarif.Report { content, err := os.ReadFile(path) require.NoError(t, err) var results *sarif.Report - if !assert.NoError(t, json.Unmarshal(content, &results)) { - return &sarif.Report{} - } + require.NoError(t, json.Unmarshal(content, &results)) // replace paths separators convertSarifRunPathsForOS(results.Runs...) return results @@ -267,9 +246,7 @@ func ReadSummaryResults(t *testing.T, path string) formats.ResultsSummary { content, err := os.ReadFile(path) require.NoError(t, err) var results formats.ResultsSummary - if !assert.NoError(t, json.Unmarshal(content, &results)) { - return formats.ResultsSummary{} - } + require.NoError(t, json.Unmarshal(content, &results)) // replace paths separators for _, targetResults := range results.Scans { targetResults.Target = filepath.FromSlash(targetResults.Target) @@ -288,7 +265,115 @@ func ChangeWDWithCallback(t *testing.T, newPath string) func() { } } -func CreateTestWatch(t *testing.T, policyName string, watchName, severity xrayUtils.Severity) (string, func()) { +func CreateTestIgnoreRules(t *testing.T, description string, filters xrayUtils.IgnoreFilters) func() { + xrayManager, err := xray.CreateXrayServiceManager(configTests.XrDetails) + require.NoError(t, err) + ignoreRuleId, err := xrayManager.CreateIgnoreRule(xrayUtils.IgnoreRuleParams{ + // expired in one day + Notes: description, + ExpiresAt: time.Now().AddDate(0, 0, 1), + IgnoreFilters: filters, + }) + assert.NoError(t, err) + return func() { + assert.NoError(t, xrayManager.DeleteIgnoreRule(ignoreRuleId)) + } +} + +func CreateSecurityPolicy(t *testing.T, policyName string, rules ...xrayUtils.PolicyRule) (string, func()) { + xrayManager, err := xray.CreateXrayServiceManager(configTests.XrDetails) + require.NoError(t, err) + // Create new default security policy. + policyParams := xrayUtils.PolicyParams{ + Name: fmt.Sprintf("%s-%s", policyName, strconv.FormatInt(time.Now().Unix(), 10)), + Type: xrayUtils.Security, + Rules: rules, + } + if !assert.NoError(t, xrayManager.CreatePolicy(policyParams)) { + return "", func() {} + } + return policyParams.Name, func() { + assert.NoError(t, xrayManager.DeletePolicy(policyParams.Name)) + } +} + +func CreateTestSecurityPolicy(t *testing.T, policyName string, severity xrayUtils.Severity, failBuild bool) (string, func()) { + return CreateSecurityPolicy(t, policyName, + xrayUtils.PolicyRule{ + Name: "sca_rule", + Criteria: *xrayUtils.CreateSeverityPolicyCriteria(severity), + Actions: getBuildFailAction(failBuild), + Priority: 1, + }, + xrayUtils.PolicyRule{ + Name: "exposers_rule", + Criteria: *xrayUtils.CreateExposuresPolicyCriteria(severity, true, true, true, true), + Actions: getBuildFailAction(failBuild), + Priority: 2, + }, + xrayUtils.PolicyRule{ + Name: "sast_rule", + Criteria: *xrayUtils.CreateSastPolicyCriteria(severity), + Actions: getBuildFailAction(failBuild), + Priority: 3, + }, + ) +} + +func getBuildFailAction(failBuild bool) *xrayUtils.PolicyAction { + if failBuild { + return &xrayUtils.PolicyAction{ + FailBuild: clientUtils.Pointer(true), + } + } + return nil +} + +func createTestWatch(t *testing.T, policyName, watchName string, assignParams func(watchParams xrayUtils.WatchParams) xrayUtils.WatchParams) (string, func()) { + xrayManager, err := xray.CreateXrayServiceManager(configTests.XrDetails) + require.NoError(t, err) + // Create new default watch. + watchParams := assignParams(xrayUtils.NewWatchParams()) + watchParams.Name = fmt.Sprintf("%s-%s", watchName, strconv.FormatInt(time.Now().Unix(), 10)) + watchParams.Active = true + // Assign the policy to the watch. + watchParams.Policies = []xrayUtils.AssignedPolicy{ + { + Name: policyName, + Type: string(xrayUtils.Security), + }, + } + assert.NoError(t, xrayManager.CreateWatch(watchParams)) + return watchParams.Name, func() { + assert.NoError(t, xrayManager.DeleteWatch(watchParams.Name)) + } +} + +// If gitResources is empty, the watch will be created with all builds. +func CreateWatchForTests(t *testing.T, policyName, watchName string, gitResources ...string) (string, func()) { + return createTestWatch(t, policyName, watchName, func(watchParams xrayUtils.WatchParams) xrayUtils.WatchParams { + if len(gitResources) > 0 { + watchParams.GitRepositories.Resources = gitResources + } else { + watchParams.Builds.Type = xrayUtils.WatchBuildAll + } + return watchParams + }) +} + +func CreateTestProjectKeyWatch(t *testing.T, policyName, watchName, projectKey string, gitResources ...string) (string, func()) { + return createTestWatch(t, policyName, watchName, func(watchParams xrayUtils.WatchParams) xrayUtils.WatchParams { + watchParams.ProjectKey = projectKey + if len(gitResources) > 0 { + watchParams.GitRepositories.Resources = gitResources + } else { + watchParams.Builds.Type = xrayUtils.WatchBuildAll + } + return watchParams + }) +} + +func CreateTestPolicyAndWatch(t *testing.T, policyName, watchName string, severity xrayUtils.Severity) (string, func()) { xrayManager, err := xray.CreateXrayServiceManager(configTests.XrDetails) require.NoError(t, err) // Create new default policy. @@ -308,19 +393,9 @@ func CreateTestWatch(t *testing.T, policyName string, watchName, severity xrayUt return "", func() {} } // Create new default watch. - watchParams := xrayUtils.NewWatchParams() - watchParams.Name = fmt.Sprintf("%s-%s", watchName, strconv.FormatInt(time.Now().Unix(), 10)) - watchParams.Active = true - watchParams.Builds.Type = xrayUtils.WatchBuildAll - watchParams.Policies = []xrayUtils.AssignedPolicy{ - { - Name: policyParams.Name, - Type: "security", - }, - } - assert.NoError(t, xrayManager.CreateWatch(watchParams)) - return watchParams.Name, func() { - assert.NoError(t, xrayManager.DeleteWatch(watchParams.Name)) + watchName, cleanUpWatch := CreateWatchForTests(t, policyParams.Name, watchName) + return watchName, func() { + cleanUpWatch() assert.NoError(t, xrayManager.DeletePolicy(policyParams.Name)) } } diff --git a/utils/auditbasicparams.go b/utils/auditbasicparams.go index ddc04d78..1ce9f951 100644 --- a/utils/auditbasicparams.go +++ b/utils/auditbasicparams.go @@ -63,7 +63,7 @@ type AuditBasicParams struct { depsRepo string installCommandName string technologies []string - scansToPreform []SubScanType + scansToPerform []SubScanType args []string installCommandArgs []string dependenciesForApplicabilityScan []string @@ -177,12 +177,12 @@ func (abp *AuditBasicParams) SetTechnologies(technologies []string) *AuditBasicP } func (abp *AuditBasicParams) SetScansToPerform(scansToPerform []SubScanType) *AuditBasicParams { - abp.scansToPreform = scansToPerform + abp.scansToPerform = scansToPerform return abp } func (abp *AuditBasicParams) ScansToPerform() []SubScanType { - return abp.scansToPreform + return abp.scansToPerform } func (abp *AuditBasicParams) Progress() ioUtils.ProgressMgr { diff --git a/utils/formats/conversion.go b/utils/formats/conversion.go index f53a4518..20cfb208 100644 --- a/utils/formats/conversion.go +++ b/utils/formats/conversion.go @@ -1,12 +1,14 @@ package formats import ( - "github.com/jfrog/jfrog-cli-security/utils/jasutils" "strconv" "strings" + + "github.com/jfrog/jfrog-cli-security/utils/jasutils" ) -func ConvertSecurityTableRowToScanTableRow(tableRows []vulnerabilityTableRow) (scanTableRows []vulnerabilityScanTableRow) { +// For binary scans +func ConvertSecurityTableRowToScanTableRow(tableRows []scaVulnerabilityOrViolationTableRow) (scanTableRows []vulnerabilityScanTableRow) { for i := range tableRows { scanTableRows = append(scanTableRows, vulnerabilityScanTableRow{ severity: tableRows[i].severity, @@ -24,6 +26,7 @@ func ConvertSecurityTableRowToScanTableRow(tableRows []vulnerabilityTableRow) (s return } +// For binary scans func ConvertLicenseViolationTableRowToScanTableRow(tableRows []licenseViolationTableRow) (scanTableRows []licenseViolationScanTableRow) { for i := range tableRows { scanTableRows = append(scanTableRows, licenseViolationScanTableRow{ @@ -84,9 +87,9 @@ func convertToComponentScanTableRow(rows []directDependenciesTableRow) (tableRow return } -func ConvertToVulnerabilityTableRow(rows []VulnerabilityOrViolationRow) (tableRows []vulnerabilityTableRow) { +func ConvertToScaVulnerabilityOrViolationTableRow(rows []VulnerabilityOrViolationRow) (tableRows []scaVulnerabilityOrViolationTableRow) { for i := range rows { - tableRows = append(tableRows, vulnerabilityTableRow{ + tableRows = append(tableRows, scaVulnerabilityOrViolationTableRow{ severity: rows[i].Severity, severityNumValue: rows[i].SeverityNumValue, applicable: rows[i].Applicable, @@ -97,12 +100,13 @@ func ConvertToVulnerabilityTableRow(rows []VulnerabilityOrViolationRow) (tableRo directDependencies: convertToComponentTableRow(rows[i].Components), cves: convertToCveTableRow(rows[i].Cves), issueId: rows[i].IssueId, + watch: rows[i].Watch, }) } return } -func ConvertToLicenseViolationTableRow(rows []LicenseRow) (tableRows []licenseViolationTableRow) { +func ConvertToLicenseViolationTableRow(rows []LicenseViolationRow) (tableRows []licenseViolationTableRow) { for i := range rows { tableRows = append(tableRows, licenseViolationTableRow{ licenseKey: rows[i].LicenseKey, @@ -112,6 +116,7 @@ func ConvertToLicenseViolationTableRow(rows []LicenseRow) (tableRows []licenseVi impactedDependencyVersion: rows[i].ImpactedDependencyVersion, impactedDependencyType: rows[i].ImpactedDependencyType, directDependencies: convertToComponentTableRow(rows[i].Components), + watch: rows[i].Watch, }) } return @@ -167,6 +172,7 @@ func ConvertToSecretsTableRow(rows []SourceCodeRow) (tableRows []secretsTableRow secret: rows[i].Snippet, tokenValidation: jasutils.TokenValidationStatus(status).ToString(), tokenInfo: info, + watch: rows[i].Watch, }) } @@ -180,6 +186,7 @@ func ConvertToIacOrSastTableRow(rows []SourceCodeRow) (tableRows []iacOrSastTabl file: rows[i].File, lineColumn: strconv.Itoa(rows[i].StartLine) + ":" + strconv.Itoa(rows[i].StartColumn), finding: rows[i].Finding, + watch: rows[i].Watch, }) } return diff --git a/utils/formats/sarifutils/sarifutils.go b/utils/formats/sarifutils/sarifutils.go index a58c13a8..bbd4f34b 100644 --- a/utils/formats/sarifutils/sarifutils.go +++ b/utils/formats/sarifutils/sarifutils.go @@ -10,6 +10,65 @@ import ( "github.com/owenrumney/go-sarif/v2/sarif" ) +const ( + WatchSarifPropertyKey = "watch" + PoliciesSarifPropertyKey = "policies" + JasIssueIdSarifPropertyKey = "issueId" + CWEPropertyKey = "CWE" +) + +// Specific JFrog Sarif Utils + +func GetResultWatches(result *sarif.Result) (watches string) { + if watchesProperty, ok := result.Properties[WatchSarifPropertyKey]; ok { + if watchesValue, ok := watchesProperty.(string); ok { + return watchesValue + } + } + return +} + +func GetResultPolicies(result *sarif.Result) (policies []string) { + if policiesProperty, ok := result.Properties[PoliciesSarifPropertyKey]; ok { + if policiesValue, ok := policiesProperty.(string); ok { + split := strings.Split(policiesValue, ",") + for _, policy := range split { + policies = append(policies, strings.TrimSpace(policy)) + } + return + } + } + return +} + +func GetResultIssueId(result *sarif.Result) (issueId string) { + if issueIdProperty, ok := result.Properties[JasIssueIdSarifPropertyKey]; ok { + if issueIdValue, ok := issueIdProperty.(string); ok { + return issueIdValue + } + } + return +} + +func GetRuleCWE(rule *sarif.ReportingDescriptor) (cwe []string) { + if rule == nil || rule.DefaultConfiguration == nil || rule.DefaultConfiguration.Parameters == nil || rule.DefaultConfiguration.Parameters.Properties == nil { + // No CWE property + return + } + if cweProperty, ok := rule.DefaultConfiguration.Parameters.Properties[CWEPropertyKey]; ok { + if cweValue, ok := cweProperty.(string); ok { + split := strings.Split(cweValue, ",") + for _, policy := range split { + cwe = append(cwe, strings.TrimSpace(policy)) + } + return + } + } + return +} + +// General Sarif Utils + func NewReport() (*sarif.Report, error) { report, err := sarif.New(sarif.Version210) if err != nil { @@ -39,7 +98,7 @@ func GetToolVersion(run *sarif.Run) string { func CopyRun(run *sarif.Run) *sarif.Run { copy := CopyRunMetadata(run) - if copy.Tool.Driver != nil { + if run.Tool.Driver != nil { copy.Tool.Driver.Rules = CopyRules(run.Tool.Driver.Rules...) } for _, result := range run.Results { @@ -417,6 +476,13 @@ func SetResultMsgMarkdown(markdown string, result *sarif.Result) { result.Message.Markdown = &markdown } +func GetResultMsgMarkdown(result *sarif.Result) string { + if result != nil && result.Message.Markdown != nil { + return *result.Message.Markdown + } + return "" +} + func GetResultMsgText(result *sarif.Result) string { if result.Message.Text != nil { return *result.Message.Text @@ -615,7 +681,7 @@ func GetRuleById(run *sarif.Run, ruleId string) *sarif.ReportingDescriptor { } func GetRuleFullDescription(rule *sarif.ReportingDescriptor) string { - if rule.FullDescription != nil && rule.FullDescription.Text != nil { + if rule != nil && rule.FullDescription != nil && rule.FullDescription.Text != nil { return *rule.FullDescription.Text } return "" @@ -644,7 +710,7 @@ func GetRuleHelpMarkdown(rule *sarif.ReportingDescriptor) string { } func GetRuleShortDescription(rule *sarif.ReportingDescriptor) string { - if rule.ShortDescription != nil && rule.ShortDescription.Text != nil { + if rule != nil && rule.ShortDescription != nil && rule.ShortDescription.Text != nil { return *rule.ShortDescription.Text } return "" diff --git a/utils/formats/sarifutils/test_sarifutils.go b/utils/formats/sarifutils/test_sarifutils.go index 88e7e270..2bd4dc11 100644 --- a/utils/formats/sarifutils/test_sarifutils.go +++ b/utils/formats/sarifutils/test_sarifutils.go @@ -82,12 +82,16 @@ func CreateDummyResultInPath(fileName string) *sarif.Result { return CreateResultWithOneLocation(fileName, 0, 0, 0, 0, "snippet", "rule", "level") } -func CreateDummyResult(markdown, msg, ruleId, level string) *sarif.Result { - return &sarif.Result{ +func CreateDummyResult(markdown, msg, ruleId, level string, locations ...*sarif.Location) *sarif.Result { + result := &sarif.Result{ Message: sarif.Message{Text: &msg, Markdown: &markdown}, Level: &level, RuleID: &ruleId, } + if len(locations) > 0 { + result.Locations = locations + } + return result } func CreateResultWithProperties(msg, ruleId, level string, properties map[string]string, locations ...*sarif.Location) *sarif.Result { diff --git a/utils/formats/simplejsonapi.go b/utils/formats/simplejsonapi.go index a322b758..68d38f4e 100644 --- a/utils/formats/simplejsonapi.go +++ b/utils/formats/simplejsonapi.go @@ -1,6 +1,10 @@ package formats -import "github.com/jfrog/jfrog-cli-security/utils/techutils" +import ( + "fmt" + + "github.com/jfrog/jfrog-cli-security/utils/techutils" +) // Structs in this file should NOT be changed! // The structs are used as an API for the simple-json format, thus changing their structure or the 'json' annotation will break the API. @@ -9,16 +13,38 @@ import "github.com/jfrog/jfrog-cli-security/utils/techutils" type SimpleJsonResults struct { Vulnerabilities []VulnerabilityOrViolationRow `json:"vulnerabilities"` SecurityViolations []VulnerabilityOrViolationRow `json:"securityViolations"` - LicensesViolations []LicenseRow `json:"licensesViolations"` + LicensesViolations []LicenseViolationRow `json:"licensesViolations"` Licenses []LicenseRow `json:"licenses"` OperationalRiskViolations []OperationalRiskViolationRow `json:"operationalRiskViolations"` - Secrets []SourceCodeRow `json:"secrets"` - Iacs []SourceCodeRow `json:"iacViolations"` - Sast []SourceCodeRow `json:"sastViolations"` + SecretsVulnerabilities []SourceCodeRow `json:"secrets"` + IacsVulnerabilities []SourceCodeRow `json:"iac"` + SastVulnerabilities []SourceCodeRow `json:"sast"` + SecretsViolations []SourceCodeRow `json:"secretsViolations"` + IacsViolations []SourceCodeRow `json:"iacViolations"` + SastViolations []SourceCodeRow `json:"sastViolations"` Errors []SimpleJsonError `json:"errors"` + Statuses ScanStatus `json:"scansStatus"` MultiScanId string `json:"multiScanId,omitempty"` } +type ScanStatus struct { + // If not nil, the scan was performed. The value is the status code of the scans. if not 0, the scan failed. + ScaStatusCode *int `json:"scaScanStatusCode,omitempty"` + SastStatusCode *int `json:"sastScanStatusCode,omitempty"` + IacStatusCode *int `json:"iacScanStatusCode,omitempty"` + SecretsStatusCode *int `json:"secretsScanStatusCode,omitempty"` + ApplicabilityStatusCode *int `json:"ContextualAnalysisScanStatusCode,omitempty"` +} + +type ViolationContext struct { + // The watch name that generated the violation + Watch string `json:"watch,omitempty"` + // Unique id of the violation if exists + IssueId string `json:"issueId,omitempty"` + // The related policy names + Policies []string `json:"policies,omitempty"` +} + type SeverityDetails struct { Severity string `json:"severity"` SeverityNumValue int `json:"-"` // For sorting @@ -35,6 +61,7 @@ type ImpactedDependencyDetails struct { // Used for vulnerabilities and security violations type VulnerabilityOrViolationRow struct { ImpactedDependencyDetails + ViolationContext Summary string `json:"summary"` Applicable string `json:"applicable"` FixedVersions []string `json:"fixedVersions"` @@ -46,14 +73,21 @@ type VulnerabilityOrViolationRow struct { Technology techutils.Technology `json:"-"` } +type LicenseViolationRow struct { + LicenseRow + ViolationContext +} + type LicenseRow struct { ImpactedDependencyDetails LicenseKey string `json:"licenseKey"` + LicenseName string `json:"licenseName,omitempty"` ImpactPaths [][]ComponentRow `json:"impactPaths"` } type OperationalRiskViolationRow struct { ImpactedDependencyDetails + ViolationContext RiskReason string `json:"riskReason"` IsEol string `json:"isEndOfLife"` EolMessage string `json:"endOfLifeMessage"` @@ -66,12 +100,20 @@ type OperationalRiskViolationRow struct { type SourceCodeRow struct { SeverityDetails + ViolationContext + ScannerInfo Location - Finding string `json:"finding,omitempty"` - Fingerprint string `json:"fingerprint,omitempty"` - Applicability *Applicability `json:"applicability,omitempty"` - ScannerDescription string `json:"scannerDescription,omitempty"` - CodeFlow [][]Location `json:"codeFlow,omitempty"` + Finding string `json:"finding,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + Applicability *Applicability `json:"applicability,omitempty"` + CodeFlow [][]Location `json:"codeFlow,omitempty"` +} + +type ScannerInfo struct { + RuleId string `json:"ruleId"` + Cwe []string `json:"cwe,omitempty"` + ScannerShortDescription string `json:"scannerShortDescription,omitempty"` + ScannerDescription string `json:"scannerDescription,omitempty"` } type Location struct { @@ -83,6 +125,11 @@ type Location struct { Snippet string `json:"snippet,omitempty"` } +// String Representation of the location (can be used as unique ID of the location) +func (l Location) ToString() string { + return fmt.Sprintf("%s|%d|%d|%d|%d|%s", l.File, l.StartLine, l.StartColumn, l.EndLine, l.EndColumn, l.Snippet) +} + type ComponentRow struct { Name string `json:"name"` Version string `json:"version"` diff --git a/utils/formats/table.go b/utils/formats/table.go index a2c258f8..c1662698 100644 --- a/utils/formats/table.go +++ b/utils/formats/table.go @@ -5,19 +5,26 @@ package formats // Use the conversion methods in this package to convert from the API structs to the table structs. type ResultsTables struct { - SecurityVulnerabilitiesTable []vulnerabilityTableRow - SecurityViolationsTable []vulnerabilityTableRow - LicensesTable []licenseTableRow + // Licenses + LicensesTable []licenseTableRow + // Sca tables + SecurityVulnerabilitiesTable []scaVulnerabilityOrViolationTableRow + SecurityViolationsTable []scaVulnerabilityOrViolationTableRow LicenseViolationsTable []licenseViolationTableRow OperationalRiskViolationsTable []operationalRiskViolationTableRow - IacTable []iacOrSastTableRow - SastTable []iacOrSastTableRow - SecretsTable []secretsTableRow - Errors []error + // Iac tables + IacVulnerabilitiesTable []iacOrSastTableRow + IacViolationsTable []iacOrSastTableRow + // Sast tables + SastVulnerabilitiesTable []iacOrSastTableRow + SastViolationsTable []iacOrSastTableRow + // Secrets + SecretsVulnerabilitiesTable []secretsTableRow + SecretsViolationsTable []secretsTableRow } // Used for vulnerabilities and security violations -type vulnerabilityTableRow struct { +type scaVulnerabilityOrViolationTableRow struct { severity string `col-name:"Severity"` applicable string `col-name:"Contextual\nAnalysis" omitempty:"true"` // For sorting @@ -28,9 +35,11 @@ type vulnerabilityTableRow struct { fixedVersions string `col-name:"Fixed\nVersions"` impactedDependencyType string `col-name:"Type"` cves []cveTableRow `embed-table:"true"` + watch string `col-name:"Watch Name" omitempty:"true"` issueId string `col-name:"Issue ID" extended:"true"` } +// For Binary scans type vulnerabilityScanTableRow struct { severity string `col-name:"Severity"` applicable string `col-name:"Contextual\nAnalysis" omitempty:"true"` @@ -70,6 +79,7 @@ type licenseViolationTableRow struct { impactedDependencyName string `col-name:"Impacted\nDependency"` impactedDependencyVersion string `col-name:"Impacted\nDependency\nVersion"` impactedDependencyType string `col-name:"Type"` + watch string `col-name:"Watch Name"` } type licenseViolationScanTableRow struct { @@ -142,6 +152,7 @@ type secretsTableRow struct { secret string `col-name:"Secret"` tokenValidation string `col-name:"Token Validation" omitempty:"true"` tokenInfo string `col-name:"Token Info" omitempty:"true"` + watch string `col-name:"Watch Name" omitempty:"true"` } type iacOrSastTableRow struct { @@ -149,4 +160,5 @@ type iacOrSastTableRow struct { file string `col-name:"File"` lineColumn string `col-name:"Line:Column"` finding string `col-name:"Finding"` + watch string `col-name:"Watch Name" omitempty:"true"` } diff --git a/utils/results/common.go b/utils/results/common.go index b84e961d..1eb57635 100644 --- a/utils/results/common.go +++ b/utils/results/common.go @@ -56,8 +56,8 @@ type ParseScaViolationFunc func(violation services.Violation, cves []formats.Cve type ParseLicensesFunc func(license services.License, impactedPackagesName, impactedPackagesVersion, impactedPackagesType string, directComponents []formats.ComponentRow, impactPaths [][]formats.ComponentRow) error type ParseJasFunc func(run *sarif.Run, rule *sarif.ReportingDescriptor, severity severityutils.Severity, result *sarif.Result, location *sarif.Location) error -// PrepareJasIssues allows to iterate over the provided SARIF runs and call the provided handler for each issue to process it. -func PrepareJasIssues(runs []*sarif.Run, entitledForJas bool, handler ParseJasFunc) error { +// Allows to iterate over the provided SARIF runs and call the provided handler for each issue to process it. +func ApplyHandlerToJasIssues(runs []*sarif.Run, entitledForJas bool, handler ParseJasFunc) error { if !entitledForJas || handler == nil { return nil } @@ -88,8 +88,8 @@ func PrepareJasIssues(runs []*sarif.Run, entitledForJas bool, handler ParseJasFu return nil } -// PrepareScaVulnerabilities allows to iterate over the provided SCA security vulnerabilities and call the provided handler for each impacted component/package with a vulnerability to process it. -func PrepareScaVulnerabilities(target ScanTarget, vulnerabilities []services.Vulnerability, entitledForJas bool, applicabilityRuns []*sarif.Run, handler ParseScaVulnerabilityFunc) error { +// ApplyHandlerToScaVulnerabilities allows to iterate over the provided SCA security vulnerabilities and call the provided handler for each impacted component/package with a vulnerability to process it. +func ApplyHandlerToScaVulnerabilities(target ScanTarget, vulnerabilities []services.Vulnerability, entitledForJas bool, applicabilityRuns []*sarif.Run, handler ParseScaVulnerabilityFunc) error { if handler == nil { return nil } @@ -116,8 +116,8 @@ func PrepareScaVulnerabilities(target ScanTarget, vulnerabilities []services.Vul return nil } -// PrepareScaViolations allows to iterate over the provided SCA violations and call the provided handler for each impacted component/package with a violation to process it. -func PrepareScaViolations(target ScanTarget, violations []services.Violation, entitledForJas bool, applicabilityRuns []*sarif.Run, securityHandler ParseScaViolationFunc, licenseHandler ParseScaViolationFunc, operationalRiskHandler ParseScaViolationFunc) (watches []string, failBuild bool, err error) { +// ApplyHandlerToScaViolations allows to iterate over the provided SCA violations and call the provided handler for each impacted component/package with a violation to process it. +func ApplyHandlerToScaViolations(target ScanTarget, violations []services.Violation, entitledForJas bool, applicabilityRuns []*sarif.Run, securityHandler ParseScaViolationFunc, licenseHandler ParseScaViolationFunc, operationalRiskHandler ParseScaViolationFunc) (watches []string, failBuild bool, err error) { if securityHandler == nil && licenseHandler == nil && operationalRiskHandler == nil { return } @@ -195,8 +195,8 @@ func PrepareScaViolations(target ScanTarget, violations []services.Violation, en return } -// PrepareLicenses allows to iterate over the provided licenses and call the provided handler for each component/package with a license to process it. -func PrepareLicenses(target ScanTarget, licenses []services.License, handler ParseLicensesFunc) error { +// ApplyHandlerToLicenses allows to iterate over the provided licenses and call the provided handler for each component/package with a license to process it. +func ApplyHandlerToLicenses(target ScanTarget, licenses []services.License, handler ParseLicensesFunc) error { if handler == nil { return nil } @@ -549,6 +549,9 @@ func getApplicabilityStatusFromRule(rule *sarif.ReportingDescriptor) jasutils.Ap } func GetDependencyId(depName, version string) string { + if version == "" { + return depName + } return fmt.Sprintf("%s:%s", depName, version) } @@ -627,3 +630,18 @@ func getFinalApplicabilityStatus(applicabilityStatuses []jasutils.ApplicabilityS return jasutils.NotApplicable } + +func ConvertPolicesToString(policies []services.Policy) []string { + var policiesStr []string + for _, policy := range policies { + policiesStr = append(policiesStr, policy.Policy) + } + return policiesStr +} + +func ScanResultsToRuns(results []ScanResult[[]*sarif.Run]) (runs []*sarif.Run) { + for _, result := range results { + runs = append(runs, result.Scan...) + } + return +} diff --git a/utils/results/conversion/convertor.go b/utils/results/conversion/convertor.go index 6b321ad2..4e8f9b1b 100644 --- a/utils/results/conversion/convertor.go +++ b/utils/results/conversion/convertor.go @@ -52,13 +52,12 @@ type ResultsStreamFormatParser[T interface{}] interface { // Will be called for each scan target (indicating the current is done parsing and starting to parse a new scan) ParseNewTargetResults(target results.ScanTarget, errors ...error) error // Parse SCA content to the current scan target - ParseViolations(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) error - ParseVulnerabilities(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) error - ParseLicenses(target results.ScanTarget, licenses []services.License) error + ParseScaIssues(target results.ScanTarget, violations bool, scaResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) error + ParseLicenses(target results.ScanTarget, scaResponse results.ScanResult[services.ScanResponse]) error // Parse JAS content to the current scan target - ParseSecrets(target results.ScanTarget, secrets ...*sarif.Run) error - ParseIacs(target results.ScanTarget, iacs ...*sarif.Run) error - ParseSast(target results.ScanTarget, sast ...*sarif.Run) error + ParseSecrets(target results.ScanTarget, violations bool, secrets []results.ScanResult[[]*sarif.Run]) error + ParseIacs(target results.ScanTarget, violations bool, iacs []results.ScanResult[[]*sarif.Run]) error + ParseSast(target results.ScanTarget, violations bool, sast []results.ScanResult[[]*sarif.Run]) error // When done parsing the stream results, get the converted content Get() (T, error) } @@ -101,23 +100,11 @@ func parseCommandResults[T interface{}](params ResultConvertParams, parser Resul return } } - if !jasEntitled || targetScansResults.JasResults == nil { + if !jasEntitled { continue } - if utils.IsScanRequested(cmdResults.CmdType, utils.SecretsScan, params.RequestedScans...) { - if err = parser.ParseSecrets(targetScansResults.ScanTarget, targetScansResults.JasResults.SecretsScanResults...); err != nil { - return - } - } - if utils.IsScanRequested(cmdResults.CmdType, utils.IacScan, params.RequestedScans...) { - if err = parser.ParseIacs(targetScansResults.ScanTarget, targetScansResults.JasResults.IacScanResults...); err != nil { - return - } - } - if utils.IsScanRequested(cmdResults.CmdType, utils.SastScan, params.RequestedScans...) { - if err = parser.ParseSast(targetScansResults.ScanTarget, targetScansResults.JasResults.SastScanResults...); err != nil { - return - } + if err = parseJasResults(params, parser, targetScansResults, cmdResults.CmdType); err != nil { + return } } return parser.Get() @@ -129,29 +116,29 @@ func parseScaResults[T interface{}](params ResultConvertParams, parser ResultsSt } for _, scaResults := range targetScansResults.ScaResults.XrayResults { actualTarget := getScaScanTarget(targetScansResults.ScaResults, targetScansResults.ScanTarget) - var applicableRuns []*sarif.Run + var applicableRuns []results.ScanResult[[]*sarif.Run] if jasEntitled && targetScansResults.JasResults != nil { applicableRuns = targetScansResults.JasResults.ApplicabilityScanResults } if params.IncludeVulnerabilities { - if err = parser.ParseVulnerabilities(actualTarget, scaResults, applicableRuns...); err != nil { + if err = parser.ParseScaIssues(actualTarget, false, scaResults, applicableRuns...); err != nil { return } } if params.HasViolationContext { - if err = parser.ParseViolations(actualTarget, scaResults, applicableRuns...); err != nil { + if err = parser.ParseScaIssues(actualTarget, true, scaResults, applicableRuns...); err != nil { return } - } else if len(scaResults.Violations) == 0 && len(params.AllowedLicenses) > 0 { + } else if !scaResults.IsScanFailed() && len(scaResults.Scan.Violations) == 0 && len(params.AllowedLicenses) > 0 { // If no violations were found, check if there are licenses that are not allowed - if scaResults.Violations = results.GetViolatedLicenses(params.AllowedLicenses, scaResults.Licenses); len(scaResults.Violations) > 0 { - if err = parser.ParseViolations(actualTarget, scaResults); err != nil { + if scaResults.Scan.Violations = results.GetViolatedLicenses(params.AllowedLicenses, scaResults.Scan.Licenses); len(scaResults.Scan.Violations) > 0 { + if err = parser.ParseScaIssues(actualTarget, true, scaResults); err != nil { return } } } if params.IncludeLicenses { - if err = parser.ParseLicenses(actualTarget, scaResults.Licenses); err != nil { + if err = parser.ParseLicenses(actualTarget, scaResults); err != nil { return } } @@ -178,3 +165,54 @@ func getScaScanTarget(scaResults *results.ScaScanResults, target results.ScanTar } return target } + +func parseJasResults[T interface{}](params ResultConvertParams, parser ResultsStreamFormatParser[T], targetResults *results.TargetResults, cmdType utils.CommandType) (err error) { + if targetResults.JasResults == nil { + return + } + // Parsing JAS Secrets results + if err = parseJasScanResults(params, targetResults, cmdType, utils.SecretsScan, func(violations bool) error { + scanResults := targetResults.JasResults.JasVulnerabilities.SecretsScanResults + if violations { + scanResults = targetResults.JasResults.JasViolations.SecretsScanResults + } + return parser.ParseSecrets(targetResults.ScanTarget, violations, scanResults) + }); err != nil { + return + } + // Parsing JAS IAC results + if err = parseJasScanResults(params, targetResults, cmdType, utils.IacScan, func(violations bool) error { + scanResults := targetResults.JasResults.JasVulnerabilities.IacScanResults + if violations { + scanResults = targetResults.JasResults.JasViolations.IacScanResults + } + return parser.ParseIacs(targetResults.ScanTarget, violations, scanResults) + }); err != nil { + return + } + // Parsing JAS SAST results + return parseJasScanResults(params, targetResults, cmdType, utils.SastScan, func(violations bool) error { + scanResults := targetResults.JasResults.JasVulnerabilities.SastScanResults + if violations { + scanResults = targetResults.JasResults.JasViolations.SastScanResults + } + return parser.ParseSast(targetResults.ScanTarget, violations, scanResults) + }) +} + +func parseJasScanResults(params ResultConvertParams, targetResults *results.TargetResults, cmdType utils.CommandType, subScanType utils.SubScanType, parseJasFunc func(violations bool) error) (err error) { + if !utils.IsScanRequested(cmdType, subScanType, params.RequestedScans...) || targetResults.JasResults == nil { + return + } + if params.IncludeVulnerabilities { + // Parse vulnerabilities + if err = parseJasFunc(false); err != nil { + return + } + } + if !params.HasViolationContext { + return + } + // Parse violations + return parseJasFunc(true) +} diff --git a/utils/results/conversion/convertor_test.go b/utils/results/conversion/convertor_test.go index ae247d27..540b6b2a 100644 --- a/utils/results/conversion/convertor_test.go +++ b/utils/results/conversion/convertor_test.go @@ -7,6 +7,11 @@ import ( "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" + "github.com/jfrog/jfrog-cli-security/utils/techutils" + "github.com/jfrog/jfrog-client-go/xray/services" testUtils "github.com/jfrog/jfrog-cli-security/tests/utils" "github.com/jfrog/jfrog-cli-security/utils/results" @@ -28,107 +33,69 @@ const ( type conversionFormat string -func getAuditValidationParams() validations.ValidationParams { - return validations.ValidationParams{ - ExactResultsMatch: true, - SecurityViolations: 11, - Vulnerabilities: 19, - Applicable: 1, - NotApplicable: 7, - NotCovered: 4, - Sast: 4, - Secrets: 3, - } -} - -// For Summary we count unique CVE finding (issueId), for SARIF and SimpleJson we count all findings (pair of issueId+impactedComponent) -// We have in the result 2 CVE with 2 impacted components each -func getDockerScanValidationParams(unique bool) validations.ValidationParams { - params := validations.ValidationParams{ - ExactResultsMatch: true, - Secrets: 3, - } - if unique { - params.Vulnerabilities = 11 - params.Applicable = 3 - params.NotApplicable = 3 - params.NotCovered = 1 - params.Undetermined = 1 - } else { - params.Vulnerabilities = 14 - params.Applicable = 5 - params.NotApplicable = 4 - params.NotCovered = 1 - params.Undetermined = 1 - } - return params -} - func TestConvertResults(t *testing.T) { - auditInputResults := testUtils.ReadCmdScanResults(t, filepath.Join(testDataDir, "audit", "audit_results.json")) - dockerScanInputResults := testUtils.ReadCmdScanResults(t, filepath.Join(testDataDir, "dockerscan", "docker_results.json")) - testCases := []struct { + cmdType utils.CommandType contentFormat conversionFormat - inputResults *results.SecurityCommandResults expectedContentPath string }{ { + cmdType: utils.SourceCode, contentFormat: SimpleJson, - inputResults: auditInputResults, expectedContentPath: filepath.Join(testDataDir, "audit", "audit_simple_json.json"), }, { + cmdType: utils.SourceCode, contentFormat: Sarif, - inputResults: auditInputResults, expectedContentPath: filepath.Join(testDataDir, "audit", "audit_sarif.json"), }, { + cmdType: utils.SourceCode, contentFormat: Summary, - inputResults: auditInputResults, expectedContentPath: filepath.Join(testDataDir, "audit", "audit_summary.json"), }, { + cmdType: utils.DockerImage, contentFormat: SimpleJson, - inputResults: dockerScanInputResults, expectedContentPath: filepath.Join(testDataDir, "dockerscan", "docker_simple_json.json"), }, { + cmdType: utils.DockerImage, contentFormat: Sarif, - inputResults: dockerScanInputResults, expectedContentPath: filepath.Join(testDataDir, "dockerscan", "docker_sarif.json"), }, { + cmdType: utils.DockerImage, contentFormat: Summary, - inputResults: dockerScanInputResults, expectedContentPath: filepath.Join(testDataDir, "dockerscan", "docker_summary.json"), }, } for _, testCase := range testCases { - t.Run(fmt.Sprintf("%s convert to %s", testCase.inputResults.CmdType, testCase.contentFormat), func(t *testing.T) { + t.Run(fmt.Sprintf("%s convert to %s", testCase.cmdType, testCase.contentFormat), func(t *testing.T) { var validationParams validations.ValidationParams - switch testCase.inputResults.CmdType { + var inputResults *results.SecurityCommandResults + switch testCase.cmdType { case utils.SourceCode: - validationParams = getAuditValidationParams() + inputResults, validationParams = getAuditTestResults(testCase.contentFormat == Summary) case utils.DockerImage: - validationParams = getDockerScanValidationParams(testCase.contentFormat == Summary) + inputResults, validationParams = getDockerScanTestResults(testCase.contentFormat == Summary) default: - t.Fatalf("Unsupported command type: %s", testCase.inputResults.CmdType) + t.Fatalf("Unsupported command type: %s", testCase.cmdType) } pretty := false if testCase.contentFormat == Sarif { pretty = true } - convertor := NewCommandResultsConvertor(ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true, Pretty: pretty}) + convertor := NewCommandResultsConvertor(ResultConvertParams{IncludeVulnerabilities: true, HasViolationContext: true, IncludeLicenses: true, Pretty: pretty}) switch testCase.contentFormat { case SimpleJson: - validateSimpleJsonConversion(t, testUtils.ReadSimpleJsonResults(t, testCase.expectedContentPath), testCase.inputResults, convertor, validationParams) + validateSimpleJsonConversion(t, testUtils.ReadSimpleJsonResults(t, testCase.expectedContentPath), inputResults, convertor, validationParams) case Sarif: - validateSarifConversion(t, testUtils.ReadSarifResults(t, testCase.expectedContentPath), testCase.inputResults, convertor, validationParams) + validateSarifConversion(t, testUtils.ReadSarifResults(t, testCase.expectedContentPath), inputResults, convertor, validationParams) case Summary: - validateSummaryConversion(t, testUtils.ReadSummaryResults(t, testCase.expectedContentPath), testCase.inputResults, convertor, validationParams) + validateSummaryConversion(t, testUtils.ReadSummaryResults(t, testCase.expectedContentPath), inputResults, convertor, validationParams) } }) } @@ -155,7 +122,7 @@ func validateSarifConversion(t *testing.T, expectedResults *sarif.Report, inputR } validationParams.Actual = actualResults - validations.ValidateCommandSarifOutput(t, validationParams) + validations.ValidateSarifIssuesCount(t, validationParams, actualResults) } func validateSummaryConversion(t *testing.T, expectedResults formats.ResultsSummary, inputResults *results.SecurityCommandResults, convertor *CommandResultsConvertor, validationParams validations.ValidationParams) { @@ -169,3 +136,436 @@ func validateSummaryConversion(t *testing.T, expectedResults formats.ResultsSumm validations.ValidateCommandSummaryOutput(t, validationParams) } + +func getAuditTestResults(unique bool) (*results.SecurityCommandResults, validations.ValidationParams) { + expected := validations.ValidationParams{ + ExactResultsMatch: true, + Total: &validations.TotalCount{Violations: 7, Licenses: 1}, + Violations: &validations.ViolationCount{ + ValidateScan: &validations.ScanCount{Sca: 3, Sast: 2, Secrets: 2}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 1, NotCovered: 1}, + }, + } + if unique { + // Only count CVE findings, not impacted components + expected.Total.Vulnerabilities = 7 + expected.Vulnerabilities = &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 4, Iac: 1, Secrets: 2}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 1, NotApplicable: 2, NotCovered: 1}, + } + } else { + // Count all findings (pair of issueId+impactedComponent) + expected.Total.Vulnerabilities = 8 + expected.Vulnerabilities = &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 5, Iac: 1, Secrets: 2}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 1, NotApplicable: 3, NotCovered: 1}, + } + } + // Create basic command results to be converted to different formats + cmdResults := results.NewCommandResults(utils.SourceCode) + cmdResults.SetEntitledForJas(true).SetXrayVersion("3.107.13").SetXscVersion("1.12.5").SetMultiScanId("7d5e4733-3f93-11ef-8147-e610d09d7daa") + npmTargetResults := cmdResults.NewScanResults(results.ScanTarget{Target: filepath.Join("Users", "user", "project-with-issues"), Technology: techutils.Npm}).SetDescriptors(filepath.Join("Users", "user", "project-with-issues", "package.json")) + // SCA scan results + npmTargetResults.NewScaScanResults(0, services.ScanResponse{ + ScanId: "711851ce-68c4-4dfd-7afb-c29737ebcb96", + Vulnerabilities: []services.Vulnerability{ + { + Cves: []services.Cve{{ + Id: "CVE-2024-39249", + }}, + Summary: "Async vulnerable to ReDoS", + Severity: severityutils.Unknown.String(), + Components: map[string]services.Component{ + "npm://async:3.2.4": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://jake:10.8.7"}, + {ComponentId: "npm://async:3.2.4"}, + }}, + }, + }, + IssueId: "XRAY-609848", + ExtendedInformation: &services.ExtendedInformation{ + ShortDescription: "ReDoS in Async may lead to denial of service while parsing", + JfrogResearchSeverity: "Low", + JfrogResearchSeverityReasons: []services.JfrogResearchSeverityReason{ + {Name: "The reported CVSS was either wrongly calculated", Description: "The reported CVSS does not reflect the severity of the vulnerability", IsPositive: true}, + }, + }, + References: []string{"https://github.com/zunak/CVE-2024-39249", "https://nvd.nist.gov/vuln/detail/CVE-2024-39249"}, + }, + { + Cves: []services.Cve{{ + Id: "CVE-2020-8203", + CvssV2Score: "5.8", + CvssV3Score: "7.4", + }}, + Summary: "Code Injection", + Severity: severityutils.High.String(), + Components: map[string]services.Component{ + "npm://lodash:4.17.0": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://lodash:4.17.0"}, + }}, + FixedVersions: []string{"[4.17.19]"}, + }, + "npm://ejs:3.1.6": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://lodash:4.17.0"}, + {ComponentId: "npm://ejs:3.1.6"}, + }}, + FixedVersions: []string{"[3.1.7]"}, + }, + }, + IssueId: "XRAY-114089", + ExtendedInformation: &services.ExtendedInformation{JfrogResearchSeverity: "Low"}, + }, + { + Cves: []services.Cve{{ + Id: "CVE-2018-16487", + }}, + Summary: "Prototype Pollution", + Severity: severityutils.Medium.String(), + Components: map[string]services.Component{ + "npm://lodash:4.17.0": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://lodash:4.17.0"}, + }}, + FixedVersions: []string{"[4.17.11]"}, + }, + }, + IssueId: "XRAY-75300", + ExtendedInformation: &services.ExtendedInformation{Remediation: "Some remediation"}, + }, + { + Cves: []services.Cve{{ + Id: "CVE-2018-3721", + }}, + Summary: "Improperly Controlled Modification of Object", + Severity: severityutils.Medium.String(), + Components: map[string]services.Component{ + "npm://lodash:4.17.0": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://lodash:4.17.0"}, + }}, + FixedVersions: []string{"[4.17.5]"}, + }, + }, + IssueId: "XRAY-72918", + }, + }, + Violations: []services.Violation{ + { + ViolationType: utils.ViolationTypeSecurity.String(), + Cves: []services.Cve{{ + Id: "CVE-2024-39249", + }}, + Summary: "Async vulnerable to ReDoS", + Severity: severityutils.Unknown.String(), + Components: map[string]services.Component{ + "npm://async:3.2.4": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://jake:10.8.7"}, + {ComponentId: "npm://async:3.2.4"}, + }}, + }, + }, + WatchName: "security-watch", + Policies: []services.Policy{{Policy: "npm-security"}}, + IssueId: "XRAY-609848", + ExtendedInformation: &services.ExtendedInformation{JfrogResearchSeverity: "Low"}, + }, + { + ViolationType: utils.ViolationTypeSecurity.String(), + Cves: []services.Cve{{ + Id: "CVE-2018-3721", + }}, + Summary: "Improperly Controlled Modification of Object", + Severity: severityutils.Medium.String(), + Components: map[string]services.Component{ + "npm://lodash:4.17.0": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://lodash:4.17.0"}, + }}, + FixedVersions: []string{"[4.17.5]"}, + }, + }, + WatchName: "security-watch", + Policies: []services.Policy{{Policy: "npm-security"}}, + IssueId: "XRAY-72918", + }, + { + ViolationType: utils.ViolationTypeLicense.String(), + LicenseKey: "MIT", + LicenseName: "MIT full name", + Severity: severityutils.High.String(), + Components: map[string]services.Component{ + "npm://lodash:4.17.0": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://lodash:4.17.0"}, + }}, + }, + }, + WatchName: "license-watch", + Policies: []services.Policy{{Policy: "npm-license"}}, + IssueId: "MIT", + }, + }, + Licenses: []services.License{ + { + Key: "MIT", + Name: "MIT full name", + Components: map[string]services.Component{ + "npm://lodash:4.17.0": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "npm://froghome:1.0.0"}, + {ComponentId: "npm://lodash:4.17.0"}, + }}, + }, + }, + }, + }, + ScannedStatus: "completed", + }) + // Contextual analysis scan results + npmTargetResults.JasResults.NewApplicabilityScanResults(0, + &sarif.Run{ + Tool: sarif.Tool{ + Driver: sarifutils.CreateDummyDriver(validations.ContextualAnalysisToolName, + validations.CreateDummyApplicabilityRule("CVE-2024-39249", "applicable"), + validations.CreateDummyApplicabilityRule("CVE-2018-16487", "not_applicable"), + validations.CreateDummyApplicabilityRule("CVE-2020-8203", "not_applicable"), + validations.CreateDummyApplicabilityRule("CVE-2018-3721", "not_covered"), + ), + }, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("Users", "user", "project-with-issues")))}, + Results: []*sarif.Result{ + validations.CreateDummyApplicableResults("CVE-2024-39249", formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "file-A"), StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "snippet"}), + validations.CreateDummyApplicableResults("CVE-2024-39249", formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "file-B"), StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "snippet2"}), + // Not Applicable result = remediation location, not a finding add for test confirmation + validations.CreateDummyApplicableResults("CVE-2018-16487", formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "file-C"), StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "snippet3"}), + }, + }, + ) + // Iac scan results + npmTargetResults.JasResults.AddJasScanResults(jasutils.IaC, + []*sarif.Run{{ + Tool: sarif.Tool{Driver: sarifutils.CreateDummyDriver(validations.IacToolName, validations.CreateDummyJasRule("aws_cloudfront_tls_only"))}, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("Users", "user", "project-with-issues")))}, + Results: []*sarif.Result{ + validations.CreateDummyJasResult("aws_cloudfront_tls_only", severityutils.LevelError, formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "req_sw_terraform_aws_cloudfront_tls_only.tf"), StartLine: 2, StartColumn: 1, EndLine: 21, EndColumn: 1, Snippet: "viewer_protocol_policy..."}), + }, + }}, + // No Violations + []*sarif.Run{}, 0, + ) + // Secrets scan results + npmTargetResults.JasResults.AddJasScanResults(jasutils.Secrets, + []*sarif.Run{{ + Tool: sarif.Tool{Driver: sarifutils.CreateDummyDriver(validations.SecretsToolName, validations.CreateDummyJasRule("REQ.SECRET.KEYS"))}, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("Users", "user", "project-with-issues")))}, + Results: []*sarif.Result{ + validations.CreateDummySecretResult("REQ.SECRET.KEYS", jasutils.Active, "active token", formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "fake-creds.txt"), StartLine: 2, StartColumn: 1, EndLine: 2, EndColumn: 11, Snippet: "Sqc************"}), + validations.CreateDummySecretResult("REQ.SECRET.KEYS", jasutils.NotAToken, "", formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "dir", "server.js"), StartLine: 3, StartColumn: 1, EndLine: 3, EndColumn: 11, Snippet: "gho************"}), + }, + }}, + []*sarif.Run{{ + Tool: sarif.Tool{Driver: sarifutils.CreateDummyDriver(validations.SecretsToolName, validations.CreateDummyJasRule("REQ.SECRET.KEYS"))}, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("Users", "user", "project-with-issues")))}, + Results: []*sarif.Result{ + validations.CreateDummySecretViolationResult("REQ.SECRET.KEYS", jasutils.Active, "active token", "watch", "sec-violation-1", []string{"policy"}, formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "fake-creds.txt"), StartLine: 2, StartColumn: 1, EndLine: 2, EndColumn: 11, Snippet: "Sqc************"}), + validations.CreateDummySecretViolationResult("REQ.SECRET.KEYS", jasutils.NotAToken, "", "watch", "sec-violation-2", []string{"policy"}, formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "server.js"), StartLine: 3, StartColumn: 1, EndLine: 3, EndColumn: 11, Snippet: "gho************"}), + }, + }}, 0, + ) + // Sast scan results + npmTargetResults.JasResults.AddJasScanResults(jasutils.Sast, + // No Vulnerabilities + []*sarif.Run{{ + Tool: sarif.Tool{Driver: sarifutils.CreateDummyDriver(validations.SastToolName, validations.CreateDummyJasRule("aws_cloudfront_tls_only"))}, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("Users", "user", "project-with-issues")))}, + }}, + []*sarif.Run{{ + Tool: sarif.Tool{Driver: sarifutils.CreateDummyDriver(validations.SastToolName, validations.CreateDummyJasRule("js-template-injection", "73"), validations.CreateDummyJasRule("js-insecure-random", "338"))}, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("Users", "user", "project-with-issues")))}, + Results: []*sarif.Result{ + validations.CreateDummySastViolationResult("js-insecure-random", severityutils.LevelNote, "watch", "sast-violation-1", []string{"policy", "policy2"}, formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "public", "js", "bootstrap.bundle.js"), StartLine: 136, StartColumn: 22, EndLine: 136, EndColumn: 35, Snippet: "Math.random()"}), + validations.CreateDummySastViolationResult("js-template-injection", severityutils.LevelError, "watch", "sast-violation-2", []string{"policy", "policy2"}, formats.Location{File: filepath.Join("Users", "user", "project-with-issues", "server.js"), StartLine: 26, StartColumn: 28, EndLine: 26, EndColumn: 37, Snippet: "req.query"}, + []formats.Location{ + {File: "/Users/user/project-with-issues/server.js", StartLine: 27, StartColumn: 28, EndLine: 26, EndColumn: 31, Snippet: "req"}, + {File: "/Users/user/project-with-issues/server.js", StartLine: 26, StartColumn: 28, EndLine: 26, EndColumn: 37, Snippet: "req.query"}, + }, + ), + }, + }}, + 0, + ) + return cmdResults, expected +} + +func getDockerScanTestResults(unique bool) (*results.SecurityCommandResults, validations.ValidationParams) { + expected := validations.ValidationParams{ + ExactResultsMatch: true, + Total: &validations.TotalCount{Violations: 2}, + Violations: &validations.ViolationCount{ + ValidateScan: &validations.ScanCount{Sca: 1, Secrets: 1}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 1, Inactive: 1}, + }, + } + if unique { + // Only count CVE findings, not impacted components + expected.Total.Vulnerabilities = 3 + expected.Vulnerabilities = &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 2, Secrets: 1}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 1, Undetermined: 1, Inactive: 1}, + } + } else { + // Count all findings (pair of issueId+impactedComponent) + expected.Total.Vulnerabilities = 4 + expected.Vulnerabilities = &validations.VulnerabilityCount{ + ValidateScan: &validations.ScanCount{Sca: 3, Secrets: 1}, + ValidateApplicabilityStatus: &validations.ApplicabilityStatusCount{Applicable: 1, Undetermined: 2, Inactive: 1}, + } + } + // Create basic command results to be converted to different formats + cmdResults := results.NewCommandResults(utils.DockerImage) + cmdResults.SetEntitledForJas(true).SetXrayVersion("3.107.13").SetXscVersion("1.12.5").SetMultiScanId("7d5e4733-3f93-11ef-8147-e610d09d7daa") + dockerImageTarget := cmdResults.NewScanResults(results.ScanTarget{Target: filepath.Join("temp", "folders", "T", "jfrog.cli.temp.-11-11", "image.tar"), Name: "platform.jfrog.io/swamp-docker/swamp:latest", Technology: techutils.Oci}) + // SCA scan results + dockerImageTarget.NewScaScanResults(0, services.ScanResponse{ + ScanId: "27da9106-88ea-416b-799b-bc7d15783473", + Vulnerabilities: []services.Vulnerability{ + { + Cves: []services.Cve{{ + Id: "CVE-2024-6119", + }}, + Summary: "Issue summary", + Severity: severityutils.Unknown.String(), + Components: map[string]services.Component{ + "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "docker://platform.jfrog.io/swamp-docker/swamp:latest"}, + { + ComponentId: "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + FullPath: "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + }, + { + ComponentId: "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1", + FullPath: "libssl3:3.0.13-1~deb12u1", + }, + }}, + }, + }, + IssueId: "XRAY-632747", + ExtendedInformation: &services.ExtendedInformation{JfrogResearchSeverity: "Medium"}, + }, + { + Cves: []services.Cve{{ + Id: "CVE-2024-38428", + }}, + Summary: "Interpretation Conflict", + Severity: severityutils.Critical.String(), + Components: map[string]services.Component{ + "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "docker://platform.jfrog.io/swamp-docker/swamp:latest"}, + { + ComponentId: "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + FullPath: "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + }, + { + ComponentId: "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1", + FullPath: "libssl3:3.0.13-1~deb12u1", + }, + }}, + }, + "deb://debian:bookworm:openssl:3.0.13-1~deb12u1": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "docker://platform.jfrog.io/swamp-docker/swamp:latest"}, + { + ComponentId: "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + FullPath: "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + }, + { + ComponentId: "deb://debian:bookworm:openssl:3.0.13-1~deb12u1", + FullPath: "openssl:3.0.13-1~deb12u1", + }, + }}, + FixedVersions: []string{"[3.0.14-1~deb12u2]"}, + }, + }, + IssueId: "XRAY-606103", + ExtendedInformation: &services.ExtendedInformation{JfrogResearchSeverity: "Critical"}, + }, + }, + Violations: []services.Violation{ + { + ViolationType: utils.ViolationTypeSecurity.String(), + Cves: []services.Cve{{ + Id: "CVE-2024-6119", + }}, + Summary: "Issue summary", + Severity: severityutils.Unknown.String(), + Components: map[string]services.Component{ + "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1": { + ImpactPaths: [][]services.ImpactPathNode{{ + {ComponentId: "docker://platform.jfrog.io/swamp-docker/swamp:latest"}, + { + ComponentId: "generic://sha256:f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595/sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + FullPath: "sha256__f21c087a3964a446bce1aa4e3ec7cf82020dd77ad14f1cf4ea49cbb32eda1595.tar", + }, + { + ComponentId: "deb://debian:bookworm:libssl3:3.0.13-1~deb12u1", + FullPath: "libssl3:3.0.13-1~deb12u1", + }, + }}, + }, + }, + IssueId: "XRAY-632747", + ExtendedInformation: &services.ExtendedInformation{JfrogResearchSeverity: "Medium"}, + WatchName: "security-watch", + Policies: []services.Policy{{Policy: "debian-security"}}, + }, + }, + ScannedStatus: "completed", + }) + // Contextual analysis scan results + dockerImageTarget.JasResults.NewApplicabilityScanResults(0, + &sarif.Run{ + Tool: sarif.Tool{ + Driver: sarifutils.CreateDummyDriver(validations.ContextualAnalysisToolName, + validations.CreateDummyApplicabilityRule("CVE-2024-6119", "applicable"), + validations.CreateDummyApplicabilityRule("CVE-2024-38428", "undetermined"), + ), + }, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("temp", "folders", "T", "jfrog.cli.temp.-11-11")))}, + Results: []*sarif.Result{validations.CreateDummyApplicableResults("CVE-2024-6119", formats.Location{File: "file://" + filepath.Join("usr", "local", "bin", "node")})}, + }, + ) + // Secrets scan results + dockerImageTarget.JasResults.AddJasScanResults(jasutils.Secrets, + []*sarif.Run{{ + Tool: sarif.Tool{Driver: sarifutils.CreateDummyDriver(validations.SecretsToolName, validations.CreateDummyJasRule("REQ.SECRET.GENERIC.CODE"))}, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("temp", "folders", "T", "jfrog.cli.temp.-11-11")))}, + Results: []*sarif.Result{ + validations.CreateDummySecretResult("REQ.SECRET.GENERIC.CODE", jasutils.Inactive, "expired", formats.Location{File: filepath.Join("temp", "folders", "T", "tmpsfyn_3d1", "unpacked", "sha256", "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", "usr", "src", "app", "server", "index.js"), StartLine: 5, StartColumn: 7, EndLine: 5, EndColumn: 57, Snippet: "tok************"}), + }, + }}, + []*sarif.Run{{ + Tool: sarif.Tool{Driver: sarifutils.CreateDummyDriver(validations.SecretsToolName, validations.CreateDummyJasRule("REQ.SECRET.GENERIC.CODE"))}, + Invocations: []*sarif.Invocation{sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation(filepath.Join("temp", "folders", "T", "jfrog.cli.temp.-11-11")))}, + Results: []*sarif.Result{ + validations.CreateDummySecretViolationResult("REQ.SECRET.GENERIC.CODE", jasutils.Inactive, "expired", "watch", "sec-violation-1", []string{"policy"}, formats.Location{File: filepath.Join("temp", "folders", "T", "tmpsfyn_3d1", "unpacked", "sha256", "9e88ea9de1b44baba5e96a79e33e4af64334b2bf129e838e12f6dae71b5c86f0", "usr", "src", "app", "server", "index.js"), StartLine: 5, StartColumn: 7, EndLine: 5, EndColumn: 57, Snippet: "tok************"}), + }, + }}, 0, + ) + + return cmdResults, expected +} diff --git a/utils/results/conversion/sarifparser/sarifparser.go b/utils/results/conversion/sarifparser/sarifparser.go index 4dba3a6b..f017ed73 100644 --- a/utils/results/conversion/sarifparser/sarifparser.go +++ b/utils/results/conversion/sarifparser/sarifparser.go @@ -10,7 +10,6 @@ import ( "github.com/owenrumney/go-sarif/v2/sarif" - "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" @@ -28,8 +27,7 @@ const ( CurrentWorkflowRunNumberEnvVar = "GITHUB_RUN_NUMBER" CurrentWorkflowWorkspaceEnvVar = "GITHUB_WORKSPACE" - FixedVersionSarifPropertyKey = "fixedVersion" - WatchSarifPropertyKey = "watch" + fixedVersionSarifPropertyKey = "fixedVersion" jfrogFingerprintAlgorithmName = "jfrogFingerprintHash" MissingCveScore = "0" maxPossibleCve = 10.0 @@ -53,16 +51,48 @@ type CmdResultsSarifConverter struct { // If we are running on Github actions, we need to add/change information to the output patchBinaryPaths bool // Current stream parse cache information - current *sarif.Report - scaCurrentRun *sarif.Run - currentTarget results.ScanTarget - parsedScaKeys *datastructures.Set[string] + current *sarif.Report + currentTargetConvertedRuns *currentTargetRuns // General information on the current command results entitledForJas bool xrayVersion string currentCmdType utils.CommandType } +type currentTargetRuns struct { + currentTarget results.ScanTarget + // Current run cache information, we combine vulnerabilities and violations in the same run + scaCurrentRun *sarif.Run + secretsCurrentRun *sarif.Run + iacCurrentRun *sarif.Run + sastCurrentRun *sarif.Run +} + +// Parse parameters for the SCA result +type scaParseParams struct { + CmdType utils.CommandType + IssueId string + Summary string + MarkdownDescription string + CveScore string + ImpactedPackagesName string + ImpactedPackagesVersion string + Watch string + GenerateTitleFunc func(depName string, version string, issueId string, watch string) string + Cves []formats.CveRow + Severity severityutils.Severity + ApplicabilityStatus jasutils.ApplicabilityStatus + FixedVersions []string + DirectComponents []formats.ComponentRow + Violation *violationContext +} + +// holds the violation context for the results +type violationContext struct { + Watch string + Policies []string +} + func NewCmdResultsSarifConverter(baseUrl string, includeVulnerabilities, hasViolationContext, patchBinaryPaths bool) *CmdResultsSarifConverter { return &CmdResultsSarifConverter{baseJfrogUrl: baseUrl, includeVulnerabilities: includeVulnerabilities, hasViolationContext: hasViolationContext, patchBinaryPaths: patchBinaryPaths} } @@ -88,7 +118,7 @@ func (sc *CmdResultsSarifConverter) Reset(cmdType utils.CommandType, _, xrayVers sc.xrayVersion = xrayVersion sc.entitledForJas = entitledForJas // Reset the current stream cache information - sc.scaCurrentRun = nil + sc.currentTargetConvertedRuns = nil return } @@ -96,19 +126,38 @@ func (sc *CmdResultsSarifConverter) ParseNewTargetResults(target results.ScanTar if sc.current == nil { return results.ErrResetConvertor } - if sc.scaCurrentRun != nil { - // Flush the current run - sc.current.Runs = append(sc.current.Runs, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.ScaScan, sc.patchBinaryPaths, sc.currentTarget, sc.scaCurrentRun)...) - } - sc.currentTarget = target + sc.flush() + // Reset the current stream cache information + sc.currentTargetConvertedRuns = ¤tTargetRuns{currentTarget: target} if sc.hasViolationContext || sc.includeVulnerabilities { - // Create Sca Run if requested to parse all vulnerabilities/violations to it - sc.scaCurrentRun = sc.createScaRun(sc.currentTarget, len(errors)) - sc.parsedScaKeys = datastructures.MakeSet[string]() + sc.currentTargetConvertedRuns.scaCurrentRun = sc.createScaRun(target, len(errors)) } return } +func (sc *CmdResultsSarifConverter) flush() { + if sc.currentTargetConvertedRuns == nil { + return + } + // Flush Sca if needed + if sc.currentTargetConvertedRuns.scaCurrentRun != nil { + sc.current.Runs = append(sc.current.Runs, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.ScaScan, sc.patchBinaryPaths, false, sc.currentTargetConvertedRuns.currentTarget, sc.currentTargetConvertedRuns.scaCurrentRun)...) + } + // Flush secrets if needed + if sc.currentTargetConvertedRuns.secretsCurrentRun != nil { + sc.current.Runs = append(sc.current.Runs, sc.currentTargetConvertedRuns.secretsCurrentRun) + } + // Flush iac if needed + if sc.currentTargetConvertedRuns.iacCurrentRun != nil { + sc.current.Runs = append(sc.current.Runs, sc.currentTargetConvertedRuns.iacCurrentRun) + } + // Flush sast if needed + if sc.currentTargetConvertedRuns.sastCurrentRun != nil { + sc.current.Runs = append(sc.current.Runs, sc.currentTargetConvertedRuns.sastCurrentRun) + } + sc.currentTargetConvertedRuns = nil +} + func (sc *CmdResultsSarifConverter) createScaRun(target results.ScanTarget, errorCount int) *sarif.Run { run := sarif.NewRunWithInformationURI(ScaScannerToolName, utils.BaseDocumentationURL+"sca") run.Tool.Driver.Version = &sc.xrayVersion @@ -129,18 +178,31 @@ func (sc *CmdResultsSarifConverter) validateBeforeParse() (err error) { if sc.current == nil { return results.ErrResetConvertor } - if (sc.hasViolationContext || sc.includeVulnerabilities) && sc.scaCurrentRun == nil { + if sc.currentTargetConvertedRuns == nil { return results.ErrNoTargetConvertor } return } -func (sc *CmdResultsSarifConverter) ParseViolations(target results.ScanTarget, scanResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { - if err = sc.validateBeforeParse(); err != nil || sc.scaCurrentRun == nil { +func (sc *CmdResultsSarifConverter) ParseScaIssues(target results.ScanTarget, violations bool, scaResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { + if violations { + if err = sc.parseScaViolations(target, scaResponse, applicableScan...); err != nil { + return + } + return + } + if err = sc.parseScaVulnerabilities(target, scaResponse, applicableScan...); err != nil { + return + } + return +} + +func (sc *CmdResultsSarifConverter) parseScaViolations(target results.ScanTarget, scanResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { + if err = sc.validateBeforeParse(); err != nil || sc.currentTargetConvertedRuns.scaCurrentRun == nil { return } // Parse violations - sarifResults, sarifRules, err := PrepareSarifScaViolations(sc.currentCmdType, target, scanResponse.Violations, sc.entitledForJas, applicabilityRuns...) + sarifResults, sarifRules, err := PrepareSarifScaViolations(sc.currentCmdType, target, scanResponse.Scan.Violations, sc.entitledForJas, results.ScanResultsToRuns(applicableScan)...) if err != nil || len(sarifRules) == 0 || len(sarifResults) == 0 { return } @@ -148,11 +210,11 @@ func (sc *CmdResultsSarifConverter) ParseViolations(target results.ScanTarget, s return } -func (sc *CmdResultsSarifConverter) ParseVulnerabilities(target results.ScanTarget, scanResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { - if err = sc.validateBeforeParse(); err != nil || sc.scaCurrentRun == nil { +func (sc *CmdResultsSarifConverter) parseScaVulnerabilities(target results.ScanTarget, scanResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { + if err = sc.validateBeforeParse(); err != nil || sc.currentTargetConvertedRuns.scaCurrentRun == nil { return } - sarifResults, sarifRules, err := PrepareSarifScaVulnerabilities(sc.currentCmdType, target, scanResponse.Vulnerabilities, sc.entitledForJas, applicabilityRuns...) + sarifResults, sarifRules, err := PrepareSarifScaVulnerabilities(sc.currentCmdType, target, scanResponse.Scan.Vulnerabilities, sc.entitledForJas, results.ScanResultsToRuns(applicableScan)...) if err != nil || len(sarifRules) == 0 || len(sarifResults) == 0 { return } @@ -160,58 +222,73 @@ func (sc *CmdResultsSarifConverter) ParseVulnerabilities(target results.ScanTarg return } -func (sc *CmdResultsSarifConverter) ParseLicenses(target results.ScanTarget, licenses []services.License) (err error) { +func (sc *CmdResultsSarifConverter) ParseLicenses(_ results.ScanTarget, _ results.ScanResult[services.ScanResponse]) (err error) { // Not supported in Sarif format return } -func (sc *CmdResultsSarifConverter) ParseSecrets(target results.ScanTarget, secrets ...*sarif.Run) (err error) { - if !sc.entitledForJas { +func (sc *CmdResultsSarifConverter) ParseSecrets(target results.ScanTarget, violations bool, secrets []results.ScanResult[[]*sarif.Run]) (err error) { + if err = sc.validateBeforeParse(); err != nil || !sc.entitledForJas { return } - if sc.current == nil { - return results.ErrResetConvertor - } - sc.current.Runs = append(sc.current.Runs, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.SecretsScan, sc.patchBinaryPaths, target, secrets...)...) + sc.currentTargetConvertedRuns.secretsCurrentRun = combineJasRunsToCurrentRun(sc.currentTargetConvertedRuns.secretsCurrentRun, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.SecretsScan, sc.patchBinaryPaths, violations, target, results.ScanResultsToRuns(secrets)...)...) return } -func (sc *CmdResultsSarifConverter) ParseIacs(target results.ScanTarget, iacs ...*sarif.Run) (err error) { - if !sc.entitledForJas { +func (sc *CmdResultsSarifConverter) ParseIacs(target results.ScanTarget, violations bool, iacs []results.ScanResult[[]*sarif.Run]) (err error) { + if err = sc.validateBeforeParse(); err != nil || !sc.entitledForJas { return } - if sc.current == nil { - return results.ErrResetConvertor - } - sc.current.Runs = append(sc.current.Runs, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.IacScan, sc.patchBinaryPaths, target, iacs...)...) + sc.currentTargetConvertedRuns.iacCurrentRun = combineJasRunsToCurrentRun(sc.currentTargetConvertedRuns.iacCurrentRun, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.IacScan, sc.patchBinaryPaths, violations, target, results.ScanResultsToRuns(iacs)...)...) return } -func (sc *CmdResultsSarifConverter) ParseSast(target results.ScanTarget, sast ...*sarif.Run) (err error) { - if !sc.entitledForJas { +func (sc *CmdResultsSarifConverter) ParseSast(target results.ScanTarget, violations bool, sast []results.ScanResult[[]*sarif.Run]) (err error) { + if err = sc.validateBeforeParse(); err != nil || !sc.entitledForJas { return } - if sc.current == nil { - return results.ErrResetConvertor - } - sc.current.Runs = append(sc.current.Runs, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.SastScan, sc.patchBinaryPaths, target, sast...)...) + sc.currentTargetConvertedRuns.sastCurrentRun = combineJasRunsToCurrentRun(sc.currentTargetConvertedRuns.sastCurrentRun, patchRunsToPassIngestionRules(sc.baseJfrogUrl, sc.currentCmdType, utils.SastScan, sc.patchBinaryPaths, violations, target, results.ScanResultsToRuns(sast)...)...) return } func (sc *CmdResultsSarifConverter) addScaResultsToCurrentRun(rules map[string]*sarif.ReportingDescriptor, results ...*sarif.Result) { for _, rule := range rules { // This method will add the rule only if it doesn't exist - sc.scaCurrentRun.Tool.Driver.AddRule(rule) + sc.currentTargetConvertedRuns.scaCurrentRun.Tool.Driver.AddRule(rule) } for _, result := range results { - sc.scaCurrentRun.AddResult(result) + sc.currentTargetConvertedRuns.scaCurrentRun.AddResult(result) + } +} + +// For JAS scanners results we get a separate runs for vulnerabilities and violations, we need to combine them to a single run +// This allows us to have a single run for each scan type in the SARIF report for the ingestion rules and the users to view +func combineJasRunsToCurrentRun(destination *sarif.Run, runs ...*sarif.Run) *sarif.Run { + for _, run := range runs { + if destination == nil { + // First run, set as the destination + destination = run + continue + } else if destination.Tool.Driver.Name != run.Tool.Driver.Name { + log.Warn(fmt.Sprintf("Skipping JAS run (%s) as it doesn't match the current run (%s)", run.Tool.Driver.Name, destination.Tool.Driver.Name)) + continue + } + // Combine the rules and results of the run to the destination + for _, rule := range run.Tool.Driver.Rules { + // This method will add the rule only if it doesn't exist + destination.Tool.Driver.AddRule(rule) + } + for _, result := range run.Results { + destination.AddResult(result) + } } + return destination } func PrepareSarifScaViolations(cmdType utils.CommandType, target results.ScanTarget, violations []services.Violation, entitledForJas bool, applicabilityRuns ...*sarif.Run) ([]*sarif.Result, map[string]*sarif.ReportingDescriptor, error) { sarifResults := []*sarif.Result{} rules := map[string]*sarif.ReportingDescriptor{} - _, _, err := results.PrepareScaViolations( + _, _, err := results.ApplyHandlerToScaViolations( target, violations, entitledForJas, @@ -227,7 +304,7 @@ func PrepareSarifScaViolations(cmdType utils.CommandType, target results.ScanTar func PrepareSarifScaVulnerabilities(cmdType utils.CommandType, target results.ScanTarget, vulnerabilities []services.Vulnerability, entitledForJas bool, applicabilityRuns ...*sarif.Run) ([]*sarif.Result, map[string]*sarif.ReportingDescriptor, error) { sarifResults := []*sarif.Result{} rules := map[string]*sarif.ReportingDescriptor{} - err := results.PrepareScaVulnerabilities( + err := results.ApplyHandlerToScaVulnerabilities( target, vulnerabilities, entitledForJas, @@ -247,7 +324,21 @@ func addSarifScaVulnerability(cmdType utils.CommandType, sarifResults *[]*sarif. if err != nil { return err } - currentResults, currentRule := parseScaToSarifFormat(cmdType, vulnerability.IssueId, vulnerability.Summary, markdownDescription, maxCveScore, getScaVulnerabilitySarifHeadline, cves, severity, applicabilityStatus, impactedPackagesName, impactedPackagesVersion, fixedVersions, directComponents) + currentResults, currentRule := parseScaToSarifFormat(scaParseParams{ + CmdType: cmdType, + IssueId: vulnerability.IssueId, + Summary: vulnerability.Summary, + MarkdownDescription: markdownDescription, + CveScore: maxCveScore, + GenerateTitleFunc: getScaVulnerabilitySarifHeadline, + Cves: cves, + Severity: severity, + ApplicabilityStatus: applicabilityStatus, + ImpactedPackagesName: impactedPackagesName, + ImpactedPackagesVersion: impactedPackagesVersion, + FixedVersions: fixedVersions, + DirectComponents: directComponents, + }) cveImpactedComponentRuleId := results.GetScaIssueId(impactedPackagesName, impactedPackagesVersion, results.GetIssueIdentifier(cves, vulnerability.IssueId, "_")) if _, ok := (*rules)[cveImpactedComponentRuleId]; !ok { // New Rule @@ -268,7 +359,26 @@ func addSarifScaSecurityViolation(cmdType utils.CommandType, sarifResults *[]*sa if err != nil { return err } - currentResults, currentRule := parseScaToSarifFormat(cmdType, violation.IssueId, violation.Summary, markdownDescription, maxCveScore, getScaSecurityViolationSarifHeadline, cves, severity, applicabilityStatus, impactedPackagesName, impactedPackagesVersion, fixedVersions, directComponents, violation.WatchName) + currentResults, currentRule := parseScaToSarifFormat(scaParseParams{ + CmdType: cmdType, + IssueId: violation.IssueId, + Watch: violation.WatchName, + Summary: violation.Summary, + MarkdownDescription: markdownDescription, + CveScore: maxCveScore, + GenerateTitleFunc: getScaSecurityViolationSarifHeadline, + Cves: cves, + Severity: severity, + ApplicabilityStatus: applicabilityStatus, + ImpactedPackagesName: impactedPackagesName, + ImpactedPackagesVersion: impactedPackagesVersion, + FixedVersions: fixedVersions, + DirectComponents: directComponents, + Violation: &violationContext{ + Watch: violation.WatchName, + Policies: results.ConvertPolicesToString(violation.Policies), + }, + }) cveImpactedComponentRuleId := results.GetScaIssueId(impactedPackagesName, impactedPackagesVersion, results.GetIssueIdentifier(cves, violation.IssueId, "_")) if _, ok := (*rules)[cveImpactedComponentRuleId]; !ok { // New Rule @@ -289,7 +399,26 @@ func addSarifScaLicenseViolation(cmdType utils.CommandType, sarifResults *[]*sar if err != nil { return err } - currentResults, currentRule := parseScaToSarifFormat(cmdType, violation.LicenseKey, getLicenseViolationSummary(impactedPackagesName, impactedPackagesVersion, violation.LicenseKey), markdownDescription, maxCveScore, getXrayLicenseSarifHeadline, cves, severity, applicabilityStatus, impactedPackagesName, impactedPackagesVersion, fixedVersions, directComponents) + currentResults, currentRule := parseScaToSarifFormat(scaParseParams{ + CmdType: cmdType, + Watch: violation.WatchName, + IssueId: violation.LicenseKey, + Summary: getLicenseViolationSummary(impactedPackagesName, impactedPackagesVersion, violation.LicenseKey), + MarkdownDescription: markdownDescription, + CveScore: maxCveScore, + GenerateTitleFunc: getXrayLicenseSarifHeadline, + Cves: cves, + Severity: severity, + ApplicabilityStatus: applicabilityStatus, + ImpactedPackagesName: impactedPackagesName, + ImpactedPackagesVersion: impactedPackagesVersion, + FixedVersions: fixedVersions, + DirectComponents: directComponents, + Violation: &violationContext{ + Watch: violation.WatchName, + Policies: results.ConvertPolicesToString(violation.Policies), + }, + }) cveImpactedComponentRuleId := results.GetScaIssueId(impactedPackagesName, impactedPackagesVersion, results.GetIssueIdentifier(cves, violation.LicenseKey, "_")) if _, ok := (*rules)[cveImpactedComponentRuleId]; !ok { // New Rule @@ -300,36 +429,42 @@ func addSarifScaLicenseViolation(cmdType utils.CommandType, sarifResults *[]*sar } } -func parseScaToSarifFormat(cmdType utils.CommandType, xrayId, summary, markdownDescription, cveScore string, generateTitleFunc func(depName string, version string, issueId string) string, cves []formats.CveRow, severity severityutils.Severity, applicabilityStatus jasutils.ApplicabilityStatus, impactedPackagesName, impactedPackagesVersion string, fixedVersions []string, directComponents []formats.ComponentRow, watches ...string) (sarifResults []*sarif.Result, rule *sarif.ReportingDescriptor) { +func parseScaToSarifFormat(params scaParseParams) (sarifResults []*sarif.Result, rule *sarif.ReportingDescriptor) { // General information - issueId := results.GetIssueIdentifier(cves, xrayId, "_") - cveImpactedComponentRuleId := results.GetScaIssueId(impactedPackagesName, impactedPackagesVersion, issueId) - level := severityutils.SeverityToSarifSeverityLevel(severity) + issueId := results.GetIssueIdentifier(params.Cves, params.IssueId, "_") + cveImpactedComponentRuleId := results.GetScaIssueId(params.ImpactedPackagesName, params.ImpactedPackagesVersion, issueId) + level := severityutils.SeverityToSarifSeverityLevel(params.Severity) // Add rule for the cve if not exists rule = getScaIssueSarifRule( cveImpactedComponentRuleId, - generateTitleFunc(impactedPackagesName, impactedPackagesVersion, issueId), - cveScore, - summary, - markdownDescription, + params.GenerateTitleFunc(params.ImpactedPackagesName, params.ImpactedPackagesVersion, issueId, params.Watch), + params.CveScore, + params.Summary, + params.MarkdownDescription, ) - for _, directDependency := range directComponents { + for _, directDependency := range params.DirectComponents { // Create result for each direct dependency issueResult := sarif.NewRuleResult(cveImpactedComponentRuleId). - WithMessage(sarif.NewTextMessage(generateTitleFunc(directDependency.Name, directDependency.Version, issueId))). + WithMessage(sarif.NewTextMessage(params.GenerateTitleFunc(directDependency.Name, directDependency.Version, issueId, params.Watch))). WithLevel(level.String()) // Add properties resultsProperties := sarif.NewPropertyBag() - if applicabilityStatus != jasutils.NotScanned { - resultsProperties.Add(jasutils.ApplicabilitySarifPropertyKey, applicabilityStatus.String()) + if params.ApplicabilityStatus != jasutils.NotScanned { + resultsProperties.Add(jasutils.ApplicabilitySarifPropertyKey, params.ApplicabilityStatus.String()) } - if len(watches) > 0 { - resultsProperties.Add(WatchSarifPropertyKey, strings.Join(watches, ", ")) + if params.Violation != nil { + // Add violation context + if params.Violation.Watch != "" { + resultsProperties.Add(sarifutils.WatchSarifPropertyKey, params.Violation.Watch) + } + if len(params.Violation.Policies) > 0 { + resultsProperties.Add(sarifutils.PoliciesSarifPropertyKey, strings.Join(params.Violation.Policies, ",")) + } } - resultsProperties.Add(FixedVersionSarifPropertyKey, getFixedVersionString(fixedVersions)) + resultsProperties.Add(fixedVersionSarifPropertyKey, getFixedVersionString(params.FixedVersions)) issueResult.AttachPropertyBag(resultsProperties) // Add location - issueLocation := getComponentSarifLocation(cmdType, directDependency) + issueLocation := getComponentSarifLocation(params.CmdType, directDependency) if issueLocation != nil { issueResult.AddLocation(issueLocation) } @@ -403,16 +538,30 @@ func getDirectDependenciesFormatted(directDependencies []formats.ComponentRow) ( return strings.TrimSuffix(formattedDirectDependencies.String(), "
"), nil } -func getScaVulnerabilitySarifHeadline(depName, version, issueId string) string { - return fmt.Sprintf("[%s] %s %s", issueId, depName, version) +func getScaVulnerabilitySarifHeadline(depName, version, issueId, watch string) string { + headline := fmt.Sprintf("[%s] %s %s", issueId, depName, version) + if watch != "" { + headline = fmt.Sprintf("%s (%s)", headline, watch) + } + return headline } -func getScaSecurityViolationSarifHeadline(depName, version, key string) string { - return fmt.Sprintf("Security violation %s", getScaVulnerabilitySarifHeadline(depName, version, key)) +func getScaSecurityViolationSarifHeadline(depName, version, key, watch string) string { + headline := getScaVulnerabilitySarifHeadline(depName, version, key, watch) + if watch == "" { + return fmt.Sprintf("Security Violation %s", headline) + } + return headline } -func getXrayLicenseSarifHeadline(depName, version, key string) string { - return fmt.Sprintf("License violation [%s] in %s %s", key, depName, version) +func getXrayLicenseSarifHeadline(depName, version, key, watch string) string { + headline := fmt.Sprintf("[%s] in %s %s", key, depName, version) + if watch != "" { + headline = fmt.Sprintf("%s (%s)", headline, watch) + } else { + headline = fmt.Sprintf("License violation %s", headline) + } + return headline } func getLicenseViolationSummary(depName, version, key string) string { @@ -427,7 +576,7 @@ func getScaLicenseViolationMarkdown(depName, version, key string, directDependen return fmt.Sprintf("%s
Direct dependencies:
%s", getLicenseViolationSummary(depName, version, key), formattedDirectDependencies), nil } -func patchRunsToPassIngestionRules(baseJfrogUrl string, cmdType utils.CommandType, subScanType utils.SubScanType, patchBinaryPaths bool, target results.ScanTarget, runs ...*sarif.Run) []*sarif.Run { +func patchRunsToPassIngestionRules(baseJfrogUrl string, cmdType utils.CommandType, subScanType utils.SubScanType, patchBinaryPaths, isJasViolations bool, target results.ScanTarget, runs ...*sarif.Run) []*sarif.Run { patchedRuns := []*sarif.Run{} // Patch changes may alter the original run, so we will create a new run for each for _, run := range runs { @@ -440,9 +589,9 @@ func patchRunsToPassIngestionRules(baseJfrogUrl string, cmdType utils.CommandTyp sarifutils.SetRunToolName(BinarySecretScannerToolName, patched) } if patched.Tool.Driver != nil { - patched.Tool.Driver.Rules = patchRules(baseJfrogUrl, cmdType, subScanType, patched.Tool.Driver.Rules...) + patched.Tool.Driver.Rules = patchRules(baseJfrogUrl, cmdType, subScanType, isJasViolations, patched.Tool.Driver.Rules...) } - patched.Results = patchResults(cmdType, subScanType, patchBinaryPaths, target, patched, patched.Results...) + patched.Results = patchResults(cmdType, subScanType, patchBinaryPaths, isJasViolations, target, patched, patched.Results...) patchedRuns = append(patchedRuns, patched) } return patchedRuns @@ -478,7 +627,7 @@ func patchDockerSecretLocations(result *sarif.Result) { } } -func patchRules(platformBaseUrl string, commandType utils.CommandType, subScanType utils.SubScanType, rules ...*sarif.ReportingDescriptor) (patched []*sarif.ReportingDescriptor) { +func patchRules(platformBaseUrl string, commandType utils.CommandType, subScanType utils.SubScanType, isViolations bool, rules ...*sarif.ReportingDescriptor) (patched []*sarif.ReportingDescriptor) { patched = []*sarif.ReportingDescriptor{} for _, rule := range rules { if rule.Name != nil && rule.ID == *rule.Name { @@ -489,6 +638,10 @@ func patchRules(platformBaseUrl string, commandType utils.CommandType, subScanTy // Patch the rule name in case of binary scan sarifutils.SetRuleShortDescriptionText(fmt.Sprintf("[Secret in Binary found] %s", sarifutils.GetRuleShortDescriptionText(rule)), rule) } + if isViolations && subScanType != utils.ScaScan { + // Add prefix to the rule description for violations + sarifutils.SetRuleShortDescriptionText(fmt.Sprintf("[Security Violation] %s", sarifutils.GetRuleShortDescriptionText(rule)), rule) + } if rule.Help == nil { // Github code scanning ingestion rules rejects rules without help content. // Patch by transferring the full description to the help field. @@ -503,7 +656,7 @@ func patchRules(platformBaseUrl string, commandType utils.CommandType, subScanTy return } -func patchResults(commandType utils.CommandType, subScanType utils.SubScanType, patchBinaryPaths bool, target results.ScanTarget, run *sarif.Run, results ...*sarif.Result) (patched []*sarif.Result) { +func patchResults(commandType utils.CommandType, subScanType utils.SubScanType, patchBinaryPaths, isJasViolations bool, target results.ScanTarget, run *sarif.Run, results ...*sarif.Result) (patched []*sarif.Result) { patched = []*sarif.Result{} for _, result := range results { if len(result.Locations) == 0 { @@ -512,14 +665,8 @@ func patchResults(commandType utils.CommandType, subScanType utils.SubScanType, log.Debug(fmt.Sprintf("[%s] Removing result [ruleId=%s] without locations: %s", subScanType.String(), sarifutils.GetResultRuleId(result), sarifutils.GetResultMsgText(result))) continue } + patchResultMsg(result, target, commandType, subScanType, isJasViolations) if commandType.IsTargetBinary() { - var markdown string - if subScanType == utils.SecretsScan { - markdown = getSecretInBinaryMarkdownMsg(commandType, target, result) - } else { - markdown = getScaInBinaryMarkdownMsg(commandType, target, result) - } - sarifutils.SetResultMsgMarkdown(markdown, result) if patchBinaryPaths { // For Binary scans, override the physical location if applicable (after data already used for markdown) result = convertBinaryPhysicalLocations(commandType, run, result) @@ -536,6 +683,28 @@ func patchResults(commandType utils.CommandType, subScanType utils.SubScanType, return patched } +func patchResultMsg(result *sarif.Result, target results.ScanTarget, commandType utils.CommandType, subScanType utils.SubScanType, isJasViolations bool) { + if commandType.IsTargetBinary() { + var markdown string + if subScanType == utils.SecretsScan { + markdown = getSecretInBinaryMarkdownMsg(commandType, target, result) + } else { + markdown = getScaInBinaryMarkdownMsg(commandType, target, result) + } + sarifutils.SetResultMsgMarkdown(markdown, result) + } + // Patch markdown + markdown := sarifutils.GetResultMsgMarkdown(result) + if markdown == "" { + markdown = sarifutils.GetResultMsgText(result) + } + if isJasViolations { + // Add prefix to the rule description for violations + markdown = fmt.Sprintf("Security Violation %s", markdown) + } + sarifutils.SetResultMsgMarkdown(markdown, result) +} + // This method may need to replace the physical location if applicable, to avoid override on the existing object we will return a new object if changed func convertBinaryPhysicalLocations(commandType utils.CommandType, run *sarif.Run, result *sarif.Result) *sarif.Result { if patchedLocation := getPatchedBinaryLocation(commandType, run); patchedLocation != "" { @@ -741,7 +910,7 @@ func calculateResultFingerprints(resultType utils.CommandType, run *sarif.Run, r if !resultType.IsTargetBinary() { return nil } - ids := []string{sarifutils.GetRunToolName(run), sarifutils.GetResultRuleId(result), getResultWatches(result)} + ids := []string{sarifutils.GetRunToolName(run), sarifutils.GetResultRuleId(result), sarifutils.GetResultWatches(result)} for _, location := range sarifutils.GetResultFileLocations(result) { ids = append(ids, strings.ReplaceAll(location, string(filepath.Separator), "/")) } @@ -755,15 +924,6 @@ func calculateResultFingerprints(resultType utils.CommandType, run *sarif.Run, r return nil } -func getResultWatches(result *sarif.Result) (watches string) { - if watchesProperty, ok := result.Properties[WatchSarifPropertyKey]; ok { - if watchesValue, ok := watchesProperty.(string); ok { - return watchesValue - } - } - return -} - // This method returns an image tag of invisible image that is used to track some parameters. // It will send a count as soon as the page with it is logged. func getAnalyticsHiddenPixel(baseUrl string, resultOfSubScan utils.SubScanType) string { diff --git a/utils/results/conversion/sarifparser/sarifparser_test.go b/utils/results/conversion/sarifparser/sarifparser_test.go index 21158f27..498302db 100644 --- a/utils/results/conversion/sarifparser/sarifparser_test.go +++ b/utils/results/conversion/sarifparser/sarifparser_test.go @@ -65,18 +65,24 @@ func TestGetComponentSarifLocation(t *testing.T) { } func TestGetVulnerabilityOrViolationSarifHeadline(t *testing.T) { - assert.Equal(t, "[CVE-2022-1234] loadsh 1.4.1", getScaVulnerabilitySarifHeadline("loadsh", "1.4.1", "CVE-2022-1234")) - assert.NotEqual(t, "[CVE-2022-1234] comp 1.4.1", getScaVulnerabilitySarifHeadline("comp", "1.2.1", "CVE-2022-1234")) + assert.Equal(t, "[CVE-2022-1234] loadsh 1.4.1", getScaVulnerabilitySarifHeadline("loadsh", "1.4.1", "CVE-2022-1234", "")) + assert.NotEqual(t, "[CVE-2022-1234] comp 1.4.1", getScaVulnerabilitySarifHeadline("comp", "1.2.1", "CVE-2022-1234", "")) + assert.Equal(t, "[CVE-2022-1234] loadsh 1.4.1 (watch)", getScaVulnerabilitySarifHeadline("loadsh", "1.4.1", "CVE-2022-1234", "watch")) + assert.NotEqual(t, "[CVE-2022-1234] comp 1.4.1", getScaVulnerabilitySarifHeadline("comp", "1.2.1", "CVE-2022-1234", "watch")) } func TestGetScaSecurityViolationSarifHeadline(t *testing.T) { - assert.Equal(t, "Security violation [CVE-2022-1234] loadsh 1.4.1", getScaSecurityViolationSarifHeadline("loadsh", "1.4.1", "CVE-2022-1234")) - assert.NotEqual(t, "[CVE-2022-1234] comp 1.2.1", getScaSecurityViolationSarifHeadline("comp", "1.2.1", "CVE-2022-1234")) + assert.Equal(t, "Security Violation [CVE-2022-1234] loadsh 1.4.1", getScaSecurityViolationSarifHeadline("loadsh", "1.4.1", "CVE-2022-1234", "")) + assert.NotEqual(t, "[CVE-2022-1234] comp 1.2.1", getScaSecurityViolationSarifHeadline("comp", "1.2.1", "CVE-2022-1234", "")) + assert.Equal(t, "[CVE-2022-1234] loadsh 1.4.1 (watch)", getScaSecurityViolationSarifHeadline("loadsh", "1.4.1", "CVE-2022-1234", "watch")) + assert.NotEqual(t, "[CVE-2022-1234] comp 1.2.1", getScaSecurityViolationSarifHeadline("comp", "1.2.1", "CVE-2022-1234", "watch")) } func TestGetXrayLicenseSarifHeadline(t *testing.T) { - assert.Equal(t, "License violation [MIT] in loadsh 1.4.1", getXrayLicenseSarifHeadline("loadsh", "1.4.1", "MIT")) - assert.NotEqual(t, "License violation [] in comp 1.2.1", getXrayLicenseSarifHeadline("comp", "1.2.1", "MIT")) + assert.Equal(t, "License violation [MIT] in loadsh 1.4.1", getXrayLicenseSarifHeadline("loadsh", "1.4.1", "MIT", "")) + assert.NotEqual(t, "License violation [] in comp 1.2.1", getXrayLicenseSarifHeadline("comp", "1.2.1", "MIT", "")) + assert.Equal(t, "[MIT] in loadsh 1.4.1 (watch)", getXrayLicenseSarifHeadline("loadsh", "1.4.1", "MIT", "watch")) + assert.NotEqual(t, "License violation [] in comp 1.2.1", getXrayLicenseSarifHeadline("comp", "1.2.1", "MIT", "watch")) } func TestGetLicenseViolationSummary(t *testing.T) { @@ -300,6 +306,7 @@ func TestPatchRunsToPassIngestionRules(t *testing.T) { subScan utils.SubScanType withEnvVars bool withDockerfile bool + isJasViolations bool input []*sarif.Run expectedResults []*sarif.Run }{ @@ -490,6 +497,23 @@ func TestPatchRunsToPassIngestionRules(t *testing.T) { ), }, }, + { + name: "Audit scan - Secrets violations", + target: results.ScanTarget{Target: wd}, + cmdType: utils.SourceCode, + subScan: utils.SecretsScan, + isJasViolations: true, + input: []*sarif.Run{ + sarifutils.CreateRunWithDummyResultsInWd(wd, + sarifutils.CreateDummyResultInPath(fmt.Sprintf("file://%s", filepath.Join(wd, "dir", "file"))), + ), + }, + expectedResults: []*sarif.Run{ + sarifutils.CreateRunWithDummyResultsWithRuleInformation("", "[Security Violation] ", "rule-msg", "rule-markdown", "rule-msg", "rule-markdown", wd, + sarifutils.CreateDummyResult("Security Violation result-markdown", "result-msg", "rule", "level", sarifutils.CreateDummyLocationInPath(filepath.Join("dir", "file"))), + ), + }, + }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -515,7 +539,7 @@ func TestPatchRunsToPassIngestionRules(t *testing.T) { revertWd := clientTests.ChangeDirWithCallback(t, wd, dockerfileDir) defer revertWd() } - patchedRuns := patchRunsToPassIngestionRules("url/", tc.cmdType, tc.subScan, true, tc.target, tc.input...) + patchedRuns := patchRunsToPassIngestionRules("url/", tc.cmdType, tc.subScan, true, tc.isJasViolations, tc.target, tc.input...) assert.ElementsMatch(t, tc.expectedResults, patchedRuns) }) } diff --git a/utils/results/conversion/simplejsonparser/simplejsonparser.go b/utils/results/conversion/simplejsonparser/simplejsonparser.go index 311caeb3..d2ac34b7 100644 --- a/utils/results/conversion/simplejsonparser/simplejsonparser.go +++ b/utils/results/conversion/simplejsonparser/simplejsonparser.go @@ -65,7 +65,35 @@ func (sjc *CmdResultsSimpleJsonConverter) ParseNewTargetResults(target results.S return } -func (sjc *CmdResultsSimpleJsonConverter) ParseViolations(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { +// We only care to update the status if it's the first time we see it or if status is 0 (completed) and the new status is not (failed) +func shouldUpdateStatus(currentStatus, newStatus *int) bool { + if currentStatus == nil || (*currentStatus == 0 && newStatus != nil) { + return true + } + return false +} + +func (sjc *CmdResultsSimpleJsonConverter) ParseScaIssues(target results.ScanTarget, violations bool, scaResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { + if sjc.current == nil { + return results.ErrResetConvertor + } + if shouldUpdateStatus(sjc.current.Statuses.ScaStatusCode, &scaResponse.StatusCode) { + sjc.current.Statuses.ScaStatusCode = &scaResponse.StatusCode + } + for i := range applicableScan { + if shouldUpdateStatus(sjc.current.Statuses.ApplicabilityStatusCode, &applicableScan[i].StatusCode) { + sjc.current.Statuses.ApplicabilityStatusCode = &applicableScan[i].StatusCode + } + } + if violations { + err = sjc.parseScaViolations(target, scaResponse.Scan, results.ScanResultsToRuns(applicableScan)...) + } else { + err = sjc.parseScaVulnerabilities(target, scaResponse.Scan, results.ScanResultsToRuns(applicableScan)...) + } + return +} + +func (sjc *CmdResultsSimpleJsonConverter) parseScaViolations(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { if sjc.current == nil { return results.ErrResetConvertor } @@ -79,7 +107,7 @@ func (sjc *CmdResultsSimpleJsonConverter) ParseViolations(target results.ScanTar return } -func (sjc *CmdResultsSimpleJsonConverter) ParseVulnerabilities(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { +func (sjc *CmdResultsSimpleJsonConverter) parseScaVulnerabilities(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { if sjc.current == nil { return results.ErrResetConvertor } @@ -91,11 +119,14 @@ func (sjc *CmdResultsSimpleJsonConverter) ParseVulnerabilities(target results.Sc return } -func (sjc *CmdResultsSimpleJsonConverter) ParseLicenses(target results.ScanTarget, licenses []services.License) (err error) { +func (sjc *CmdResultsSimpleJsonConverter) ParseLicenses(target results.ScanTarget, scaResponse results.ScanResult[services.ScanResponse]) (err error) { if sjc.current == nil { return results.ErrResetConvertor } - licSimpleJson, err := PrepareSimpleJsonLicenses(target, licenses) + if sjc.current.Statuses.ScaStatusCode == nil || *sjc.current.Statuses.ScaStatusCode == 0 { + sjc.current.Statuses.ScaStatusCode = &scaResponse.StatusCode + } + licSimpleJson, err := PrepareSimpleJsonLicenses(target, scaResponse.Scan.Licenses) if err != nil || len(licSimpleJson) == 0 { return } @@ -103,61 +134,88 @@ func (sjc *CmdResultsSimpleJsonConverter) ParseLicenses(target results.ScanTarge return } -func (sjc *CmdResultsSimpleJsonConverter) ParseSecrets(_ results.ScanTarget, secrets ...*sarif.Run) (err error) { +func (sjc *CmdResultsSimpleJsonConverter) ParseSecrets(_ results.ScanTarget, isViolationsResults bool, secrets []results.ScanResult[[]*sarif.Run]) (err error) { if !sjc.entitledForJas { return } if sjc.current == nil { return results.ErrResetConvertor } - secretsSimpleJson, err := PrepareSimpleJsonJasIssues(sjc.entitledForJas, sjc.pretty, secrets...) + for i := range secrets { + if shouldUpdateStatus(sjc.current.Statuses.SecretsStatusCode, &secrets[i].StatusCode) { + sjc.current.Statuses.SecretsStatusCode = &secrets[i].StatusCode + } + } + secretsSimpleJson, err := PrepareSimpleJsonJasIssues(sjc.entitledForJas, sjc.pretty, results.ScanResultsToRuns(secrets)...) if err != nil || len(secretsSimpleJson) == 0 { return } - sjc.current.Secrets = append(sjc.current.Secrets, secretsSimpleJson...) + if isViolationsResults { + sjc.current.SecretsViolations = append(sjc.current.SecretsViolations, secretsSimpleJson...) + } else { + sjc.current.SecretsVulnerabilities = append(sjc.current.SecretsVulnerabilities, secretsSimpleJson...) + } return } -func (sjc *CmdResultsSimpleJsonConverter) ParseIacs(_ results.ScanTarget, iacs ...*sarif.Run) (err error) { +func (sjc *CmdResultsSimpleJsonConverter) ParseIacs(_ results.ScanTarget, isViolationsResults bool, iacs []results.ScanResult[[]*sarif.Run]) (err error) { if !sjc.entitledForJas { return } if sjc.current == nil { return results.ErrResetConvertor } - iacSimpleJson, err := PrepareSimpleJsonJasIssues(sjc.entitledForJas, sjc.pretty, iacs...) + for i := range iacs { + if shouldUpdateStatus(sjc.current.Statuses.IacStatusCode, &iacs[i].StatusCode) { + sjc.current.Statuses.IacStatusCode = &iacs[i].StatusCode + } + } + iacSimpleJson, err := PrepareSimpleJsonJasIssues(sjc.entitledForJas, sjc.pretty, results.ScanResultsToRuns(iacs)...) if err != nil || len(iacSimpleJson) == 0 { return } - sjc.current.Iacs = append(sjc.current.Iacs, iacSimpleJson...) + if isViolationsResults { + sjc.current.IacsViolations = append(sjc.current.IacsViolations, iacSimpleJson...) + } else { + sjc.current.IacsVulnerabilities = append(sjc.current.IacsVulnerabilities, iacSimpleJson...) + } return } -func (sjc *CmdResultsSimpleJsonConverter) ParseSast(_ results.ScanTarget, sast ...*sarif.Run) (err error) { +func (sjc *CmdResultsSimpleJsonConverter) ParseSast(_ results.ScanTarget, isViolationsResults bool, sast []results.ScanResult[[]*sarif.Run]) (err error) { if !sjc.entitledForJas { return } if sjc.current == nil { return results.ErrResetConvertor } - sastSimpleJson, err := PrepareSimpleJsonJasIssues(sjc.entitledForJas, sjc.pretty, sast...) + for i := range sast { + if shouldUpdateStatus(sjc.current.Statuses.SastStatusCode, &sast[i].StatusCode) { + sjc.current.Statuses.SastStatusCode = &sast[i].StatusCode + } + } + sastSimpleJson, err := PrepareSimpleJsonJasIssues(sjc.entitledForJas, sjc.pretty, results.ScanResultsToRuns(sast)...) if err != nil || len(sastSimpleJson) == 0 { return } - sjc.current.Sast = append(sjc.current.Sast, sastSimpleJson...) + if isViolationsResults { + sjc.current.SastViolations = append(sjc.current.SastViolations, sastSimpleJson...) + } else { + sjc.current.SastVulnerabilities = append(sjc.current.SastVulnerabilities, sastSimpleJson...) + } return } -func PrepareSimpleJsonViolations(target results.ScanTarget, scaResponse services.ScanResponse, pretty, jasEntitled bool, applicabilityRuns ...*sarif.Run) ([]formats.VulnerabilityOrViolationRow, []formats.LicenseRow, []formats.OperationalRiskViolationRow, error) { +func PrepareSimpleJsonViolations(target results.ScanTarget, scaResponse services.ScanResponse, pretty, jasEntitled bool, applicabilityRuns ...*sarif.Run) ([]formats.VulnerabilityOrViolationRow, []formats.LicenseViolationRow, []formats.OperationalRiskViolationRow, error) { var securityViolationsRows []formats.VulnerabilityOrViolationRow - var licenseViolationsRows []formats.LicenseRow + var licenseViolationsRows []formats.LicenseViolationRow var operationalRiskViolationsRows []formats.OperationalRiskViolationRow - _, _, err := results.PrepareScaViolations( + _, _, err := results.ApplyHandlerToScaViolations( target, scaResponse.Violations, jasEntitled, applicabilityRuns, - addSimpleJsonSecurityViolation(&securityViolationsRows, pretty), + addSimpleJsonSecurityViolation(target, &securityViolationsRows, pretty), addSimpleJsonLicenseViolation(&licenseViolationsRows, pretty), addSimpleJsonOperationalRiskViolation(&operationalRiskViolationsRows, pretty), ) @@ -166,18 +224,22 @@ func PrepareSimpleJsonViolations(target results.ScanTarget, scaResponse services func PrepareSimpleJsonVulnerabilities(target results.ScanTarget, scaResponse services.ScanResponse, pretty, entitledForJas bool, applicabilityRuns ...*sarif.Run) ([]formats.VulnerabilityOrViolationRow, error) { var vulnerabilitiesRows []formats.VulnerabilityOrViolationRow - err := results.PrepareScaVulnerabilities( + err := results.ApplyHandlerToScaVulnerabilities( target, scaResponse.Vulnerabilities, entitledForJas, applicabilityRuns, - addSimpleJsonVulnerability(&vulnerabilitiesRows, pretty), + addSimpleJsonVulnerability(target, &vulnerabilitiesRows, pretty), ) return vulnerabilitiesRows, err } -func addSimpleJsonVulnerability(vulnerabilitiesRows *[]formats.VulnerabilityOrViolationRow, pretty bool) results.ParseScaVulnerabilityFunc { +func addSimpleJsonVulnerability(target results.ScanTarget, vulnerabilitiesRows *[]formats.VulnerabilityOrViolationRow, pretty bool) results.ParseScaVulnerabilityFunc { return func(vulnerability services.Vulnerability, cves []formats.CveRow, applicabilityStatus jasutils.ApplicabilityStatus, severity severityutils.Severity, impactedPackagesName, impactedPackagesVersion, impactedPackagesType string, fixedVersion []string, directComponents []formats.ComponentRow, impactPaths [][]formats.ComponentRow) error { + tech := target.Technology + if tech == "" { + tech = techutils.Technology(impactedPackagesType) + } *vulnerabilitiesRows = append(*vulnerabilitiesRows, formats.VulnerabilityOrViolationRow{ Summary: vulnerability.Summary, @@ -194,7 +256,7 @@ func addSimpleJsonVulnerability(vulnerabilitiesRows *[]formats.VulnerabilityOrVi References: vulnerability.References, JfrogResearchInformation: convertJfrogResearchInformation(vulnerability.ExtendedInformation), ImpactPaths: impactPaths, - Technology: techutils.Technology(vulnerability.Technology), + Technology: tech, Applicable: applicabilityStatus.ToString(pretty), }, ) @@ -202,8 +264,12 @@ func addSimpleJsonVulnerability(vulnerabilitiesRows *[]formats.VulnerabilityOrVi } } -func addSimpleJsonSecurityViolation(securityViolationsRows *[]formats.VulnerabilityOrViolationRow, pretty bool) results.ParseScaViolationFunc { +func addSimpleJsonSecurityViolation(target results.ScanTarget, securityViolationsRows *[]formats.VulnerabilityOrViolationRow, pretty bool) results.ParseScaViolationFunc { return func(violation services.Violation, cves []formats.CveRow, applicabilityStatus jasutils.ApplicabilityStatus, severity severityutils.Severity, impactedPackagesName, impactedPackagesVersion, impactedPackagesType string, fixedVersion []string, directComponents []formats.ComponentRow, impactPaths [][]formats.ComponentRow) error { + tech := target.Technology + if tech == "" { + tech = techutils.Technology(impactedPackagesType) + } *securityViolationsRows = append(*securityViolationsRows, formats.VulnerabilityOrViolationRow{ Summary: violation.Summary, @@ -214,13 +280,17 @@ func addSimpleJsonSecurityViolation(securityViolationsRows *[]formats.Vulnerabil ImpactedDependencyType: impactedPackagesType, Components: directComponents, }, - FixedVersions: fixedVersion, - Cves: cves, + FixedVersions: fixedVersion, + Cves: cves, + ViolationContext: formats.ViolationContext{ + Watch: violation.WatchName, + Policies: results.ConvertPolicesToString(violation.Policies), + }, IssueId: violation.IssueId, References: violation.References, JfrogResearchInformation: convertJfrogResearchInformation(violation.ExtendedInformation), ImpactPaths: impactPaths, - Technology: techutils.Technology(violation.Technology), + Technology: tech, Applicable: applicabilityStatus.ToString(pretty), }, ) @@ -228,17 +298,24 @@ func addSimpleJsonSecurityViolation(securityViolationsRows *[]formats.Vulnerabil } } -func addSimpleJsonLicenseViolation(licenseViolationsRows *[]formats.LicenseRow, pretty bool) results.ParseScaViolationFunc { +func addSimpleJsonLicenseViolation(licenseViolationsRows *[]formats.LicenseViolationRow, pretty bool) results.ParseScaViolationFunc { return func(violation services.Violation, cves []formats.CveRow, applicabilityStatus jasutils.ApplicabilityStatus, severity severityutils.Severity, impactedPackagesName, impactedPackagesVersion, impactedPackagesType string, fixedVersion []string, directComponents []formats.ComponentRow, impactPaths [][]formats.ComponentRow) error { *licenseViolationsRows = append(*licenseViolationsRows, - formats.LicenseRow{ - LicenseKey: getLicenseKey(violation.LicenseKey, violation.IssueId), - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: severityutils.GetAsDetails(severity, applicabilityStatus, pretty), - ImpactedDependencyName: impactedPackagesName, - ImpactedDependencyVersion: impactedPackagesVersion, - ImpactedDependencyType: impactedPackagesType, - Components: directComponents, + formats.LicenseViolationRow{ + ViolationContext: formats.ViolationContext{ + Watch: violation.WatchName, + Policies: results.ConvertPolicesToString(violation.Policies), + }, + LicenseRow: formats.LicenseRow{ + LicenseKey: getLicenseKey(violation.LicenseKey, violation.IssueId), + LicenseName: violation.LicenseName, + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: severityutils.GetAsDetails(severity, applicabilityStatus, pretty), + ImpactedDependencyName: impactedPackagesName, + ImpactedDependencyVersion: impactedPackagesVersion, + ImpactedDependencyType: impactedPackagesType, + Components: directComponents, + }, }, }, ) @@ -258,6 +335,10 @@ func addSimpleJsonOperationalRiskViolation(operationalRiskViolationsRows *[]form violationOpRiskData := getOperationalRiskViolationReadableData(violation) for compIndex := 0; compIndex < len(impactedPackagesName); compIndex++ { operationalRiskViolationsRow := &formats.OperationalRiskViolationRow{ + ViolationContext: formats.ViolationContext{ + Watch: violation.WatchName, + Policies: results.ConvertPolicesToString(violation.Policies), + }, ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ SeverityDetails: severityutils.GetAsDetails(severity, applicabilityStatus, pretty), ImpactedDependencyName: impactedPackagesName, @@ -282,7 +363,7 @@ func addSimpleJsonOperationalRiskViolation(operationalRiskViolationsRows *[]form func PrepareSimpleJsonLicenses(target results.ScanTarget, licenses []services.License) ([]formats.LicenseRow, error) { var licensesRows []formats.LicenseRow - err := results.PrepareLicenses(target, licenses, addSimpleJsonLicense(&licensesRows)) + err := results.ApplyHandlerToLicenses(target, licenses, addSimpleJsonLicense(&licensesRows)) return licensesRows, err } @@ -306,17 +387,23 @@ func addSimpleJsonLicense(licenseViolationsRows *[]formats.LicenseRow) results.P func PrepareSimpleJsonJasIssues(entitledForJas, pretty bool, jasIssues ...*sarif.Run) ([]formats.SourceCodeRow, error) { var rows []formats.SourceCodeRow - err := results.PrepareJasIssues(jasIssues, entitledForJas, func(run *sarif.Run, rule *sarif.ReportingDescriptor, severity severityutils.Severity, result *sarif.Result, location *sarif.Location) error { - scannerDescription := "" - if rule != nil { - scannerDescription = sarifutils.GetRuleFullDescription(rule) - } + err := results.ApplyHandlerToJasIssues(jasIssues, entitledForJas, func(run *sarif.Run, rule *sarif.ReportingDescriptor, severity severityutils.Severity, result *sarif.Result, location *sarif.Location) error { rows = append(rows, formats.SourceCodeRow{ - SeverityDetails: severityutils.GetAsDetails(severity, jasutils.Applicable, pretty), - Finding: sarifutils.GetResultMsgText(result), - ScannerDescription: scannerDescription, - Fingerprint: sarifutils.GetResultFingerprint(result), + ScannerInfo: formats.ScannerInfo{ + RuleId: sarifutils.GetResultRuleId(result), + Cwe: sarifutils.GetRuleCWE(rule), + ScannerDescription: sarifutils.GetRuleFullDescription(rule), + ScannerShortDescription: sarifutils.GetRuleShortDescription(rule), + }, + ViolationContext: formats.ViolationContext{ + Watch: sarifutils.GetResultWatches(result), + IssueId: sarifutils.GetResultIssueId(result), + Policies: sarifutils.GetResultPolicies(result), + }, + SeverityDetails: severityutils.GetAsDetails(severity, jasutils.Applicable, pretty), + Finding: sarifutils.GetResultMsgText(result), + Fingerprint: sarifutils.GetResultFingerprint(result), Location: formats.Location{ File: sarifutils.GetRelativeLocationFileName(location, run.Invocations), StartLine: sarifutils.GetLocationStartLine(location), @@ -560,17 +647,20 @@ func sortResults(simpleJsonResults *formats.SimpleJsonResults) { if simpleJsonResults == nil { return } - if len(simpleJsonResults.SecurityViolations) > 0 { - sortVulnerabilityOrViolationRows(simpleJsonResults.SecurityViolations) - } - if len(simpleJsonResults.Vulnerabilities) > 0 { - sortVulnerabilityOrViolationRows(simpleJsonResults.Vulnerabilities) - } + // Licenses if len(simpleJsonResults.Licenses) > 0 { sort.Slice(simpleJsonResults.Licenses, func(i, j int) bool { return simpleJsonResults.Licenses[i].LicenseKey < simpleJsonResults.Licenses[j].LicenseKey }) } + // Sca Vulnerabilities + if len(simpleJsonResults.Vulnerabilities) > 0 { + sortVulnerabilityOrViolationRows(simpleJsonResults.Vulnerabilities) + } + // Sca Violations + if len(simpleJsonResults.SecurityViolations) > 0 { + sortVulnerabilityOrViolationRows(simpleJsonResults.SecurityViolations) + } if len(simpleJsonResults.LicensesViolations) > 0 { sort.Slice(simpleJsonResults.LicensesViolations, func(i, j int) bool { return simpleJsonResults.LicensesViolations[i].SeverityNumValue > simpleJsonResults.LicensesViolations[j].SeverityNumValue @@ -581,14 +671,25 @@ func sortResults(simpleJsonResults *formats.SimpleJsonResults) { return simpleJsonResults.OperationalRiskViolations[i].SeverityNumValue > simpleJsonResults.OperationalRiskViolations[j].SeverityNumValue }) } - if len(simpleJsonResults.Secrets) > 0 { - sortSourceCodeRow(simpleJsonResults.Secrets) + // Jas Vulnerabilities + if len(simpleJsonResults.SecretsVulnerabilities) > 0 { + sortSourceCodeRow(simpleJsonResults.SecretsVulnerabilities) + } + if len(simpleJsonResults.IacsVulnerabilities) > 0 { + sortSourceCodeRow(simpleJsonResults.IacsVulnerabilities) + } + if len(simpleJsonResults.SastVulnerabilities) > 0 { + sortSourceCodeRow(simpleJsonResults.SastVulnerabilities) + } + // Jas Violations + if len(simpleJsonResults.SecretsViolations) > 0 { + sortSourceCodeRow(simpleJsonResults.SecretsViolations) } - if len(simpleJsonResults.Iacs) > 0 { - sortSourceCodeRow(simpleJsonResults.Iacs) + if len(simpleJsonResults.IacsViolations) > 0 { + sortSourceCodeRow(simpleJsonResults.IacsViolations) } - if len(simpleJsonResults.Sast) > 0 { - sortSourceCodeRow(simpleJsonResults.Sast) + if len(simpleJsonResults.SastViolations) > 0 { + sortSourceCodeRow(simpleJsonResults.SastViolations) } } diff --git a/utils/results/conversion/simplejsonparser/simplejsonparser_test.go b/utils/results/conversion/simplejsonparser/simplejsonparser_test.go index 49316ec5..c965961f 100644 --- a/utils/results/conversion/simplejsonparser/simplejsonparser_test.go +++ b/utils/results/conversion/simplejsonparser/simplejsonparser_test.go @@ -95,6 +95,7 @@ var ( IssueId: "XRAY-3", Summary: "summary-3", Severity: "Low", + WatchName: "lic-watch-name", ViolationType: "license", LicenseKey: "license-1", Components: map[string]services.Component{ @@ -425,7 +426,7 @@ func TestPrepareSimpleJsonViolations(t *testing.T) { entitledForJas bool applicabilityRuns []*sarif.Run expectedSecurityOutput []formats.VulnerabilityOrViolationRow - expectedLicenseOutput []formats.LicenseRow + expectedLicenseOutput []formats.LicenseViolationRow expectedOperationalRiskOutput []formats.OperationalRiskViolationRow }{ { @@ -451,6 +452,9 @@ func TestPrepareSimpleJsonViolations(t *testing.T) { }}, }, ImpactPaths: [][]formats.ComponentRow{{{Name: "root"}, {Name: "component-A"}}}, + ViolationContext: formats.ViolationContext{ + Watch: "watch-name", + }, }, { Summary: "summary-1", @@ -466,6 +470,9 @@ func TestPrepareSimpleJsonViolations(t *testing.T) { }}, }, ImpactPaths: [][]formats.ComponentRow{{{Name: "root"}, {Name: "component-B"}}}, + ViolationContext: formats.ViolationContext{ + Watch: "watch-name", + }, }, { Summary: "summary-2", @@ -481,15 +488,23 @@ func TestPrepareSimpleJsonViolations(t *testing.T) { }}, }, ImpactPaths: [][]formats.ComponentRow{{{Name: "root"}, {Name: "component-B"}}}, + ViolationContext: formats.ViolationContext{ + Watch: "watch-name", + }, }, }, - expectedLicenseOutput: []formats.LicenseRow{ + expectedLicenseOutput: []formats.LicenseViolationRow{ { - LicenseKey: "license-1", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 10}, - ImpactedDependencyName: "component-B", - Components: []formats.ComponentRow{{Name: "component-B", Location: &formats.Location{File: "target"}}}, + LicenseRow: formats.LicenseRow{ + LicenseKey: "license-1", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 10}, + ImpactedDependencyName: "component-B", + Components: []formats.ComponentRow{{Name: "component-B", Location: &formats.Location{File: "target"}}}, + }, + }, + ViolationContext: formats.ViolationContext{ + Watch: "lic-watch-name", }, }, }, @@ -525,6 +540,9 @@ func TestPrepareSimpleJsonViolations(t *testing.T) { }}, }, ImpactPaths: [][]formats.ComponentRow{{{Name: "root"}, {Name: "component-A"}}}, + ViolationContext: formats.ViolationContext{ + Watch: "watch-name", + }, }, { Summary: "summary-1", @@ -541,6 +559,9 @@ func TestPrepareSimpleJsonViolations(t *testing.T) { }}, }, ImpactPaths: [][]formats.ComponentRow{{{Name: "root"}, {Name: "component-B"}}}, + ViolationContext: formats.ViolationContext{ + Watch: "watch-name", + }, }, { Summary: "summary-2", @@ -567,19 +588,27 @@ func TestPrepareSimpleJsonViolations(t *testing.T) { }}, }, ImpactPaths: [][]formats.ComponentRow{{{Name: "root"}, {Name: "component-B"}}}, + ViolationContext: formats.ViolationContext{ + Watch: "watch-name", + }, }, }, - expectedLicenseOutput: []formats.LicenseRow{ + expectedLicenseOutput: []formats.LicenseViolationRow{ { - LicenseKey: "license-1", - ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ - SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 10}, - ImpactedDependencyName: "component-B", - // Direct - Components: []formats.ComponentRow{{ - Name: "component-B", - Location: &formats.Location{File: "target"}, - }}, + LicenseRow: formats.LicenseRow{ + LicenseKey: "license-1", + ImpactedDependencyDetails: formats.ImpactedDependencyDetails{ + SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 10}, + ImpactedDependencyName: "component-B", + // Direct + Components: []formats.ComponentRow{{ + Name: "component-B", + Location: &formats.Location{File: "target"}, + }}, + }, + }, + ViolationContext: formats.ViolationContext{ + Watch: "lic-watch-name", }, }, }, @@ -683,10 +712,10 @@ func TestPrepareSimpleJsonJasIssues(t *testing.T) { jasIssues: issues, expectedOutput: []formats.SourceCodeRow{ { - Finding: "result-msg", - ScannerDescription: "rule-msg", - Location: formats.Location{File: "file", StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "secret-snippet"}, - SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 13}, + Finding: "result-msg", + Location: formats.Location{File: "file", StartLine: 1, StartColumn: 2, EndLine: 3, EndColumn: 4, Snippet: "secret-snippet"}, + SeverityDetails: formats.SeverityDetails{Severity: "Low", SeverityNumValue: 13}, + ScannerInfo: formats.ScannerInfo{RuleId: "secret-rule-id", ScannerDescription: "rule-msg"}, }, }, }, diff --git a/utils/results/conversion/summaryparser/summaryparser.go b/utils/results/conversion/summaryparser/summaryparser.go index 58f3f1e0..04c8a88b 100644 --- a/utils/results/conversion/summaryparser/summaryparser.go +++ b/utils/results/conversion/summaryparser/summaryparser.go @@ -4,6 +4,7 @@ import ( "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" "github.com/jfrog/jfrog-cli-security/utils/jasutils" "github.com/jfrog/jfrog-cli-security/utils/results" "github.com/jfrog/jfrog-cli-security/utils/severityutils" @@ -69,7 +70,14 @@ func (sc *CmdResultsSummaryConverter) validateBeforeParse() (err error) { return } -func (sc *CmdResultsSummaryConverter) ParseViolations(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { +func (sc *CmdResultsSummaryConverter) ParseScaIssues(target results.ScanTarget, violations bool, scaResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { + if violations { + return sc.parseScaViolations(target, scaResponse, applicableScan...) + } + return sc.parseScaVulnerabilities(target, scaResponse, applicableScan...) +} + +func (sc *CmdResultsSummaryConverter) parseScaViolations(target results.ScanTarget, scaResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { if err = sc.validateBeforeParse(); err != nil || sc.currentScan.Violations == nil { return } @@ -77,17 +85,27 @@ func (sc *CmdResultsSummaryConverter) ParseViolations(target results.ScanTarget, sc.currentScan.Violations.ScanResultSummary.ScaResults = &formats.ScaScanResultSummary{} } // Parse general SCA results - if scaResponse.ScanId != "" { - sc.currentScan.Violations.ScanResultSummary.ScaResults.ScanIds = utils.UniqueUnion(sc.currentScan.Violations.ScanResultSummary.ScaResults.ScanIds, scaResponse.ScanId) + if scaResponse.Scan.ScanId != "" { + sc.currentScan.Violations.ScanResultSummary.ScaResults.ScanIds = utils.UniqueUnion(sc.currentScan.Violations.ScanResultSummary.ScaResults.ScanIds, scaResponse.Scan.ScanId) + } + if scaResponse.Scan.XrayDataUrl != "" { + sc.currentScan.Violations.ScanResultSummary.ScaResults.MoreInfoUrls = utils.UniqueUnion(sc.currentScan.Violations.ScanResultSummary.ScaResults.MoreInfoUrls, scaResponse.Scan.XrayDataUrl) } - if scaResponse.XrayDataUrl != "" { - sc.currentScan.Violations.ScanResultSummary.ScaResults.MoreInfoUrls = utils.UniqueUnion(sc.currentScan.Violations.ScanResultSummary.ScaResults.MoreInfoUrls, scaResponse.XrayDataUrl) + if scaResponse.IsScanFailed() { + return + } + applicabilityRuns := []*sarif.Run{} + for _, scan := range applicableScan { + if scan.IsScanFailed() { + continue + } + applicabilityRuns = append(applicabilityRuns, scan.Scan...) } // Parse violations parsed := datastructures.MakeSet[string]() - watches, failBuild, err := results.PrepareScaViolations( + watches, failBuild, err := results.ApplyHandlerToScaViolations( target, - scaResponse.Violations, + scaResponse.Scan.Violations, sc.entitledForJas, applicabilityRuns, sc.getScaSecurityViolationHandler(parsed), @@ -156,7 +174,7 @@ func (sc *CmdResultsSummaryConverter) getScaOperationalRiskViolationHandler(pars } } -func (sc *CmdResultsSummaryConverter) ParseVulnerabilities(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { +func (sc *CmdResultsSummaryConverter) parseScaVulnerabilities(target results.ScanTarget, scaResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { if err = sc.validateBeforeParse(); err != nil || sc.currentScan.Vulnerabilities == nil { return } @@ -164,17 +182,27 @@ func (sc *CmdResultsSummaryConverter) ParseVulnerabilities(target results.ScanTa sc.currentScan.Vulnerabilities.ScaResults = &formats.ScaScanResultSummary{} } // Parse general SCA results - if scaResponse.ScanId != "" { - sc.currentScan.Vulnerabilities.ScaResults.ScanIds = utils.UniqueUnion(sc.currentScan.Vulnerabilities.ScaResults.ScanIds, scaResponse.ScanId) + if scaResponse.Scan.ScanId != "" { + sc.currentScan.Vulnerabilities.ScaResults.ScanIds = utils.UniqueUnion(sc.currentScan.Vulnerabilities.ScaResults.ScanIds, scaResponse.Scan.ScanId) } - if scaResponse.XrayDataUrl != "" { - sc.currentScan.Vulnerabilities.ScaResults.MoreInfoUrls = utils.UniqueUnion(sc.currentScan.Vulnerabilities.ScaResults.MoreInfoUrls, scaResponse.XrayDataUrl) + if scaResponse.Scan.XrayDataUrl != "" { + sc.currentScan.Vulnerabilities.ScaResults.MoreInfoUrls = utils.UniqueUnion(sc.currentScan.Vulnerabilities.ScaResults.MoreInfoUrls, scaResponse.Scan.XrayDataUrl) + } + if scaResponse.IsScanFailed() { + return + } + applicabilityRuns := []*sarif.Run{} + for _, scan := range applicableScan { + if scan.IsScanFailed() { + continue + } + applicabilityRuns = append(applicabilityRuns, scan.Scan...) } // Parse vulnerabilities parsed := datastructures.MakeSet[string]() - err = results.PrepareScaVulnerabilities( + err = results.ApplyHandlerToScaVulnerabilities( target, - scaResponse.Vulnerabilities, + scaResponse.Scan.Vulnerabilities, sc.entitledForJas, applicabilityRuns, sc.getScaVulnerabilityHandler(parsed), @@ -221,12 +249,12 @@ func getCveIds(cves []formats.CveRow, issueId string) []string { return ids } -func (sc *CmdResultsSummaryConverter) ParseLicenses(target results.ScanTarget, licenses []services.License) (err error) { +func (sc *CmdResultsSummaryConverter) ParseLicenses(_ results.ScanTarget, _ results.ScanResult[services.ScanResponse]) (err error) { // Not supported in the summary return } -func (sc *CmdResultsSummaryConverter) ParseSecrets(_ results.ScanTarget, secrets ...*sarif.Run) (err error) { +func (sc *CmdResultsSummaryConverter) ParseSecrets(_ results.ScanTarget, isViolationsResults bool, secrets []results.ScanResult[[]*sarif.Run]) (err error) { if !sc.entitledForJas || sc.currentScan.Vulnerabilities == nil { // JAS results are only supported as vulnerabilities for now return @@ -234,13 +262,19 @@ func (sc *CmdResultsSummaryConverter) ParseSecrets(_ results.ScanTarget, secrets if err = sc.validateBeforeParse(); err != nil { return } - if sc.currentScan.Vulnerabilities.SecretsResults == nil { + if !isViolationsResults && sc.currentScan.Vulnerabilities.SecretsResults == nil { sc.currentScan.Vulnerabilities.SecretsResults = &formats.ResultSummary{} } - return results.PrepareJasIssues(secrets, sc.entitledForJas, sc.getJasHandler(jasutils.Secrets)) + if isViolationsResults { + if sc.currentScan.Violations.SecretsResults == nil { + sc.currentScan.Violations.SecretsResults = &formats.ResultSummary{} + } + sc.currentScan.Violations.Watches = utils.UniqueUnion(sc.currentScan.Violations.Watches, getJasScansWatches(secrets...)...) + } + return results.ApplyHandlerToJasIssues(results.ScanResultsToRuns(secrets), sc.entitledForJas, sc.getJasHandler(jasutils.Secrets, isViolationsResults)) } -func (sc *CmdResultsSummaryConverter) ParseIacs(_ results.ScanTarget, iacs ...*sarif.Run) (err error) { +func (sc *CmdResultsSummaryConverter) ParseIacs(_ results.ScanTarget, isViolationsResults bool, iacs []results.ScanResult[[]*sarif.Run]) (err error) { if !sc.entitledForJas || sc.currentScan.Vulnerabilities == nil { // JAS results are only supported as vulnerabilities for now return @@ -248,13 +282,19 @@ func (sc *CmdResultsSummaryConverter) ParseIacs(_ results.ScanTarget, iacs ...*s if err = sc.validateBeforeParse(); err != nil { return } - if sc.currentScan.Vulnerabilities.IacResults == nil { + if !isViolationsResults && sc.currentScan.Vulnerabilities.IacResults == nil { sc.currentScan.Vulnerabilities.IacResults = &formats.ResultSummary{} } - return results.PrepareJasIssues(iacs, sc.entitledForJas, sc.getJasHandler(jasutils.IaC)) + if isViolationsResults { + if sc.currentScan.Violations.IacResults == nil { + sc.currentScan.Violations.IacResults = &formats.ResultSummary{} + } + sc.currentScan.Violations.Watches = utils.UniqueUnion(sc.currentScan.Violations.Watches, getJasScansWatches(iacs...)...) + } + return results.ApplyHandlerToJasIssues(results.ScanResultsToRuns(iacs), sc.entitledForJas, sc.getJasHandler(jasutils.IaC, isViolationsResults)) } -func (sc *CmdResultsSummaryConverter) ParseSast(_ results.ScanTarget, sast ...*sarif.Run) (err error) { +func (sc *CmdResultsSummaryConverter) ParseSast(_ results.ScanTarget, isViolationsResults bool, sast []results.ScanResult[[]*sarif.Run]) (err error) { if !sc.entitledForJas || sc.currentScan.Vulnerabilities == nil { // JAS results are only supported as vulnerabilities for now return @@ -262,36 +302,72 @@ func (sc *CmdResultsSummaryConverter) ParseSast(_ results.ScanTarget, sast ...*s if err = sc.validateBeforeParse(); err != nil { return } - if sc.currentScan.Vulnerabilities.SastResults == nil { + if !isViolationsResults && sc.currentScan.Vulnerabilities.SastResults == nil { sc.currentScan.Vulnerabilities.SastResults = &formats.ResultSummary{} } - return results.PrepareJasIssues(sast, sc.entitledForJas, sc.getJasHandler(jasutils.Sast)) + if isViolationsResults { + if sc.currentScan.Violations.SastResults == nil { + sc.currentScan.Violations.SastResults = &formats.ResultSummary{} + } + sc.currentScan.Violations.Watches = utils.UniqueUnion(sc.currentScan.Violations.Watches, getJasScansWatches(sast...)...) + } + return results.ApplyHandlerToJasIssues(results.ScanResultsToRuns(sast), sc.entitledForJas, sc.getJasHandler(jasutils.Sast, isViolationsResults)) } -func (sc *CmdResultsSummaryConverter) getJasHandler(scanType jasutils.JasScanType) results.ParseJasFunc { +// getJasHandler returns a handler that counts the JAS results (based on severity and CA status) for each issue it handles +func (sc *CmdResultsSummaryConverter) getJasHandler(scanType jasutils.JasScanType, violations bool) results.ParseJasFunc { return func(run *sarif.Run, rule *sarif.ReportingDescriptor, severity severityutils.Severity, result *sarif.Result, location *sarif.Location) (err error) { if location == nil { // Only count the issue if it has a location return } - // Get the scanType count + // Get the count map in the `sc.currentScan` object based on the scanType and violation + resultStatus := formats.NoStatus var count *formats.ResultSummary switch scanType { case jasutils.Secrets: - count = sc.currentScan.Vulnerabilities.SecretsResults + if tokenStatus := results.GetResultPropertyTokenValidation(result); tokenStatus != "" { + resultStatus = tokenStatus + } + if violations { + count = sc.currentScan.Violations.SecretsResults + } else { + count = sc.currentScan.Vulnerabilities.SecretsResults + } case jasutils.IaC: - count = sc.currentScan.Vulnerabilities.IacResults + if violations { + count = sc.currentScan.Violations.IacResults + } else { + count = sc.currentScan.Vulnerabilities.IacResults + } case jasutils.Sast: - count = sc.currentScan.Vulnerabilities.SastResults + if violations { + count = sc.currentScan.Violations.SastResults + } else { + count = sc.currentScan.Vulnerabilities.SastResults + } } if count == nil { return } - // PrepareJasIssues calls the handler for each issue (location) + // Aggregate the issue in to the count (based on severity and CA status) if _, ok := (*count)[severity.String()]; !ok { (*count)[severity.String()] = map[string]int{} } - (*count)[severity.String()][formats.NoStatus] += 1 + (*count)[severity.String()][resultStatus] += 1 return } } + +func getJasScansWatches(scans ...results.ScanResult[[]*sarif.Run]) (watches []string) { + for _, scanInfo := range scans { + for _, run := range scanInfo.Scan { + for _, result := range run.Results { + if watch := sarifutils.GetResultWatches(result); watch != "" { + watches = append(watches, watch) + } + } + } + } + return +} diff --git a/utils/results/conversion/tableparser/tableparser.go b/utils/results/conversion/tableparser/tableparser.go index 09345f8b..cc6774d3 100644 --- a/utils/results/conversion/tableparser/tableparser.go +++ b/utils/results/conversion/tableparser/tableparser.go @@ -27,14 +27,17 @@ func (tc *CmdResultsTableConverter) Get() (formats.ResultsTables, error) { return formats.ResultsTables{}, err } return formats.ResultsTables{ - SecurityVulnerabilitiesTable: formats.ConvertToVulnerabilityTableRow(simpleJsonFormat.Vulnerabilities), - SecurityViolationsTable: formats.ConvertToVulnerabilityTableRow(simpleJsonFormat.SecurityViolations), - LicenseViolationsTable: formats.ConvertToLicenseViolationTableRow(simpleJsonFormat.LicensesViolations), LicensesTable: formats.ConvertToLicenseTableRow(simpleJsonFormat.Licenses), + SecurityVulnerabilitiesTable: formats.ConvertToScaVulnerabilityOrViolationTableRow(simpleJsonFormat.Vulnerabilities), + SecurityViolationsTable: formats.ConvertToScaVulnerabilityOrViolationTableRow(simpleJsonFormat.SecurityViolations), + LicenseViolationsTable: formats.ConvertToLicenseViolationTableRow(simpleJsonFormat.LicensesViolations), OperationalRiskViolationsTable: formats.ConvertToOperationalRiskViolationTableRow(simpleJsonFormat.OperationalRiskViolations), - SecretsTable: formats.ConvertToSecretsTableRow(simpleJsonFormat.Secrets), - IacTable: formats.ConvertToIacOrSastTableRow(simpleJsonFormat.Iacs), - SastTable: formats.ConvertToIacOrSastTableRow(simpleJsonFormat.Sast), + SecretsVulnerabilitiesTable: formats.ConvertToSecretsTableRow(simpleJsonFormat.SecretsVulnerabilities), + SecretsViolationsTable: formats.ConvertToSecretsTableRow(simpleJsonFormat.SecretsViolations), + IacVulnerabilitiesTable: formats.ConvertToIacOrSastTableRow(simpleJsonFormat.IacsVulnerabilities), + IacViolationsTable: formats.ConvertToIacOrSastTableRow(simpleJsonFormat.IacsViolations), + SastVulnerabilitiesTable: formats.ConvertToIacOrSastTableRow(simpleJsonFormat.SastVulnerabilities), + SastViolationsTable: formats.ConvertToIacOrSastTableRow(simpleJsonFormat.SastViolations), }, nil } @@ -46,26 +49,22 @@ func (tc *CmdResultsTableConverter) ParseNewTargetResults(target results.ScanTar return tc.simpleJsonConvertor.ParseNewTargetResults(target, errors...) } -func (tc *CmdResultsTableConverter) ParseViolations(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { - return tc.simpleJsonConvertor.ParseViolations(target, scaResponse, applicabilityRuns...) -} - -func (tc *CmdResultsTableConverter) ParseVulnerabilities(target results.ScanTarget, scaResponse services.ScanResponse, applicabilityRuns ...*sarif.Run) (err error) { - return tc.simpleJsonConvertor.ParseVulnerabilities(target, scaResponse, applicabilityRuns...) +func (tc *CmdResultsTableConverter) ParseScaIssues(target results.ScanTarget, violations bool, scaResponse results.ScanResult[services.ScanResponse], applicableScan ...results.ScanResult[[]*sarif.Run]) (err error) { + return tc.simpleJsonConvertor.ParseScaIssues(target, violations, scaResponse, applicableScan...) } -func (tc *CmdResultsTableConverter) ParseLicenses(target results.ScanTarget, licenses []services.License) (err error) { - return tc.simpleJsonConvertor.ParseLicenses(target, licenses) +func (tc *CmdResultsTableConverter) ParseLicenses(target results.ScanTarget, scaResponse results.ScanResult[services.ScanResponse]) (err error) { + return tc.simpleJsonConvertor.ParseLicenses(target, scaResponse) } -func (tc *CmdResultsTableConverter) ParseSecrets(target results.ScanTarget, secrets ...*sarif.Run) (err error) { - return tc.simpleJsonConvertor.ParseSecrets(target, secrets...) +func (tc *CmdResultsTableConverter) ParseSecrets(target results.ScanTarget, isViolationsResults bool, secrets []results.ScanResult[[]*sarif.Run]) (err error) { + return tc.simpleJsonConvertor.ParseSecrets(target, isViolationsResults, secrets) } -func (tc *CmdResultsTableConverter) ParseIacs(target results.ScanTarget, iacs ...*sarif.Run) (err error) { - return tc.simpleJsonConvertor.ParseIacs(target, iacs...) +func (tc *CmdResultsTableConverter) ParseIacs(target results.ScanTarget, isViolationsResults bool, iacs []results.ScanResult[[]*sarif.Run]) (err error) { + return tc.simpleJsonConvertor.ParseIacs(target, isViolationsResults, iacs) } -func (tc *CmdResultsTableConverter) ParseSast(target results.ScanTarget, sast ...*sarif.Run) (err error) { - return tc.simpleJsonConvertor.ParseSast(target, sast...) +func (tc *CmdResultsTableConverter) ParseSast(target results.ScanTarget, isViolationsResults bool, sast []results.ScanResult[[]*sarif.Run]) (err error) { + return tc.simpleJsonConvertor.ParseSast(target, isViolationsResults, sast) } diff --git a/utils/results/output/resultwriter.go b/utils/results/output/resultwriter.go index cc669a07..76116c4d 100644 --- a/utils/results/output/resultwriter.go +++ b/utils/results/output/resultwriter.go @@ -24,18 +24,14 @@ type ResultsWriter struct { platformUrl string // Format The output format. format format.OutputFormat - // IncludeVulnerabilities If true, include all vulnerabilities as part of the output. Else, include violations only. - includeVulnerabilities bool - // If true, will print violation results. - hasViolationContext bool - // IncludeLicenses If true, also include license violations as part of the output. - includeLicenses bool + // For build-scan where always we expect violations, to override the default behavior. + showViolations bool // IsMultipleRoots multipleRoots is set to true, in case the given results array contains (or may contain) results of several projects (like in binary scan). isMultipleRoots *bool // PrintExtended, If true, show extended results. printExtended bool - // For table format - show table only for the given subScansPreformed - subScansPreformed []utils.SubScanType + // For table format - show table only for the given subScansPerformed + subScansPerformed []utils.SubScanType // Messages - Option array of messages, to be displayed if the format is Table messages []string } @@ -59,23 +55,13 @@ func (rw *ResultsWriter) SetIsMultipleRootProject(isMultipleRootProject bool) *R return rw } -func (rw *ResultsWriter) SetSubScansPreformed(subScansPreformed []utils.SubScanType) *ResultsWriter { - rw.subScansPreformed = subScansPreformed +func (rw *ResultsWriter) SetSubScansPerformed(subScansPerformed []utils.SubScanType) *ResultsWriter { + rw.subScansPerformed = subScansPerformed return rw } -func (rw *ResultsWriter) SetHasViolationContext(hasViolationContext bool) *ResultsWriter { - rw.hasViolationContext = hasViolationContext - return rw -} - -func (rw *ResultsWriter) SetIncludeVulnerabilities(includeVulnerabilities bool) *ResultsWriter { - rw.includeVulnerabilities = includeVulnerabilities - return rw -} - -func (rw *ResultsWriter) SetIncludeLicenses(licenses bool) *ResultsWriter { - rw.includeLicenses = licenses +func (rw *ResultsWriter) SetHasViolationContext(violationContext bool) *ResultsWriter { + rw.showViolations = violationContext return rw } @@ -147,10 +133,10 @@ func (rw *ResultsWriter) createResultsConvertor(pretty bool) *conversion.Command return conversion.NewCommandResultsConvertor(conversion.ResultConvertParams{ PlatformUrl: rw.platformUrl, IsMultipleRoots: rw.isMultipleRoots, - IncludeLicenses: rw.includeLicenses, - IncludeVulnerabilities: rw.includeVulnerabilities, - HasViolationContext: rw.hasViolationContext, - RequestedScans: rw.subScansPreformed, + IncludeLicenses: rw.commandResults.IncludesLicenses(), + IncludeVulnerabilities: rw.commandResults.IncludesVulnerabilities(), + HasViolationContext: rw.commandResults.HasViolationContext(), + RequestedScans: rw.subScansPerformed, Pretty: pretty, }) } @@ -200,37 +186,58 @@ func (rw *ResultsWriter) printTables() (err error) { return } printMessages(rw.messages) - if utils.IsScanRequested(rw.commandResults.CmdType, utils.ScaScan, rw.subScansPreformed...) { - if rw.hasViolationContext { - if err = PrintViolationsTable(tableContent, rw.commandResults.CmdType, rw.printExtended); err != nil { - return - } - } - if rw.includeVulnerabilities { - if err = PrintVulnerabilitiesTable(tableContent, rw.commandResults.CmdType, len(rw.commandResults.GetTechnologies()) > 0, rw.printExtended); err != nil { - return - } - } - if rw.includeLicenses { - if err = PrintLicensesTable(tableContent, rw.printExtended, rw.commandResults.CmdType); err != nil { - return - } + if err = rw.printScaTablesIfNeeded(tableContent); err != nil { + return + } + if err = rw.printJasTablesIfNeeded(tableContent, utils.SecretsScan, jasutils.Secrets); err != nil { + return + } + if rw.shouldPrintSecretValidationExtraMessage() { + log.Output("This table contains multiple secret types, such as tokens, generic password, ssh keys and more, token validation is only supported on tokens.") + } + if err = rw.printJasTablesIfNeeded(tableContent, utils.IacScan, jasutils.IaC); err != nil { + return + } + return rw.printJasTablesIfNeeded(tableContent, utils.SastScan, jasutils.Sast) +} + +func (rw *ResultsWriter) printScaTablesIfNeeded(tableContent formats.ResultsTables) (err error) { + if !utils.IsScanRequested(rw.commandResults.CmdType, utils.ScaScan, rw.subScansPerformed...) { + return + } + if rw.showViolations || rw.commandResults.HasViolationContext() { + if err = PrintViolationsTable(tableContent, rw.commandResults.CmdType, rw.printExtended); err != nil { + return } } - if utils.IsScanRequested(rw.commandResults.CmdType, utils.SecretsScan, rw.subScansPreformed...) { - if err = PrintSecretsTable(tableContent, rw.commandResults.EntitledForJas, rw.commandResults.SecretValidation); err != nil { + if rw.commandResults.IncludesVulnerabilities() { + if err = PrintVulnerabilitiesTable(tableContent, rw.commandResults.CmdType, len(rw.commandResults.GetTechnologies()) > 0, rw.printExtended); err != nil { return } } - if utils.IsScanRequested(rw.commandResults.CmdType, utils.IacScan, rw.subScansPreformed...) { - if err = PrintJasTable(tableContent, rw.commandResults.EntitledForJas, jasutils.IaC); err != nil { + if !rw.commandResults.IncludesLicenses() { + return + } + return PrintLicensesTable(tableContent, rw.printExtended, rw.commandResults.CmdType) +} + +func (rw *ResultsWriter) printJasTablesIfNeeded(tableContent formats.ResultsTables, subScan utils.SubScanType, scanType jasutils.JasScanType) (err error) { + if !utils.IsScanRequested(rw.commandResults.CmdType, subScan, rw.subScansPerformed...) { + return + } + if rw.showViolations || rw.commandResults.HasViolationContext() { + if err = PrintJasTable(tableContent, rw.commandResults.EntitledForJas, scanType, true); err != nil { return } } - if !utils.IsScanRequested(rw.commandResults.CmdType, utils.SastScan, rw.subScansPreformed...) { - return nil + if !rw.commandResults.IncludesVulnerabilities() { + return } - return PrintJasTable(tableContent, rw.commandResults.EntitledForJas, jasutils.Sast) + return PrintJasTable(tableContent, rw.commandResults.EntitledForJas, scanType, false) +} + +func (rw *ResultsWriter) shouldPrintSecretValidationExtraMessage() bool { + return rw.commandResults.SecretValidation && utils.IsScanRequested(rw.commandResults.CmdType, utils.SecretsScan, rw.subScansPerformed...) } // PrintVulnerabilitiesTable prints the vulnerabilities in a table. @@ -301,20 +308,7 @@ func PrintLicensesTable(tables formats.ResultsTables, printExtended bool, cmdTyp return coreutils.PrintTable(tables.LicensesTable, "Licenses", "No licenses were found", printExtended) } -func PrintSecretsTable(tables formats.ResultsTables, entitledForJas, tokenValidationEnabled bool) (err error) { - if !entitledForJas { - return - } - if err = PrintJasTable(tables, entitledForJas, jasutils.Secrets); err != nil { - return - } - if tokenValidationEnabled { - log.Output("This table contains multiple secret types, such as tokens, generic password, ssh keys and more, token validation is only supported on tokens.") - } - return -} - -func PrintJasTable(tables formats.ResultsTables, entitledForJas bool, scanType jasutils.JasScanType) error { +func PrintJasTable(tables formats.ResultsTables, entitledForJas bool, scanType jasutils.JasScanType, violations bool) error { if !entitledForJas { return nil } @@ -322,14 +316,29 @@ func PrintJasTable(tables formats.ResultsTables, entitledForJas bool, scanType j log.Output() switch scanType { case jasutils.Secrets: - return coreutils.PrintTable(tables.SecretsTable, "Secret Detection", - "✨ No secrets were found ✨", false) + if violations { + return coreutils.PrintTable(tables.SecretsViolationsTable, "Secret Violations", + "✨ No violations were found ✨", false) + } else { + return coreutils.PrintTable(tables.SecretsVulnerabilitiesTable, "Secret Detection", + "✨ No secrets were found ✨", false) + } case jasutils.IaC: - return coreutils.PrintTable(tables.IacTable, "Infrastructure as Code Vulnerabilities", - "✨ No Infrastructure as Code vulnerabilities were found ✨", false) + if violations { + return coreutils.PrintTable(tables.IacViolationsTable, "Infrastructure as Code Violations", + "✨ No Infrastructure as Code violations were found ✨", false) + } else { + return coreutils.PrintTable(tables.IacVulnerabilitiesTable, "Infrastructure as Code Vulnerabilities", + "✨ No Infrastructure as Code vulnerabilities were found ✨", false) + } case jasutils.Sast: - return coreutils.PrintTable(tables.SastTable, "Static Application Security Testing (SAST)", - "✨ No Static Application Security Testing vulnerabilities were found ✨", false) + if violations { + return coreutils.PrintTable(tables.SastViolationsTable, "Static Application Security Testing (SAST) Violations", + "✨ No Static Application Security Testing violations were found ✨", false) + } else { + return coreutils.PrintTable(tables.SastVulnerabilitiesTable, "Static Application Security Testing (SAST)", + "✨ No Static Application Security Testing vulnerabilities were found ✨", false) + } } return nil } diff --git a/utils/results/results.go b/utils/results/results.go index e954ff4f..925e58f8 100644 --- a/utils/results/results.go +++ b/utils/results/results.go @@ -13,6 +13,7 @@ import ( "github.com/jfrog/jfrog-cli-security/utils/techutils" "github.com/jfrog/jfrog-client-go/utils/log" "github.com/jfrog/jfrog-client-go/xray/services" + xrayApi "github.com/jfrog/jfrog-client-go/xray/services/utils" "github.com/owenrumney/go-sarif/v2/sarif" ) @@ -24,6 +25,7 @@ type SecurityCommandResults struct { EntitledForJas bool `json:"jas_entitled"` SecretValidation bool `json:"secret_validation,omitempty"` CmdType utils.CommandType `json:"command_type"` + ResultContext ResultContext `json:"result_context,omitempty"` StartTime time.Time `json:"start_time"` // MultiScanId is a unique identifier that is used to group multiple scans together. MultiScanId string `json:"multi_scan_id,omitempty"` @@ -35,6 +37,33 @@ type SecurityCommandResults struct { errorsMutex sync.Mutex `json:"-"` } +// We have three types of results: vulnerabilities, violations and licenses. +// If the user provides a violation context (watches, repo_path, project_key, git_repo_key) the results will only include violations. +// If the user provides a violation context and requests vulnerabilities, the results will include both vulnerabilities and violations. +// If the user doesn't provide a violation context, the results will include vulnerabilities. +// Only one (Resource) field can be provided at a time. +// License information can be provided in all cases if requested. +type ResultContext struct { + // If watches are provided, the scan will be performed only with the provided watches. + Watches []string `json:"watches,omitempty"` + // (Resource) If repo_path is provided, the scan will be performed on the repository's watches. + RepoPath string `json:"repo_path,omitempty"` + // (Resource) If projectKey is provided we will fetch the watches defined on the project. + ProjectKey string `json:"project_key,omitempty"` + // (Resource) If gitRepository is provided we will fetch the watches defined on the git repository. + GitRepoHttpsCloneUrl string `json:"git_repo_key,omitempty"` + // If non of the above is provided or requested, the results will include vulnerabilities + IncludeVulnerabilities bool `json:"include_vulnerabilities"` + // If requested, the results will include licenses + IncludeLicenses bool `json:"include_licenses"` + // The active watches defined on the project_key and git_repository values above that were fetched from the platform + PlatformWatches *xrayApi.ResourcesWatchesBody `json:"platform_watches,omitempty"` +} + +func (rc *ResultContext) HasViolationContext() bool { + return len(rc.Watches) > 0 || len(rc.GitRepoHttpsCloneUrl) > 0 || len(rc.ProjectKey) > 0 || len(rc.RepoPath) > 0 +} + type TargetResults struct { ScanTarget // All scan results for the target @@ -45,19 +74,33 @@ type TargetResults struct { errorsMutex sync.Mutex `json:"-"` } +type ScanResult[T interface{}] struct { + Scan T `json:"scan"` + StatusCode int `json:"status_code,omitempty"` +} + +func (sr *ScanResult[T]) IsScanFailed() bool { + return sr.StatusCode != 0 +} + type ScaScanResults struct { IsMultipleRootProject *bool `json:"is_multiple_root_project,omitempty"` // Target of the scan Descriptors []string `json:"descriptors,omitempty"` // Sca scan results - XrayResults []services.ScanResponse `json:"xray_scan,omitempty"` + XrayResults []ScanResult[services.ScanResponse] `json:"xray_scan,omitempty"` } type JasScansResults struct { - ApplicabilityScanResults []*sarif.Run `json:"contextual_analysis,omitempty"` - SecretsScanResults []*sarif.Run `json:"secrets,omitempty"` - IacScanResults []*sarif.Run `json:"iac,omitempty"` - SastScanResults []*sarif.Run `json:"sast,omitempty"` + JasVulnerabilities JasScanResults `json:"jas_vulnerabilities,omitempty"` + JasViolations JasScanResults `json:"jas_violations,omitempty"` + ApplicabilityScanResults []ScanResult[[]*sarif.Run] `json:"contextual_analysis,omitempty"` +} + +type JasScanResults struct { + SecretsScanResults []ScanResult[[]*sarif.Run] `json:"secrets,omitempty"` + IacScanResults []ScanResult[[]*sarif.Run] `json:"iac,omitempty"` + SastScanResults []ScanResult[[]*sarif.Run] `json:"sast,omitempty"` } type ScanTarget struct { @@ -86,10 +129,6 @@ func (st ScanTarget) String() (str string) { return } -// func NewCommandResults(cmdType utils.CommandType, xrayVersion string, entitledForJas, secretValidation bool) *SecurityCommandResults { -// return &SecurityCommandResults{CmdType: cmdType, XrayVersion: xrayVersion, EntitledForJas: entitledForJas, SecretValidation: secretValidation, targetsMutex: sync.Mutex{}} -// } - func NewCommandResults(cmdType utils.CommandType) *SecurityCommandResults { return &SecurityCommandResults{CmdType: cmdType, targetsMutex: sync.Mutex{}, errorsMutex: sync.Mutex{}} } @@ -124,6 +163,11 @@ func (r *SecurityCommandResults) SetMultiScanId(multiScanId string) *SecurityCom return r } +func (r *SecurityCommandResults) SetResultsContext(context ResultContext) *SecurityCommandResults { + r.ResultContext = context + return r +} + // --- Aggregated results for all targets --- // Adds a general error to the command results in different phases of its execution. // Notice that in some usages we pass constant 'false' to the 'allowSkippingError' parameter in some places, where we wish to force propagation of the error when it occurs. @@ -132,10 +176,27 @@ func (r *SecurityCommandResults) AddGeneralError(err error, allowSkippingError b log.Warn(fmt.Sprintf("Partial results are allowed, the error is skipped: %s", err.Error())) return r } + r.errorsMutex.Lock() r.GeneralError = errors.Join(r.GeneralError, err) + r.errorsMutex.Unlock() return r } +// Is the result includes violations +func (r *SecurityCommandResults) HasViolationContext() bool { + return r.ResultContext.HasViolationContext() +} + +// Is the result includes vulnerabilities +func (r *SecurityCommandResults) IncludesVulnerabilities() bool { + return r.ResultContext.IncludeVulnerabilities +} + +// Is the result includes licenses +func (r *SecurityCommandResults) IncludesLicenses() bool { + return r.ResultContext.IncludeLicenses +} + func (r *SecurityCommandResults) GetTargetsPaths() (paths []string) { for _, scan := range r.Targets { paths = append(paths, scan.Target) @@ -143,6 +204,13 @@ func (r *SecurityCommandResults) GetTargetsPaths() (paths []string) { return } +func (r *SecurityCommandResults) GetTargets() (targets []ScanTarget) { + for _, scan := range r.Targets { + targets = append(targets, scan.ScanTarget) + } + return +} + func (r *SecurityCommandResults) GetScaScansXrayResults() (results []services.ScanResponse) { for _, scan := range r.Targets { results = append(results, scan.GetScaScansXrayResults()...) @@ -150,14 +218,16 @@ func (r *SecurityCommandResults) GetScaScansXrayResults() (results []services.Sc return } -func (r *SecurityCommandResults) GetJasScansResults(scanType jasutils.JasScanType) (results []*sarif.Run) { +func (r *SecurityCommandResults) HasJasScansResults(scanType jasutils.JasScanType) bool { if !r.EntitledForJas { - return + return false } - for _, scan := range r.Targets { - results = append(results, scan.GetJasScansResults(scanType)...) + for _, target := range r.Targets { + if target.HasJasScansResults(scanType) { + return true + } } - return + return false } func (r *SecurityCommandResults) GetErrors() (err error) { @@ -194,8 +264,8 @@ func (r *SecurityCommandResults) HasMultipleTargets() bool { } func (r *SecurityCommandResults) HasInformation() bool { - for _, scan := range r.Targets { - if scan.HasInformation() { + for _, target := range r.Targets { + if target.HasInformation() { return true } } @@ -203,8 +273,8 @@ func (r *SecurityCommandResults) HasInformation() bool { } func (r *SecurityCommandResults) HasFindings() bool { - for _, scan := range r.Targets { - if scan.HasFindings() { + for _, target := range r.Targets { + if target.HasFindings() { return true } } @@ -216,7 +286,7 @@ func (r *SecurityCommandResults) HasFindings() bool { func (r *SecurityCommandResults) NewScanResults(target ScanTarget) *TargetResults { targetResults := &TargetResults{ScanTarget: target, errorsMutex: sync.Mutex{}} if r.EntitledForJas { - targetResults.JasResults = &JasScansResults{} + targetResults.JasResults = &JasScansResults{JasVulnerabilities: JasScanResults{}, JasViolations: JasScanResults{}} } r.targetsMutex.Lock() @@ -258,7 +328,9 @@ func (sr *TargetResults) GetScaScansXrayResults() (results []services.ScanRespon if sr.ScaResults == nil { return } - results = append(results, sr.ScaResults.XrayResults...) + for _, scanResult := range sr.ScaResults.XrayResults { + results = append(results, scanResult.Scan) + } return } @@ -271,12 +343,13 @@ func (sr *TargetResults) GetTechnologies() []techutils.Technology { return technologiesSet.ToSlice() } for _, scaResult := range sr.ScaResults.XrayResults { - for _, vulnerability := range scaResult.Vulnerabilities { + xrayScanResult := scaResult.Scan + for _, vulnerability := range xrayScanResult.Vulnerabilities { if tech := techutils.Technology(strings.ToLower(vulnerability.Technology)); tech != "" { technologiesSet.Add(tech) } } - for _, violation := range scaResult.Violations { + for _, violation := range xrayScanResult.Violations { if tech := techutils.Technology(strings.ToLower(violation.Technology)); tech != "" { technologiesSet.Add(tech) } @@ -285,11 +358,18 @@ func (sr *TargetResults) GetTechnologies() []techutils.Technology { return technologiesSet.ToSlice() } +func (sr *TargetResults) HasJasScansResults(scanType jasutils.JasScanType) bool { + if sr.JasResults == nil { + return false + } + return sr.JasResults.HasInformationByType(scanType) +} + func (sr *TargetResults) GetJasScansResults(scanType jasutils.JasScanType) (results []*sarif.Run) { if sr.JasResults == nil { return } - return sr.JasResults.GetResults(scanType) + return sr.JasResults.GetVulnerabilitiesResults(scanType) } func (sr *TargetResults) HasInformation() bool { @@ -331,11 +411,13 @@ func (sr *TargetResults) SetDescriptors(descriptors ...string) *TargetResults { return sr } -func (sr *TargetResults) NewScaScanResults(responses ...services.ScanResponse) *ScaScanResults { +func (sr *TargetResults) NewScaScanResults(errorCode int, responses ...services.ScanResponse) *ScaScanResults { if sr.ScaResults == nil { sr.ScaResults = &ScaScanResults{} } - sr.ScaResults.XrayResults = append(sr.ScaResults.XrayResults, responses...) + for _, response := range responses { + sr.ScaResults.XrayResults = append(sr.ScaResults.XrayResults, ScanResult[services.ScanResponse]{Scan: response, StatusCode: errorCode}) + } return sr.ScaResults } @@ -344,7 +426,7 @@ func (ssr *ScaScanResults) HasInformation() bool { return true } for _, scanResults := range ssr.XrayResults { - if len(scanResults.Licenses) > 0 { + if len(scanResults.Scan.Licenses) > 0 { return true } } @@ -353,23 +435,88 @@ func (ssr *ScaScanResults) HasInformation() bool { func (ssr *ScaScanResults) HasFindings() bool { for _, scanResults := range ssr.XrayResults { - if len(scanResults.Vulnerabilities) > 0 || len(scanResults.Violations) > 0 { + if len(scanResults.Scan.Vulnerabilities) > 0 || len(scanResults.Scan.Violations) > 0 { return true } } return false } -func (jsr *JasScansResults) GetResults(scanType jasutils.JasScanType) (results []*sarif.Run) { +func (jsr *JasScansResults) NewApplicabilityScanResults(exitCode int, runs ...*sarif.Run) { + jsr.ApplicabilityScanResults = append(jsr.ApplicabilityScanResults, ScanResult[[]*sarif.Run]{Scan: runs, StatusCode: exitCode}) +} + +func (jsr *JasScansResults) AddJasScanResults(scanType jasutils.JasScanType, vulnerabilitiesRuns []*sarif.Run, violationsRuns []*sarif.Run, exitCode int) { + switch scanType { + case jasutils.Secrets: + jsr.JasVulnerabilities.SecretsScanResults = append(jsr.JasVulnerabilities.SecretsScanResults, ScanResult[[]*sarif.Run]{Scan: vulnerabilitiesRuns, StatusCode: exitCode}) + jsr.JasViolations.SecretsScanResults = append(jsr.JasViolations.SecretsScanResults, ScanResult[[]*sarif.Run]{Scan: violationsRuns, StatusCode: exitCode}) + case jasutils.IaC: + jsr.JasVulnerabilities.IacScanResults = append(jsr.JasVulnerabilities.IacScanResults, ScanResult[[]*sarif.Run]{Scan: vulnerabilitiesRuns, StatusCode: exitCode}) + jsr.JasViolations.IacScanResults = append(jsr.JasViolations.IacScanResults, ScanResult[[]*sarif.Run]{Scan: violationsRuns, StatusCode: exitCode}) + case jasutils.Sast: + jsr.JasVulnerabilities.SastScanResults = append(jsr.JasVulnerabilities.SastScanResults, ScanResult[[]*sarif.Run]{Scan: vulnerabilitiesRuns, StatusCode: exitCode}) + jsr.JasViolations.SastScanResults = append(jsr.JasViolations.SastScanResults, ScanResult[[]*sarif.Run]{Scan: violationsRuns, StatusCode: exitCode}) + } +} + +func (jsr *JasScansResults) GetApplicabilityScanResults() (results []*sarif.Run) { + for _, scan := range jsr.ApplicabilityScanResults { + results = append(results, scan.Scan...) + } + return +} + +func (jsr *JasScansResults) GetVulnerabilitiesResults(scanType jasutils.JasScanType) (results []*sarif.Run) { switch scanType { - case jasutils.Applicability: - results = jsr.ApplicabilityScanResults case jasutils.Secrets: - results = jsr.SecretsScanResults + for _, scan := range jsr.JasVulnerabilities.SecretsScanResults { + if scan.IsScanFailed() { + continue + } + results = append(results, scan.Scan...) + } case jasutils.IaC: - results = jsr.IacScanResults + for _, scan := range jsr.JasVulnerabilities.IacScanResults { + if scan.IsScanFailed() { + continue + } + results = append(results, scan.Scan...) + } case jasutils.Sast: - results = jsr.SastScanResults + for _, scan := range jsr.JasVulnerabilities.SastScanResults { + if scan.IsScanFailed() { + continue + } + results = append(results, scan.Scan...) + } + } + return +} + +func (jsr *JasScansResults) GetViolationsResults(scanType jasutils.JasScanType) (results []*sarif.Run) { + switch scanType { + case jasutils.Secrets: + for _, scan := range jsr.JasViolations.SecretsScanResults { + if scan.IsScanFailed() { + continue + } + results = append(results, scan.Scan...) + } + case jasutils.IaC: + for _, scan := range jsr.JasViolations.IacScanResults { + if scan.IsScanFailed() { + continue + } + results = append(results, scan.Scan...) + } + case jasutils.Sast: + for _, scan := range jsr.JasViolations.SastScanResults { + if scan.IsScanFailed() { + continue + } + results = append(results, scan.Scan...) + } } return } @@ -384,7 +531,15 @@ func (jsr *JasScansResults) HasFindings() bool { } func (jsr *JasScansResults) HasFindingsByType(scanType jasutils.JasScanType) bool { - for _, run := range jsr.GetResults(scanType) { + for _, run := range jsr.GetVulnerabilitiesResults(scanType) { + for _, result := range run.Results { + if len(result.Locations) > 0 { + return true + } + } + } + + for _, run := range jsr.GetViolationsResults(scanType) { for _, result := range run.Results { if len(result.Locations) > 0 { return true @@ -404,7 +559,15 @@ func (jsr *JasScansResults) HasInformation() bool { } func (jsr *JasScansResults) HasInformationByType(scanType jasutils.JasScanType) bool { - for _, run := range jsr.GetResults(scanType) { + if scanType == jasutils.Applicability && len(jsr.ApplicabilityScanResults) > 0 { + return true + } + for _, run := range jsr.GetVulnerabilitiesResults(scanType) { + if len(run.Results) > 0 { + return true + } + } + for _, run := range jsr.GetViolationsResults(scanType) { if len(run.Results) > 0 { return true } diff --git a/utils/techutils/techutils.go b/utils/techutils/techutils.go index 549df5c1..1c7e0e51 100644 --- a/utils/techutils/techutils.go +++ b/utils/techutils/techutils.go @@ -431,29 +431,6 @@ func getDirNoTechList(technologiesDetected map[Technology]map[string][]string, d // If all children exists in childNoTechList, add only the parent directory to NoTech noTechList = []string{dir} } - - // for _, techDirs := range technologiesDetected { - // if _, exist := techDirs[dir]; exist { - // // The directory is already mapped to a technology, no need to add the dir or its sub directories to NoTech - // break - // } - // for _, child := range children { - // childNoTechList := getDirNoTechList(technologiesDetected, child, dirsList) - // } - - // if len(children) == 0 { - // // No children directories, add the directory to NoTech - // childNoTechList = append(childNoTechList, dir) - // break - // } - // for _, child := range children { - // childNoTechList = append(childNoTechList, getDirNoTechList(technologiesDetected, child, dirsList)...) - // } - // // If all children exists in childNoTechList, add only the parent directory to NoTech - // if len(children) == len(childNoTechList) { - // childNoTechList = []string{dir} - // } - // } return } @@ -466,58 +443,6 @@ func getDirChildren(dir string, dirsList []string) (children []string) { return } -// func addNoTechIfNeeded(technologiesDetected map[Technology]map[string][]string, path, excludePathPattern string) (finalMap map[Technology]map[string][]string, err error) { -// finalMap = technologiesDetected -// noTechMap := map[string][]string{} -// // TODO: not only direct, need to see if multiple levels of directories are missing technology indicators -// // if all directories in are found no need for anything else, -// // if one missing need to add it to NoTech -// // if not one detected add only parent directory no need for each directory -// directories, err := getDirectDirectories(path, excludePathPattern) -// if err != nil { -// return -// } -// for _, dir := range directories { -// // Check if the directory is already mapped to a technology -// isMapped := false -// for _, techDirs := range finalMap { -// if _, exist := techDirs[dir]; exist { -// isMapped = true -// break -// } -// } -// if !isMapped { -// // Add the directory to NoTech (no indicators/descriptors were found) -// noTechMap[dir] = []string{} -// } -// } -// if len(technologiesDetected) == 0 || len(noTechMap) > 0 { -// // no technologies detected at all (add NoTech without any directories) or some directories were added to NoTech -// finalMap[NoTech] = noTechMap -// } -// return -// } - -// func getDirectDirectories(path, excludePathPattern string) (directories []string, err error) { -// // Get all files and directories in the path, not recursive -// filesOrDirsInPath, err := fspatterns.ListFiles(path, false, true, true, true, excludePathPattern) -// if err != nil { -// return -// } -// // Filter to directories only -// for _, potentialDir := range filesOrDirsInPath { -// isDir, e := fileutils.IsDirExists(potentialDir, true) -// if e != nil { -// err = errors.Join(err, fmt.Errorf("failed to check if %s is a directory: %w", potentialDir, e)) -// continue -// } -// if isDir { -// directories = append(directories, potentialDir) -// } -// } -// return -// } - // Map files to relevant working directories according to the technologies' indicators/descriptors and requested descriptors. // files: The file paths to map. // requestedDescriptors: Special requested descriptors (for example in Pip requirement.txt can have different path) for each technology. diff --git a/utils/utils.go b/utils/utils.go index 9db0f861..77e8ee2d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -18,6 +18,7 @@ import ( "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" ) @@ -61,6 +62,10 @@ func (v ViolationIssueType) String() string { type SubScanType string +func (s SubScanType) String() string { + return string(s) +} + const ( SourceCode CommandType = "source_code" Binary CommandType = "binary" @@ -72,10 +77,6 @@ const ( type CommandType string -func (s SubScanType) String() string { - return string(s) -} - func (s CommandType) IsTargetBinary() bool { return s == Binary || s == DockerImage } @@ -92,6 +93,29 @@ func IsScanRequested(cmdType CommandType, subScan SubScanType, requestedScans .. return len(requestedScans) == 0 || slices.Contains(requestedScans, subScan) } +func GetScanFindingsLog(scanType SubScanType, vulnerabilitiesCount, violationsCount, threadId int) string { + threadPrefix := "" + if threadId >= 0 { + threadPrefix = clientutils.GetLogMsgPrefix(threadId, false) + } + if vulnerabilitiesCount == 0 && violationsCount == 0 { + return fmt.Sprintf("%sNo %s findings", threadPrefix, scanType.String()) + } + msg := fmt.Sprintf("%sFound", threadPrefix) + hasVulnerabilities := vulnerabilitiesCount > 0 + if hasVulnerabilities { + msg += fmt.Sprintf(" %d %s vulnerabilities", vulnerabilitiesCount, scanType.String()) + } + if violationsCount > 0 { + if hasVulnerabilities { + msg = fmt.Sprintf("%s (%d violations)", msg, violationsCount) + } else { + msg += fmt.Sprintf(" %d %s violations", violationsCount, scanType.String()) + } + } + return msg +} + func IsCI() bool { return strings.ToLower(os.Getenv(coreutils.CI)) == "true" } @@ -171,6 +195,10 @@ func NewFloat64Ptr(v float64) *float64 { return &v } +func NewStrPtr(v string) *string { + return &v +} + func Md5Hash(values ...string) (string, error) { return toHash(crypto.MD5, values...) } diff --git a/utils/validations/test_mocks.go b/utils/validations/test_mocks.go index 1dac6f90..767868e7 100644 --- a/utils/validations/test_mocks.go +++ b/utils/validations/test_mocks.go @@ -8,10 +8,20 @@ import ( "strings" "testing" - "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" + coreutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils" "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-security/utils" + "github.com/jfrog/jfrog-cli-security/utils/formats" + "github.com/jfrog/jfrog-cli-security/utils/formats/sarifutils" + "github.com/jfrog/jfrog-cli-security/utils/jasutils" + "github.com/jfrog/jfrog-cli-security/utils/results" + "github.com/jfrog/jfrog-cli-security/utils/severityutils" "github.com/jfrog/jfrog-client-go/artifactory" + "github.com/jfrog/jfrog-client-go/xray/services" + xrayutils "github.com/jfrog/jfrog-client-go/xray/services/utils" + xscservices "github.com/jfrog/jfrog-client-go/xsc/services" xscutils "github.com/jfrog/jfrog-client-go/xsc/services/utils" + "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" ) @@ -19,7 +29,6 @@ const ( TestMsi = "27e175b8-e525-11ee-842b-7aa2c69b8f1f" TestScaScanId = "3d90ec4b-cf33-4846-6831-4bf9576f2235" - // TestMoreInfoUrl = "https://www.jfrog.com" TestPlatformUrl = "https://test-platform-url.jfrog.io/" TestMoreInfoUrl = "https://test-more-info-url.jfrog.io/" @@ -28,6 +37,15 @@ const ( var ( versionApiUrl = "/%s/%ssystem/version" + + TestMockGitInfo = xscservices.XscGitInfoContext{ + GitRepoHttpsCloneUrl: "https://github.com/jfrog/dummy-repo.git", + GitProvider: "github", + GitRepoName: "dummy-repo", + GitProject: "jfrog", + BranchName: "dev", + LastCommitHash: "4be861f9a585d8ae5dde0b9550669972ee05c9d7", + } ) type restsTestHandler func(w http.ResponseWriter, r *http.Request) @@ -41,7 +59,7 @@ func CreateXscRestsMockServer(t *testing.T, testHandler restsTestHandler) (*http testServer := CreateRestsMockServer(testHandler) serverDetails := &config.ServerDetails{Url: testServer.URL + "/", XrayUrl: testServer.URL + "/xray/"} - serviceManager, err := utils.CreateServiceManager(serverDetails, -1, 0, false) + serviceManager, err := coreutils.CreateServiceManager(serverDetails, -1, 0, false) assert.NoError(t, err) return testServer, serverDetails, serviceManager } @@ -53,12 +71,17 @@ func CreateXrayRestsMockServer(testHandler restsTestHandler) (*httptest.Server, } type MockServerParams struct { + // General params to mock Xray and Xsc (backward compatible and inner service based on the following params) XrayVersion string XscVersion string XscNotExists bool - ReturnMsi string + // Xsc/Event Api + ReturnMsi string + // Xsc/Watch/Resource Api + ReturnMockPlatformWatches xrayutils.ResourcesWatchesBody } +// Mock Only Xsc server API (backward compatible) func XscServer(t *testing.T, params MockServerParams) (*httptest.Server, *config.ServerDetails) { if !xscutils.IsXscXrayInnerService(params.XrayVersion) { serverMock, serverDetails, _ := CreateXscRestsMockServer(t, getXscServerApiHandler(t, params)) @@ -116,6 +139,7 @@ func getXscServerApiHandler(t *testing.T, params MockServerParams) func(w http.R } } +// Mock Xray server (with Xsc inner service if supported based on version - not backward compatible to XSC API) func XrayServer(t *testing.T, params MockServerParams) (*httptest.Server, *config.ServerDetails) { serverMock, serverDetails := CreateXrayRestsMockServer(func(w http.ResponseWriter, r *http.Request) { if r.RequestURI == fmt.Sprintf(versionApiUrl, "api/v1/", "xray") { @@ -172,7 +196,123 @@ func XrayServer(t *testing.T, params MockServerParams) (*httptest.Server, *confi if !isXrayAfterXscMigration { return } + // Get defined active watches only supported after xsc was inner service + if strings.Contains(r.RequestURI, "/api/v1/xsc/watches/resource") { + if r.Method == http.MethodGet { + w.WriteHeader(http.StatusOK) + content, err := utils.GetAsJsonBytes(params.ReturnMockPlatformWatches, false, false) + if !assert.NoError(t, err) { + return + } + _, err = w.Write(content) + if !assert.NoError(t, err) { + return + } + } + } getXscServerApiHandler(t, params)(w, r) }) return serverMock, serverDetails } + +func NewMockJasRuns(runs ...*sarif.Run) []results.ScanResult[[]*sarif.Run] { + return []results.ScanResult[[]*sarif.Run]{{Scan: runs}} +} + +func NewMockScaResults(responses ...services.ScanResponse) (converted []results.ScanResult[services.ScanResponse]) { + for _, response := range responses { + status := 0 + if response.ScannedStatus == "Failed" { + status = 1 + } + converted = append(converted, results.ScanResult[services.ScanResponse]{Scan: response, StatusCode: status}) + } + return +} + +func CreateDummyApplicabilityRule(cve string, applicableStatus jasutils.ApplicabilityStatus) *sarif.ReportingDescriptor { + return &sarif.ReportingDescriptor{ + ID: fmt.Sprintf("applic_%s", cve), + Name: &cve, + ShortDescription: sarif.NewMultiformatMessageString(fmt.Sprintf("Scanner for %s", cve)), + FullDescription: sarif.NewMultiformatMessageString(fmt.Sprintf("The Scanner checks for %s", cve)), + Properties: map[string]interface{}{"applicability": applicableStatus.String()}, + } +} + +func CreateDummyApplicableResults(cve string, location formats.Location) *sarif.Result { + return &sarif.Result{ + Message: *sarif.NewTextMessage("ca msg"), + RuleID: utils.NewStrPtr(fmt.Sprintf("applic_%s", cve)), + Locations: []*sarif.Location{ + sarifutils.CreateLocation(location.File, location.StartLine, location.StartColumn, location.EndLine, location.EndColumn, location.Snippet), + }, + } +} + +func CreateDummyJasRule(id string, cwe ...string) *sarif.ReportingDescriptor { + descriptor := &sarif.ReportingDescriptor{ + ID: id, + Name: &id, + ShortDescription: sarif.NewMultiformatMessageString(fmt.Sprintf("Scanner for %s", id)).WithMarkdown(fmt.Sprintf("Scanner for %s", id)), + FullDescription: sarif.NewMultiformatMessageString(fmt.Sprintf("The Scanner checks for %s", id)).WithMarkdown(fmt.Sprintf("The Scanner checks for %s", id)), + } + if len(cwe) > 0 { + descriptor.DefaultConfiguration = &sarif.ReportingConfiguration{ + Parameters: &sarif.PropertyBag{ + Properties: map[string]interface{}{"CWE": strings.Join(cwe, ",")}, + }, + } + } + return descriptor +} + +func CreateDummySecretResult(id string, status jasutils.TokenValidationStatus, metadata string, location formats.Location) *sarif.Result { + return &sarif.Result{ + Message: *sarif.NewTextMessage(fmt.Sprintf("Secret %s were found", id)), + RuleID: utils.NewStrPtr(id), + Level: utils.NewStrPtr(severityutils.LevelInfo.String()), + Locations: []*sarif.Location{ + sarifutils.CreateLocation(location.File, location.StartLine, location.StartColumn, location.EndLine, location.EndColumn, location.Snippet), + }, + PropertyBag: sarif.PropertyBag{ + Properties: map[string]interface{}{"tokenValidation": status.String(), "metadata": metadata}, + }, + } +} + +func CreateDummySecretViolationResult(id string, status jasutils.TokenValidationStatus, metadata, watch, issueId string, policies []string, location formats.Location) *sarif.Result { + result := CreateDummySecretResult(id, status, metadata, location) + result.PropertyBag.Properties[sarifutils.WatchSarifPropertyKey] = watch + result.PropertyBag.Properties[sarifutils.JasIssueIdSarifPropertyKey] = issueId + result.PropertyBag.Properties[sarifutils.PoliciesSarifPropertyKey] = policies + return result +} + +func CreateDummyJasResult(id string, level severityutils.SarifSeverityLevel, location formats.Location, codeFlows ...[]formats.Location) *sarif.Result { + result := &sarif.Result{ + Message: *sarif.NewTextMessage(fmt.Sprintf("Vulnerability %s were found", id)), + RuleID: utils.NewStrPtr(id), + Level: utils.NewStrPtr(level.String()), + Locations: []*sarif.Location{ + sarifutils.CreateLocation(location.File, location.StartLine, location.StartColumn, location.EndLine, location.EndColumn, location.Snippet), + }, + PropertyBag: sarif.PropertyBag{Properties: map[string]interface{}{}}, + } + for _, codeFlow := range codeFlows { + flows := []*sarif.Location{} + for _, location := range codeFlow { + flows = append(flows, sarifutils.CreateLocation(location.File, location.StartLine, location.StartColumn, location.EndLine, location.EndColumn, location.Snippet)) + } + result.CodeFlows = append(result.CodeFlows, sarifutils.CreateCodeFlow(sarifutils.CreateThreadFlow(flows...))) + } + return result +} + +func CreateDummySastViolationResult(id string, level severityutils.SarifSeverityLevel, watch, issueId string, policies []string, location formats.Location, codeFlows ...[]formats.Location) *sarif.Result { + result := CreateDummyJasResult(id, level, location, codeFlows...) + result.PropertyBag.Properties[sarifutils.WatchSarifPropertyKey] = watch + result.PropertyBag.Properties[sarifutils.JasIssueIdSarifPropertyKey] = issueId + result.PropertyBag.Properties[sarifutils.PoliciesSarifPropertyKey] = policies + return result +} diff --git a/utils/validations/test_validate_sarif.go b/utils/validations/test_validate_sarif.go index 7a8a8013..5c225e09 100644 --- a/utils/validations/test_validate_sarif.go +++ b/utils/validations/test_validate_sarif.go @@ -18,7 +18,8 @@ const ( SastToolName = "🐸 JFrog SAST" IacToolName = "JFrog Terraform scanner" // #nosec G101 -- Not credentials. - SecretsToolName = "JFrog Secrets scanner" + SecretsToolName = "JFrog Secrets scanner" + ContextualAnalysisToolName = "JFrog Applicability Scanner" ) // Validate sarif report according to the expected values and issue counts in the validation params. @@ -43,65 +44,129 @@ func ValidateCommandSarifOutput(t *testing.T, params ValidationParams) { // If Expected is provided, the validation will check if the Actual content matches the expected results. // If ExactResultsMatch is true, the validation will check exact values and not only the 'equal or grater' counts / existence of expected attributes. (For Integration tests with JFrog API, ExactResultsMatch should be set to false) func ValidateSarifIssuesCount(t *testing.T, params ValidationParams, report *sarif.Report) { - var vulnerabilities, securityViolations, licenseViolations, applicableResults, undeterminedResults, notCoveredResults, notApplicableResults, missingContextResults, inactiveResults int + actualValues := validationCountActualValues{} - iac := sarifutils.GetResultsLocationCount(sarifutils.GetRunsByToolName(report, IacToolName)...) - vulnerabilities += iac - secrets := sarifutils.GetResultsLocationCount(sarifutils.GetRunsByToolName(report, SecretsToolName)...) - secrets += sarifutils.GetResultsLocationCount(sarifutils.GetRunsByToolName(report, sarifparser.BinarySecretScannerToolName)...) - vulnerabilities += secrets - sast := sarifutils.GetResultsLocationCount(sarifutils.GetRunsByToolName(report, SastToolName)...) - vulnerabilities += sast + // SCA + actualValues.ScaVulnerabilities, actualValues.ApplicableVulnerabilities, actualValues.UndeterminedVulnerabilities, actualValues.NotCoveredVulnerabilities, actualValues.NotApplicableVulnerabilities, actualValues.MissingContextVulnerabilities, actualValues.ScaViolations, actualValues.SecurityViolations, actualValues.LicenseViolations, actualValues.ApplicableViolations, actualValues.UndeterminedViolations, actualValues.NotCoveredViolations, actualValues.NotApplicableViolations, actualValues.MissingContextViolations = countScaResults(report) + actualValues.Vulnerabilities += actualValues.ScaVulnerabilities + actualValues.Violations += actualValues.ScaViolations - scaRuns := sarifutils.GetRunsByToolName(report, sarifparser.ScaScannerToolName) - for _, run := range scaRuns { + // Secrets + actualValues.SecretsVulnerabilities, actualValues.InactiveSecretsVulnerabilities, actualValues.SecretsViolations, actualValues.InactiveSecretsViolations = countSecretsResults(report) + actualValues.Vulnerabilities += actualValues.SecretsVulnerabilities + actualValues.Violations += actualValues.SecretsViolations + + // IAC + actualValues.IacVulnerabilities, actualValues.IacViolations = countJasResults(sarifutils.GetRunsByToolName(report, IacToolName)) + actualValues.Vulnerabilities += actualValues.IacVulnerabilities + actualValues.Violations += actualValues.IacViolations + + // SAST + actualValues.SastVulnerabilities, actualValues.SastViolations = countJasResults(sarifutils.GetRunsByToolName(report, SastToolName)) + actualValues.Vulnerabilities += actualValues.SastVulnerabilities + actualValues.Violations += actualValues.SastViolations + + if params.Total != nil { + // Not supported in the summary output + params.Total.Licenses = 0 + } + ValidateCount(t, "sarif report", params, actualValues) +} + +func countScaResults(report *sarif.Report) (vulnerabilities, applicableVulnerabilitiesResults, undeterminedVulnerabilitiesResults, notCoveredVulnerabilitiesResults, notApplicableVulnerabilitiesResults, missingContextVulnerabilitiesResults, violations, securityViolations, licenseViolations, applicableViolationsResults, undeterminedViolationsResults, notCoveredViolationsResults, notApplicableViolationsResults, missingContextViolationsResults int) { + for _, run := range sarifutils.GetRunsByToolName(report, sarifparser.ScaScannerToolName) { for _, result := range run.Results { // If watch property exists, add to security violations or license violations else add to vulnerabilities - if _, ok := result.Properties[sarifparser.WatchSarifPropertyKey]; ok { - if isSecurityIssue(result) { - securityViolations++ - } else { + isViolations := false + if _, ok := result.Properties[sarifutils.WatchSarifPropertyKey]; ok { + isViolations = true + violations++ + if !isSecurityIssue(result) { licenseViolations++ + continue } - continue + securityViolations++ + } else { + vulnerabilities++ } - vulnerabilities++ + // Get the applicability status in the result properties (convert to string) and add count to the appropriate category applicabilityProperty := result.Properties[jasutils.ApplicabilitySarifPropertyKey] if applicability, ok := applicabilityProperty.(string); ok { switch applicability { case jasutils.Applicable.String(): - applicableResults++ + if isViolations { + applicableViolationsResults++ + } else { + applicableVulnerabilitiesResults++ + } case jasutils.NotApplicable.String(): - notApplicableResults++ + if isViolations { + notApplicableViolationsResults++ + } else { + notApplicableVulnerabilitiesResults++ + } case jasutils.ApplicabilityUndetermined.String(): - undeterminedResults++ + if isViolations { + undeterminedViolationsResults++ + } else { + undeterminedVulnerabilitiesResults++ + } case jasutils.NotCovered.String(): - notCoveredResults++ + if isViolations { + notCoveredViolationsResults++ + } else { + notCoveredVulnerabilitiesResults++ + } case jasutils.MissingContext.String(): - missingContextResults++ + if isViolations { + missingContextViolationsResults++ + } else { + missingContextVulnerabilitiesResults++ + } } } - if tokenStatus := results.GetResultPropertyTokenValidation(result); tokenStatus == jasutils.Inactive.ToString() { - inactiveResults++ + } + } + return +} + +func countSecretsResults(report *sarif.Report) (vulnerabilities, inactiveVulnerabilities, violations, inactiveViolations int) { + allRuns := append(sarifutils.GetRunsByToolName(report, SecretsToolName), sarifutils.GetRunsByToolName(report, sarifparser.BinarySecretScannerToolName)...) + for _, run := range allRuns { + for _, result := range run.Results { + isViolation := false + // JAS results may not have watch property, we should also try to infer by prefix in msg + if _, ok := result.Properties[sarifutils.WatchSarifPropertyKey]; ok || strings.HasPrefix(sarifutils.GetResultMsgMarkdown(result), "Security Violation") { + isViolation = true + violations++ + } else { + vulnerabilities++ + } + if tokenStatus := results.GetResultPropertyTokenValidation(result); tokenStatus == jasutils.Inactive.String() { + if isViolation { + inactiveViolations++ + } else { + inactiveVulnerabilities++ + } } } } + return +} - ValidateContent(t, params.ExactResultsMatch, - CountValidation[int]{Expected: params.Sast, Actual: sast, Msg: GetValidationCountErrMsg("sast", "sarif report", params.ExactResultsMatch, params.Sast, sast)}, - CountValidation[int]{Expected: params.Iac, Actual: iac, Msg: GetValidationCountErrMsg("Iac", "sarif report", params.ExactResultsMatch, params.Iac, iac)}, - CountValidation[int]{Expected: params.Secrets, Actual: secrets, Msg: GetValidationCountErrMsg("secrets", "sarif report", params.ExactResultsMatch, params.Secrets, secrets)}, - CountValidation[int]{Expected: params.Inactive, Actual: inactiveResults, Msg: GetValidationCountErrMsg("inactive secret results", "sarif report", params.ExactResultsMatch, params.Inactive, inactiveResults)}, - CountValidation[int]{Expected: params.Applicable, Actual: applicableResults, Msg: GetValidationCountErrMsg("applicable results", "sarif report", params.ExactResultsMatch, params.Applicable, applicableResults)}, - CountValidation[int]{Expected: params.Undetermined, Actual: undeterminedResults, Msg: GetValidationCountErrMsg("undetermined results", "sarif report", params.ExactResultsMatch, params.Undetermined, undeterminedResults)}, - CountValidation[int]{Expected: params.NotCovered, Actual: notCoveredResults, Msg: GetValidationCountErrMsg("not covered results", "sarif report", params.ExactResultsMatch, params.NotCovered, notCoveredResults)}, - CountValidation[int]{Expected: params.NotApplicable, Actual: notApplicableResults, Msg: GetValidationCountErrMsg("not applicable results", "sarif report", params.ExactResultsMatch, params.NotApplicable, notApplicableResults)}, - CountValidation[int]{Expected: params.MissingContext, Actual: missingContextResults, Msg: GetValidationCountErrMsg("missing context results", "sarif report", params.ExactResultsMatch, params.MissingContext, missingContextResults)}, - CountValidation[int]{Expected: params.SecurityViolations, Actual: securityViolations, Msg: GetValidationCountErrMsg("security violations", "sarif report", params.ExactResultsMatch, params.SecurityViolations, securityViolations)}, - CountValidation[int]{Expected: params.LicenseViolations, Actual: licenseViolations, Msg: GetValidationCountErrMsg("license violations", "sarif report", params.ExactResultsMatch, params.LicenseViolations, licenseViolations)}, - CountValidation[int]{Expected: params.Vulnerabilities, Actual: vulnerabilities, Msg: GetValidationCountErrMsg("vulnerabilities", "sarif report", params.ExactResultsMatch, params.Vulnerabilities, vulnerabilities)}, - ) +func countJasResults(runs []*sarif.Run) (vulnerabilities, violations int) { + for _, run := range runs { + for _, result := range run.Results { + // JAS results may not have watch property, we should also try to infer by prefix in msg + if _, ok := result.Properties[sarifutils.WatchSarifPropertyKey]; ok || strings.HasPrefix(sarifutils.GetResultMsgMarkdown(result), "Security Violation") { + violations++ + } else { + vulnerabilities++ + } + } + } + return } func isSecurityIssue(result *sarif.Result) bool { @@ -201,11 +266,11 @@ func getResultByResultId(expected *sarif.Result, actual []*sarif.Result) *sarif. } func isPotentialSimilarResults(expected, actual *sarif.Result) bool { - return sarifutils.GetResultRuleId(actual) == sarifutils.GetResultRuleId(expected) && sarifutils.GetResultProperty(sarifparser.WatchSarifPropertyKey, actual) == sarifutils.GetResultProperty(sarifparser.WatchSarifPropertyKey, expected) + return sarifutils.GetResultRuleId(actual) == sarifutils.GetResultRuleId(expected) && sarifutils.GetResultProperty(sarifutils.WatchSarifPropertyKey, actual) == sarifutils.GetResultProperty(sarifutils.WatchSarifPropertyKey, expected) } func getResultId(result *sarif.Result) string { - return fmt.Sprintf("%s-%s-%s-%s", sarifutils.GetResultRuleId(result), sarifutils.GetResultMsgText(result), sarifutils.GetResultProperty(sarifparser.WatchSarifPropertyKey, result), getLocationsId(result.Locations)) + return fmt.Sprintf("%s-%s-%s-%s", sarifutils.GetResultRuleId(result), sarifutils.GetResultMsgText(result), sarifutils.GetResultProperty(sarifutils.WatchSarifPropertyKey, result), getLocationsId(result.Locations)) } func getLocationsId(locations []*sarif.Location) string { diff --git a/utils/validations/test_validate_sca.go b/utils/validations/test_validate_sca.go index 23591ef8..13c9deb9 100644 --- a/utils/validations/test_validate_sca.go +++ b/utils/validations/test_validate_sca.go @@ -38,10 +38,11 @@ func ValidateCommandJsonOutput(t *testing.T, params ValidationParams) { } func ValidateScanResponseIssuesCount(t *testing.T, params ValidationParams, content ...services.ScanResponse) { - var vulnerabilities, licenses, securityViolations, licenseViolations, operationalViolations int + var vulnerabilities, violations, licenses, securityViolations, licenseViolations, operationalViolations int for _, result := range content { vulnerabilities += len(result.Vulnerabilities) + violations += len(result.Violations) licenses += len(result.Licenses) for _, violation := range result.Violations { switch violation.ViolationType { @@ -55,13 +56,16 @@ func ValidateScanResponseIssuesCount(t *testing.T, params ValidationParams, cont } } - ValidateContent(t, params.ExactResultsMatch, - CountValidation[int]{Expected: params.Vulnerabilities, Actual: vulnerabilities, Msg: GetValidationCountErrMsg("vulnerabilities", "scan responses", params.ExactResultsMatch, params.Vulnerabilities, vulnerabilities)}, - CountValidation[int]{Expected: params.Licenses, Actual: licenses, Msg: GetValidationCountErrMsg("licenses", "scan responses", params.ExactResultsMatch, params.Licenses, licenses)}, - CountValidation[int]{Expected: params.SecurityViolations, Actual: securityViolations, Msg: GetValidationCountErrMsg("security violations", "scan responses", params.ExactResultsMatch, params.SecurityViolations, securityViolations)}, - CountValidation[int]{Expected: params.LicenseViolations, Actual: licenseViolations, Msg: GetValidationCountErrMsg("license violations", "scan responses", params.ExactResultsMatch, params.LicenseViolations, licenseViolations)}, - CountValidation[int]{Expected: params.OperationalViolations, Actual: operationalViolations, Msg: GetValidationCountErrMsg("operational risk violations", "scan responses", params.ExactResultsMatch, params.OperationalViolations, operationalViolations)}, - ) + ValidateTotalCount(t, "json", params.ExactResultsMatch, params.Total, vulnerabilities, violations, licenses) + if params.Violations != nil { + ValidateScaViolationCount(t, "json", params.ExactResultsMatch, params.Violations.ValidateType, securityViolations, licenseViolations, operationalViolations) + if params.Violations.ValidateApplicabilityStatus != nil || params.Violations.ValidateScan != nil { + t.Error("Validate Violations only support ValidateType for JSON output") + } + } + if params.Vulnerabilities != nil { + t.Error("Validate Vulnerabilities is not supported for JSON output") + } } func ValidateScanResponses(t *testing.T, exactMatch bool, expected, actual []services.ScanResponse) { diff --git a/utils/validations/test_validate_simple_json.go b/utils/validations/test_validate_simple_json.go index ed0d5622..9cf90b3d 100644 --- a/utils/validations/test_validate_simple_json.go +++ b/utils/validations/test_validate_simple_json.go @@ -44,48 +44,73 @@ func ValidateCommandSimpleJsonOutput(t *testing.T, params ValidationParams) { // If Expected is provided, the validation will check if the Actual content matches the expected results. // If ExactResultsMatch is true, the validation will check exact values and not only the 'equal or grater' counts / existence of expected attributes. (For Integration tests with JFrog API, ExactResultsMatch should be set to false) func ValidateSimpleJsonIssuesCount(t *testing.T, params ValidationParams, results formats.SimpleJsonResults) { - var applicableResults, undeterminedResults, notCoveredResults, notApplicableResults, missingContextResults, inactiveResults int + actualValues := validationCountActualValues{ + // Total + Vulnerabilities: len(results.Vulnerabilities) + len(results.SecretsVulnerabilities) + len(results.SastVulnerabilities) + len(results.IacsVulnerabilities), + Violations: len(results.SecurityViolations) + len(results.LicensesViolations) + len(results.OperationalRiskViolations) + len(results.SecretsViolations) + len(results.SastViolations) + len(results.IacsViolations), + Licenses: len(results.Licenses), + // Jas vulnerabilities + SastVulnerabilities: len(results.SastVulnerabilities), + SecretsVulnerabilities: len(results.SecretsVulnerabilities), + IacVulnerabilities: len(results.IacsVulnerabilities), + // Jas violations + SastViolations: len(results.SastViolations), + SecretsViolations: len(results.SecretsViolations), + IacViolations: len(results.IacsViolations), + // Sca vulnerabilities + ScaVulnerabilities: len(results.Vulnerabilities), + // Sca violations + ScaViolations: len(results.SecurityViolations) + len(results.LicensesViolations) + len(results.OperationalRiskViolations), + SecurityViolations: len(results.SecurityViolations), + LicenseViolations: len(results.LicensesViolations), + OperationalViolations: len(results.OperationalRiskViolations), + } + // Inactive secrets + for _, result := range results.SecretsVulnerabilities { + if result.Applicability != nil { + if result.Applicability.Status == jasutils.Inactive.String() { + actualValues.InactiveSecretsVulnerabilities += 1 + } + } + } + for _, result := range results.SecretsViolations { + if result.Applicability != nil { + if result.Applicability.Status == jasutils.Inactive.String() { + actualValues.InactiveSecretsViolations += 1 + } + } + } + // CA status for _, vuln := range results.Vulnerabilities { switch vuln.Applicable { case jasutils.NotApplicable.String(): - notApplicableResults++ + actualValues.NotApplicableVulnerabilities++ case jasutils.Applicable.String(): - applicableResults++ + actualValues.ApplicableVulnerabilities++ case jasutils.NotCovered.String(): - notCoveredResults++ + actualValues.NotCoveredVulnerabilities++ case jasutils.ApplicabilityUndetermined.String(): - undeterminedResults++ + actualValues.UndeterminedVulnerabilities++ case jasutils.MissingContext.String(): - missingContextResults++ + actualValues.MissingContextVulnerabilities++ } } - for _, result := range results.Secrets { - if result.Applicability != nil { - if result.Applicability.Status == jasutils.Inactive.String() { - inactiveResults += 1 - } + for _, vuln := range results.SecurityViolations { + switch vuln.Applicable { + case jasutils.NotApplicable.String(): + actualValues.NotApplicableViolations++ + case jasutils.Applicable.String(): + actualValues.ApplicableViolations++ + case jasutils.NotCovered.String(): + actualValues.NotCoveredViolations++ + case jasutils.ApplicabilityUndetermined.String(): + actualValues.UndeterminedViolations++ + case jasutils.MissingContext.String(): + actualValues.MissingContextViolations++ } } - vulnerabilitiesCount := len(results.Vulnerabilities) + len(results.Secrets) + len(results.Sast) + len(results.Iacs) - - ValidateContent(t, params.ExactResultsMatch, - CountValidation[int]{Expected: params.Vulnerabilities, Actual: vulnerabilitiesCount, Msg: GetValidationCountErrMsg("vulnerabilities", "simple-json", params.ExactResultsMatch, params.Vulnerabilities, vulnerabilitiesCount)}, - CountValidation[int]{Expected: params.Sast, Actual: len(results.Sast), Msg: GetValidationCountErrMsg("sast", "simple-json", params.ExactResultsMatch, params.Sast, len(results.Sast))}, - CountValidation[int]{Expected: params.Iac, Actual: len(results.Iacs), Msg: GetValidationCountErrMsg("IaC", "simple-json", params.ExactResultsMatch, params.Iac, len(results.Iacs))}, - CountValidation[int]{Expected: params.Secrets, Actual: len(results.Secrets), Msg: GetValidationCountErrMsg("secrets", "simple-json", params.ExactResultsMatch, params.Secrets, len(results.Secrets))}, - CountValidation[int]{Expected: params.Inactive, Actual: inactiveResults, Msg: GetValidationCountErrMsg("inactive secrets", "simple-json", params.ExactResultsMatch, params.Inactive, inactiveResults)}, - CountValidation[int]{Expected: params.Applicable, Actual: applicableResults, Msg: GetValidationCountErrMsg("applicable vulnerabilities", "simple-json", params.ExactResultsMatch, params.Applicable, applicableResults)}, - CountValidation[int]{Expected: params.Undetermined, Actual: undeterminedResults, Msg: GetValidationCountErrMsg("undetermined vulnerabilities", "simple-json", params.ExactResultsMatch, params.Undetermined, undeterminedResults)}, - CountValidation[int]{Expected: params.NotCovered, Actual: notCoveredResults, Msg: GetValidationCountErrMsg("not covered vulnerabilities", "simple-json", params.ExactResultsMatch, params.NotCovered, notCoveredResults)}, - CountValidation[int]{Expected: params.NotApplicable, Actual: notApplicableResults, Msg: GetValidationCountErrMsg("not applicable vulnerabilities", "simple-json", params.ExactResultsMatch, params.NotApplicable, notApplicableResults)}, - CountValidation[int]{Expected: params.MissingContext, Actual: missingContextResults, Msg: GetValidationCountErrMsg("missing context vulnerabilities", "simple-json", params.ExactResultsMatch, params.MissingContext, missingContextResults)}, - - CountValidation[int]{Expected: params.SecurityViolations, Actual: len(results.SecurityViolations), Msg: GetValidationCountErrMsg("security violations", "simple-json", params.ExactResultsMatch, params.SecurityViolations, len(results.SecurityViolations))}, - CountValidation[int]{Expected: params.LicenseViolations, Actual: len(results.LicensesViolations), Msg: GetValidationCountErrMsg("license violations", "simple-json", params.ExactResultsMatch, params.LicenseViolations, len(results.LicensesViolations))}, - CountValidation[int]{Expected: params.OperationalViolations, Actual: len(results.OperationalRiskViolations), Msg: GetValidationCountErrMsg("operational risk violations", "simple-json", params.ExactResultsMatch, params.OperationalViolations, len(results.OperationalRiskViolations))}, - CountValidation[int]{Expected: params.Licenses, Actual: len(results.Licenses), Msg: GetValidationCountErrMsg("Licenses", "simple-json", params.ExactResultsMatch, params.Licenses, len(results.Licenses))}, - ) + ValidateCount(t, "simple-json", params, actualValues) } func ValidateSimpleJsonResults(t *testing.T, exactMatch bool, expected, actual formats.SimpleJsonResults) { @@ -107,6 +132,7 @@ func ValidateSimpleJsonResults(t *testing.T, exactMatch bool, expected, actual f } validateVulnerabilityOrViolationRow(t, exactMatch, expectedViolation, *violation) } + } func getVulnerabilityOrViolationByIssueId(issueId, impactedDependencyName, impactedDependencyVersion string, content []formats.VulnerabilityOrViolationRow) *formats.VulnerabilityOrViolationRow { @@ -123,7 +149,7 @@ func validateVulnerabilityOrViolationRow(t *testing.T, exactMatch bool, expected StringValidation{Expected: expected.Summary, Actual: actual.Summary, Msg: fmt.Sprintf("IssueId %s: Summary mismatch", expected.IssueId)}, StringValidation{Expected: expected.Severity, Actual: actual.Severity, Msg: fmt.Sprintf("IssueId %s: Severity mismatch", expected.IssueId)}, StringValidation{Expected: expected.Applicable, Actual: actual.Applicable, Msg: fmt.Sprintf("IssueId %s: Applicable mismatch", expected.IssueId)}, - StringValidation{Expected: expected.Technology.String(), Actual: actual.Technology.String(), Msg: fmt.Sprintf("IssueId %s: Technology mismatch", expected.IssueId)}, + // StringValidation{Expected: expected.Technology.String(), Actual: actual.Technology.String(), Msg: fmt.Sprintf("IssueId %s: Technology mismatch", expected.IssueId)}, ListValidation[string]{Expected: expected.References, Actual: actual.References, Msg: fmt.Sprintf("IssueId %s: References mismatch", expected.IssueId)}, StringValidation{Expected: expected.ImpactedDependencyType, Actual: actual.ImpactedDependencyType, Msg: fmt.Sprintf("IssueId %s: ImpactedDependencyType mismatch", expected.IssueId)}, @@ -150,7 +176,7 @@ func validateImpactPaths(t *testing.T, issueId string, exactMatch bool, expected } for _, expectedPath := range expected { impactPath := getImpactPath(expectedPath, actual) - if !assert.NotNil(t, impactPath, fmt.Sprintf("IssueId %s: expected ImpactPath not found in the impactPaths", issueId)) { + if !assert.NotNil(t, impactPath, fmt.Sprintf("IssueId %s: expected ImpactPath %v not found in the impactPaths %v", issueId, expectedPath, actual)) { return } } diff --git a/utils/validations/test_validate_summary.go b/utils/validations/test_validate_summary.go index 5eedf12e..038613b0 100644 --- a/utils/validations/test_validate_summary.go +++ b/utils/validations/test_validate_summary.go @@ -20,17 +20,27 @@ func ValidateCommandSummaryOutput(t *testing.T, params ValidationParams) { } func ValidateSummaryIssuesCount(t *testing.T, params ValidationParams, results formats.ResultsSummary) { - var vulnerabilities, securityViolations, licenseViolations, opRiskViolations, applicableResults, undeterminedResults, notCoveredResults, notApplicableResults, missingContextResults, sast, iac, secrets int - - vulnerabilities = results.GetTotalVulnerabilities() + actualValues := validationCountActualValues{ + // Total + Vulnerabilities: results.GetTotalVulnerabilities(), + Violations: results.GetTotalViolations(), + // Jas vulnerabilities + SastVulnerabilities: results.GetTotalVulnerabilities(formats.SastResult), + SecretsVulnerabilities: results.GetTotalVulnerabilities(formats.SecretsResult), + IacVulnerabilities: results.GetTotalVulnerabilities(formats.IacResult), + // Jas violations + SastViolations: results.GetTotalViolations(formats.SastResult), + SecretsViolations: results.GetTotalViolations(formats.SecretsResult), + IacViolations: results.GetTotalViolations(formats.IacResult), + // Sca vulnerabilities + ScaVulnerabilities: results.GetTotalVulnerabilities(formats.ScaSecurityResult), + // Sca violations + ScaViolations: results.GetTotalViolations(formats.ScaSecurityResult, formats.ScaLicenseResult, formats.ScaOperationalResult), + SecurityViolations: results.GetTotalViolations(formats.ScaSecurityResult), + LicenseViolations: results.GetTotalViolations(formats.ScaLicenseResult), + OperationalViolations: results.GetTotalViolations(formats.ScaOperationalResult), + } - securityViolations = results.GetTotalViolations(formats.ScaSecurityResult) - licenseViolations = results.GetTotalViolations(formats.ScaLicenseResult) - opRiskViolations = results.GetTotalViolations(formats.ScaOperationalResult) - // Jas Results only available as vulnerabilities - sast = results.GetTotalVulnerabilities(formats.SastResult) - secrets = results.GetTotalVulnerabilities(formats.SecretsResult) - iac = results.GetTotalVulnerabilities(formats.IacResult) // Get applicability status counts for _, scan := range results.Scans { if scan.Vulnerabilities != nil { @@ -39,34 +49,62 @@ func ValidateSummaryIssuesCount(t *testing.T, params ValidationParams, results f for status, count := range counts { switch status { case jasutils.Applicable.String(): - applicableResults += count + actualValues.ApplicableVulnerabilities += count + case jasutils.ApplicabilityUndetermined.String(): + actualValues.UndeterminedVulnerabilities += count + case jasutils.NotCovered.String(): + actualValues.NotCoveredVulnerabilities += count + case jasutils.NotApplicable.String(): + actualValues.NotApplicableVulnerabilities += count + case jasutils.MissingContext.String(): + actualValues.MissingContextVulnerabilities += count + } + } + } + } + if scan.Vulnerabilities.SecretsResults != nil { + for _, counts := range *scan.Vulnerabilities.SecretsResults { + for status, count := range counts { + if status == jasutils.Inactive.String() { + actualValues.InactiveSecretsVulnerabilities += count + } + } + } + } + } + if scan.Violations != nil { + if scan.Violations.ScaResults != nil { + for _, counts := range scan.Violations.ScaResults.Security { + for status, count := range counts { + switch status { + case jasutils.Applicable.String(): + actualValues.ApplicableViolations += count case jasutils.ApplicabilityUndetermined.String(): - undeterminedResults += count + actualValues.UndeterminedViolations += count case jasutils.NotCovered.String(): - notCoveredResults += count + actualValues.NotCoveredViolations += count case jasutils.NotApplicable.String(): - notApplicableResults += count + actualValues.NotApplicableViolations += count case jasutils.MissingContext.String(): - missingContextResults += count + actualValues.MissingContextViolations += count + } + } + } + } + if scan.Violations.SecretsResults != nil { + for _, counts := range *scan.Violations.SecretsResults { + for status, count := range counts { + if status == jasutils.Inactive.String() { + actualValues.InactiveSecretsViolations += count } } } } } } - - ValidateContent(t, params.ExactResultsMatch, - CountValidation[int]{Expected: params.Vulnerabilities, Actual: vulnerabilities, Msg: GetValidationCountErrMsg("vulnerabilities", "summary", params.ExactResultsMatch, params.Vulnerabilities, vulnerabilities)}, - CountValidation[int]{Expected: params.Sast, Actual: sast, Msg: GetValidationCountErrMsg("sast", "summary", params.ExactResultsMatch, params.Sast, sast)}, - CountValidation[int]{Expected: params.Secrets, Actual: secrets, Msg: GetValidationCountErrMsg("secrets", "summary", params.ExactResultsMatch, params.Secrets, secrets)}, - CountValidation[int]{Expected: params.Iac, Actual: iac, Msg: GetValidationCountErrMsg("IaC", "summary", params.ExactResultsMatch, params.Iac, iac)}, - CountValidation[int]{Expected: params.Applicable, Actual: applicableResults, Msg: GetValidationCountErrMsg("applicable vulnerabilities", "summary", params.ExactResultsMatch, params.Applicable, applicableResults)}, - CountValidation[int]{Expected: params.Undetermined, Actual: undeterminedResults, Msg: GetValidationCountErrMsg("undetermined vulnerabilities", "summary", params.ExactResultsMatch, params.Undetermined, undeterminedResults)}, - CountValidation[int]{Expected: params.NotCovered, Actual: notCoveredResults, Msg: GetValidationCountErrMsg("not covered vulnerabilities", "summary", params.ExactResultsMatch, params.NotCovered, notCoveredResults)}, - CountValidation[int]{Expected: params.NotApplicable, Actual: notApplicableResults, Msg: GetValidationCountErrMsg("not applicable vulnerabilities", "summary", params.ExactResultsMatch, params.NotApplicable, notApplicableResults)}, - CountValidation[int]{Expected: params.MissingContext, Actual: missingContextResults, Msg: GetValidationCountErrMsg("missing context vulnerabilities", "summary", params.ExactResultsMatch, params.MissingContext, missingContextResults)}, - CountValidation[int]{Expected: params.SecurityViolations, Actual: securityViolations, Msg: GetValidationCountErrMsg("security violations", "summary", params.ExactResultsMatch, params.SecurityViolations, securityViolations)}, - CountValidation[int]{Expected: params.LicenseViolations, Actual: licenseViolations, Msg: GetValidationCountErrMsg("license violations", "summary", params.ExactResultsMatch, params.LicenseViolations, licenseViolations)}, - CountValidation[int]{Expected: params.OperationalViolations, Actual: opRiskViolations, Msg: GetValidationCountErrMsg("operational risk violations", "summary", params.ExactResultsMatch, params.OperationalViolations, opRiskViolations)}, - ) + if params.Total != nil { + // Not supported in the summary output + params.Total.Licenses = 0 + } + ValidateCount(t, "summary", params, actualValues) } diff --git a/utils/validations/test_validation.go b/utils/validations/test_validation.go index 21d86bfd..d12c0966 100644 --- a/utils/validations/test_validation.go +++ b/utils/validations/test_validation.go @@ -28,21 +28,73 @@ type ValidationParams struct { Expected interface{} // If provided, the test will check exact values and not only the minimum values / existence. ExactResultsMatch bool - // Expected issues for each type to check if the content has the correct amount of issues. - Vulnerabilities int - Licenses int - SecurityViolations int - LicenseViolations int - OperationalViolations int - Applicable int - Undetermined int - NotCovered int - NotApplicable int - MissingContext int - Inactive int - Sast int - Iac int - Secrets int + + // Validate total number of licenses, vulnerabilities and violations + Total *TotalCount + // Validate number of vulnerabilities in different contexts + Vulnerabilities *VulnerabilityCount + // Validate number of violations in different contexts + Violations *ViolationCount +} + +type TotalCount struct { + // Expected number of licenses + Licenses int + // Expected number of total vulnerabilities (sca + sast + iac + secrets) + Vulnerabilities int + // Expected number of total violations (sca security + sca license + sca operational + sast + iac + secrets) + Violations int +} + +type ScanCount struct { + // Expected number of Sca issues + Sca int + // Expected number of Sast issues + Sast int + // Expected number of Iac issues + Iac int + // Expected number of Secrets issues + Secrets int +} + +type VulnerabilityCount struct { + // If exists, validate the total amount of issues in different scan types (SCA/SAST/SECRETS/IAC) + ValidateScan *ScanCount + // If exists, validate the total amount of contextual statuses for the issues (sca/secrets) + ValidateApplicabilityStatus *ApplicabilityStatusCount +} + +type ViolationCount struct { + // Expected number of violations by scan type (SCA/JAS) + ValidateScan *ScanCount + // Expected number of contextual statuses for violations (sca/secrets) + ValidateApplicabilityStatus *ApplicabilityStatusCount + // Expected number of violations by violation type (license, operational, security: SCA+JAS) + ValidateType *ScaViolationCount +} + +type ScaViolationCount struct { + // Expected number of security violations (Sca, JAS) + Security int + // Expected number of license violations + License int + // Expected number of operational violations + Operational int +} + +type ApplicabilityStatusCount struct { + // Expected number of 'Applicable' contextual-analysis statuses for the issues (sca) + Applicable int + // Expected number of 'Undetermined' contextual-analysis statuses for the issues (sca) + Undetermined int + // Expected number of 'NotCovered' contextual-analysis statuses for the issues (sca) + NotCovered int + // Expected number of 'NotApplicable' contextual-analysis statuses for the issues (sca) + NotApplicable int + // Expected number of 'MissingContext' contextual-analysis statuses for the issues (sca) + MissingContext int + // Expected number of 'Inactive' contextual-analysis statuses for the issues (secrets) + Inactive int } // Validation allows to validate/assert a content with expected values. @@ -204,10 +256,102 @@ func errMsg(expected, actual string, msg string) []string { // If exactMatch is true, the content must match exactly. // If at least one validation fails, the function returns false and stops validating the rest of the pairs. func ValidateContent(t *testing.T, exactMatch bool, validations ...Validation) bool { + validationSuccess := true for _, validation := range validations { if !validation.Validate(t, exactMatch) { - return false + validationSuccess = false } } - return true + return validationSuccess +} + +type validationCountActualValues struct { + // Total counts + Vulnerabilities, Violations, Licenses int + // Vulnerabilities counts + SastVulnerabilities, SecretsVulnerabilities, IacVulnerabilities, ScaVulnerabilities int + ApplicableVulnerabilities, UndeterminedVulnerabilities, NotCoveredVulnerabilities, NotApplicableVulnerabilities, MissingContextVulnerabilities, InactiveSecretsVulnerabilities int + // Violations counts + SastViolations, SecretsViolations, IacViolations, ScaViolations int + SecurityViolations, LicenseViolations, OperationalViolations int + ApplicableViolations, UndeterminedViolations, NotCoveredViolations, NotApplicableViolations, MissingContextViolations, InactiveSecretsViolations int +} + +func ValidateCount(t *testing.T, outputType string, params ValidationParams, actual validationCountActualValues) { + ValidateTotalCount(t, outputType, params.ExactResultsMatch, params.Total, actual.Vulnerabilities, actual.Violations, actual.Licenses) + ValidateVulnerabilitiesCount(t, outputType, params.ExactResultsMatch, params.Vulnerabilities, actual) + ValidateViolationCount(t, outputType, params.ExactResultsMatch, params.Violations, actual) +} + +func ValidateTotalCount(t *testing.T, outputType string, exactMatch bool, params *TotalCount, vulnerabilities, violations, license int) { + if params == nil { + return + } + ValidateContent(t, exactMatch, + CountValidation[int]{Expected: params.Vulnerabilities, Actual: vulnerabilities, Msg: GetValidationCountErrMsg("vulnerabilities", outputType, exactMatch, params.Vulnerabilities, vulnerabilities)}, + CountValidation[int]{Expected: params.Violations, Actual: violations, Msg: GetValidationCountErrMsg("violations", outputType, exactMatch, params.Violations, violations)}, + CountValidation[int]{Expected: params.Licenses, Actual: license, Msg: GetValidationCountErrMsg("licenses", outputType, exactMatch, params.Licenses, license)}, + ) +} + +func ValidateVulnerabilitiesCount(t *testing.T, outputType string, exactMatch bool, params *VulnerabilityCount, actual validationCountActualValues) { + if params == nil { + return + } + ValidateScanTypeCount(t, outputType, false, exactMatch, params.ValidateScan, actual.ScaVulnerabilities, actual.SastVulnerabilities, actual.SecretsVulnerabilities, actual.IacVulnerabilities) + ValidateApplicabilityStatusCount(t, outputType, false, exactMatch, params.ValidateApplicabilityStatus, actual.ApplicableVulnerabilities, actual.UndeterminedVulnerabilities, actual.NotCoveredVulnerabilities, actual.NotApplicableVulnerabilities, actual.MissingContextVulnerabilities, actual.InactiveSecretsVulnerabilities) +} + +func ValidateViolationCount(t *testing.T, outputType string, exactMatch bool, params *ViolationCount, actual validationCountActualValues) { + if params == nil { + return + } + ValidateScanTypeCount(t, outputType, true, exactMatch, params.ValidateScan, actual.ScaViolations, actual.SastViolations, actual.SecretsViolations, actual.IacViolations) + ValidateApplicabilityStatusCount(t, outputType, true, exactMatch, params.ValidateApplicabilityStatus, actual.ApplicableViolations, actual.UndeterminedViolations, actual.NotCoveredViolations, actual.NotApplicableViolations, actual.MissingContextViolations, actual.InactiveSecretsViolations) + ValidateScaViolationCount(t, outputType, exactMatch, params.ValidateType, actual.SecurityViolations, actual.LicenseViolations, actual.OperationalViolations) +} + +func ValidateScanTypeCount(t *testing.T, outputType string, violation, exactMatch bool, params *ScanCount, scaViolations, sastViolations, secretsViolations, iacViolations int) { + if params == nil { + return + } + suffix := "vulnerabilities" + if violation { + suffix = "violations" + } + ValidateContent(t, exactMatch, + CountValidation[int]{Expected: params.Sast, Actual: sastViolations, Msg: GetValidationCountErrMsg(fmt.Sprintf("sast %s", suffix), outputType, exactMatch, params.Sast, sastViolations)}, + CountValidation[int]{Expected: params.Secrets, Actual: secretsViolations, Msg: GetValidationCountErrMsg(fmt.Sprintf("secrets %s", suffix), outputType, exactMatch, params.Secrets, secretsViolations)}, + CountValidation[int]{Expected: params.Iac, Actual: iacViolations, Msg: GetValidationCountErrMsg(fmt.Sprintf("IaC %s", suffix), outputType, exactMatch, params.Iac, iacViolations)}, + CountValidation[int]{Expected: params.Sca, Actual: scaViolations, Msg: GetValidationCountErrMsg(fmt.Sprintf("Sca %s", suffix), outputType, exactMatch, params.Sca, scaViolations)}, + ) +} + +func ValidateApplicabilityStatusCount(t *testing.T, outputType string, violation, exactMatch bool, params *ApplicabilityStatusCount, applicableResults, undeterminedResults, notCoveredResults, notApplicableResults, missingContextResults, inactiveSecrets int) { + if params == nil { + return + } + suffix := "vulnerabilities" + if violation { + suffix = "violations" + } + ValidateContent(t, exactMatch, + CountValidation[int]{Expected: params.Applicable, Actual: applicableResults, Msg: GetValidationCountErrMsg(fmt.Sprintf("applicable %s", suffix), outputType, exactMatch, params.Applicable, applicableResults)}, + CountValidation[int]{Expected: params.Undetermined, Actual: undeterminedResults, Msg: GetValidationCountErrMsg(fmt.Sprintf("undetermined %s", suffix), outputType, exactMatch, params.Undetermined, undeterminedResults)}, + CountValidation[int]{Expected: params.NotCovered, Actual: notCoveredResults, Msg: GetValidationCountErrMsg(fmt.Sprintf("not covered %s", suffix), outputType, exactMatch, params.NotCovered, notCoveredResults)}, + CountValidation[int]{Expected: params.NotApplicable, Actual: notApplicableResults, Msg: GetValidationCountErrMsg(fmt.Sprintf("not applicable %s", suffix), outputType, exactMatch, params.NotApplicable, notApplicableResults)}, + CountValidation[int]{Expected: params.MissingContext, Actual: missingContextResults, Msg: GetValidationCountErrMsg(fmt.Sprintf("missing context %s", suffix), outputType, exactMatch, params.MissingContext, missingContextResults)}, + CountValidation[int]{Expected: params.Inactive, Actual: inactiveSecrets, Msg: GetValidationCountErrMsg(fmt.Sprintf("inactive secrets %s", suffix), outputType, exactMatch, params.Inactive, inactiveSecrets)}, + ) +} + +func ValidateScaViolationCount(t *testing.T, outputType string, exactMatch bool, params *ScaViolationCount, securityViolations, licenseViolations, operationalViolations int) { + if params == nil { + return + } + ValidateContent(t, exactMatch, + CountValidation[int]{Expected: params.Security, Actual: securityViolations, Msg: GetValidationCountErrMsg("security violations", outputType, exactMatch, params.Security, securityViolations)}, + CountValidation[int]{Expected: params.License, Actual: licenseViolations, Msg: GetValidationCountErrMsg("license violations", outputType, exactMatch, params.License, licenseViolations)}, + CountValidation[int]{Expected: params.Operational, Actual: operationalViolations, Msg: GetValidationCountErrMsg("operational risk violations", outputType, exactMatch, params.Operational, operationalViolations)}, + ) } diff --git a/utils/xray/scangraph/params.go b/utils/xray/scangraph/params.go index eb51f2ee..787c5869 100644 --- a/utils/xray/scangraph/params.go +++ b/utils/xray/scangraph/params.go @@ -2,26 +2,18 @@ package scangraph import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" + "github.com/jfrog/jfrog-cli-security/utils/techutils" "github.com/jfrog/jfrog-client-go/xray/services" ) type ScanGraphParams struct { serverDetails *config.ServerDetails + technology techutils.Technology xrayGraphScanParams *services.XrayGraphScanParams fixableOnly bool - xrayVersion string severityLevel int } -type CommonGraphScanParams struct { - RepoPath string - ProjectKey string - Watches []string - ScanType services.ScanType - IncludeVulnerabilities bool - IncludeLicenses bool -} - func NewScanGraphParams() *ScanGraphParams { return &ScanGraphParams{} } @@ -36,11 +28,6 @@ func (sgp *ScanGraphParams) SetXrayGraphScanParams(params *services.XrayGraphSca return sgp } -func (sgp *ScanGraphParams) SetXrayVersion(xrayVersion string) *ScanGraphParams { - sgp.xrayVersion = xrayVersion - return sgp -} - func (sgp *ScanGraphParams) SetSeverityLevel(severity string) *ScanGraphParams { sgp.severityLevel = getLevelOfSeverity(severity) return sgp @@ -50,10 +37,6 @@ func (sgp *ScanGraphParams) XrayGraphScanParams() *services.XrayGraphScanParams return sgp.xrayGraphScanParams } -func (sgp *ScanGraphParams) XrayVersion() string { - return sgp.xrayVersion -} - func (sgp *ScanGraphParams) ServerDetails() *config.ServerDetails { return sgp.serverDetails } @@ -66,3 +49,12 @@ func (sgp *ScanGraphParams) SetFixableOnly(fixable bool) *ScanGraphParams { sgp.fixableOnly = fixable return sgp } + +func (sgp *ScanGraphParams) SetTechnology(technology techutils.Technology) *ScanGraphParams { + sgp.technology = technology + return sgp +} + +func (sgp *ScanGraphParams) Technology() techutils.Technology { + return sgp.technology +} diff --git a/utils/xray/scangraph/scangraph.go b/utils/xray/scangraph/scangraph.go index 253c17dc..1917388a 100644 --- a/utils/xray/scangraph/scangraph.go +++ b/utils/xray/scangraph/scangraph.go @@ -16,7 +16,7 @@ const ( ) func RunScanGraphAndGetResults(params *ScanGraphParams, xrayManager *xray.XrayServicesManager) (*services.ScanResponse, error) { - err := clientutils.ValidateMinimumVersion(clientutils.Xray, params.xrayVersion, ScanTypeMinXrayVersion) + err := clientutils.ValidateMinimumVersion(clientutils.Xray, params.xrayGraphScanParams.XrayVersion, ScanTypeMinXrayVersion) if err != nil { // Remove scan type param if Xray version is under the minimum supported version params.xrayGraphScanParams.ScanType = "" @@ -28,7 +28,7 @@ func RunScanGraphAndGetResults(params *ScanGraphParams, xrayManager *xray.XraySe } xscEnabled := params.xrayGraphScanParams.XscVersion != "" && params.xrayGraphScanParams.MultiScanId != "" - scanResult, err := xrayManager.GetScanGraphResults(scanId, params.xrayVersion, params.XrayGraphScanParams().IncludeVulnerabilities, params.XrayGraphScanParams().IncludeLicenses, xscEnabled) + scanResult, err := xrayManager.GetScanGraphResults(scanId, params.xrayGraphScanParams.XrayVersion, params.XrayGraphScanParams().IncludeVulnerabilities, params.XrayGraphScanParams().IncludeLicenses, xscEnabled) if err != nil { return nil, err } diff --git a/utils/xsc/analyticsmetrics.go b/utils/xsc/analyticsmetrics.go index a72ffa55..27cd7c95 100644 --- a/utils/xsc/analyticsmetrics.go +++ b/utils/xsc/analyticsmetrics.go @@ -38,7 +38,7 @@ func SendNewScanEvent(xrayVersion, xscVersion string, serviceDetails *config.Ser log.Debug("Analytics metrics are disabled, skip sending event request to XSC") return } - xscService, err := CreateXscService(xrayVersion, serviceDetails) + xscService, err := CreateXscServiceBackwardCompatible(xrayVersion, serviceDetails) if err != nil { log.Debug(fmt.Sprintf("failed to create xsc manager for analytics metrics service, error: %s ", err.Error())) return @@ -60,7 +60,7 @@ func SendScanEndedEvent(xrayVersion, xscVersion string, serviceDetails *config.S return } // Generate the finalize event. - xscService, err := CreateXscService(xrayVersion, serviceDetails) + xscService, err := CreateXscServiceBackwardCompatible(xrayVersion, serviceDetails) if err != nil { log.Debug(fmt.Sprintf("failed to create xsc manager for analytics metrics service, skip sending command finalize event, error: %s ", err.Error())) return @@ -115,7 +115,7 @@ func GetScanEvent(xrayVersion, xscVersion, multiScanId string, serviceDetails *c log.Debug("Can't get general event from XSC - analytics metrics are disabled.") return nil, nil } - xscService, err := CreateXscService(xrayVersion, serviceDetails) + xscService, err := CreateXscServiceBackwardCompatible(xrayVersion, serviceDetails) if err != nil { log.Debug(fmt.Sprintf("failed to create xsc manager for analytics metrics service, skip getting general event, error: %s ", err.Error())) return nil, err diff --git a/utils/xsc/analyticsmetrics_test.go b/utils/xsc/analyticsmetrics_test.go index a783eb42..db9905db 100644 --- a/utils/xsc/analyticsmetrics_test.go +++ b/utils/xsc/analyticsmetrics_test.go @@ -16,7 +16,6 @@ import ( "github.com/jfrog/jfrog-client-go/xray/services" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" xscutils "github.com/jfrog/jfrog-client-go/xsc/services/utils" - "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" ) @@ -191,22 +190,23 @@ func getDummyContentForGeneralEvent(withJas, withErr bool) *results.SecurityComm cmdResults.StartTime = time.Now() cmdResults.MultiScanId = "msi" scanResults := cmdResults.NewScanResults(results.ScanTarget{Target: "target"}) - scanResults.NewScaScanResults(services.ScanResponse{Vulnerabilities: vulnerabilities}) + scanResults.NewScaScanResults(0, services.ScanResponse{Vulnerabilities: vulnerabilities}) if withJas { - scanResults.JasResults.ApplicabilityScanResults = []*sarif.Run{sarifutils.CreateRunWithDummyResults(sarifutils.CreateDummyPassingResult("applic_CVE-123"))} - scanResults.JasResults.SecretsScanResults = []*sarif.Run{ + scanResults.JasResults.ApplicabilityScanResults = validations.NewMockJasRuns(sarifutils.CreateRunWithDummyResults(sarifutils.CreateDummyPassingResult("applic_CVE-123"))) + + scanResults.JasResults.JasVulnerabilities.SecretsScanResults = validations.NewMockJasRuns( sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithLocations("", "", "note", sarifutils.CreateLocation("", 0, 0, 0, 0, ""))), sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithLocations("", "", "note", sarifutils.CreateLocation("", 1, 1, 1, 1, ""))), - } - scanResults.JasResults.IacScanResults = []*sarif.Run{ + ) + scanResults.JasResults.JasVulnerabilities.IacScanResults = validations.NewMockJasRuns( sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithLocations("", "", "note", sarifutils.CreateLocation("", 0, 0, 0, 0, ""))), sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithLocations("", "", "note", sarifutils.CreateLocation("", 1, 1, 1, 1, ""))), - } - scanResults.JasResults.SastScanResults = []*sarif.Run{ + ) + scanResults.JasResults.JasVulnerabilities.SastScanResults = validations.NewMockJasRuns( sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithLocations("", "", "note", sarifutils.CreateLocation("", 0, 0, 0, 0, ""))), sarifutils.CreateRunWithDummyResults(sarifutils.CreateResultWithLocations("", "", "note", sarifutils.CreateLocation("", 1, 1, 1, 1, ""))), - } + ) } if withErr { diff --git a/utils/xsc/configprofile.go b/utils/xsc/configprofile.go index eb1066d4..616e13a2 100644 --- a/utils/xsc/configprofile.go +++ b/utils/xsc/configprofile.go @@ -2,6 +2,7 @@ package xsc import ( "fmt" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" clientutils "github.com/jfrog/jfrog-client-go/utils" "github.com/jfrog/jfrog-client-go/utils/log" @@ -15,7 +16,7 @@ func GetConfigProfileByName(xrayVersion, xscVersion string, serverDetails *confi return nil, err } - xscService, err := CreateXscService(xrayVersion, serverDetails) + xscService, err := CreateXscServiceBackwardCompatible(xrayVersion, serverDetails) if err != nil { return nil, err } @@ -31,11 +32,10 @@ func GetConfigProfileByUrl(xrayVersion string, serverDetails *config.ServerDetai log.Info(fmt.Sprintf("Minimal Xray version required to utilize config profile by url is '%s'. All configurations will be induced from provided Env vars and files", services.ConfigProfileByUrlMinXrayVersion)) return nil, err } - xscService, err := CreateXscService(xrayVersion, serverDetails) + xscService, err := CreateXscService(serverDetails) if err != nil { return nil, err } - configProfile, err := xscService.GetConfigProfileByUrl(cloneRepoUrl) if err != nil { err = fmt.Errorf("failed to get config profile for url '%s': %q", serverDetails.Url, err) diff --git a/utils/xsc/errorreport.go b/utils/xsc/errorreport.go index 83762b4f..6ee06212 100644 --- a/utils/xsc/errorreport.go +++ b/utils/xsc/errorreport.go @@ -18,7 +18,7 @@ func ReportError(xrayVersion, xscVersion string, serverDetails *config.ServerDet return nil } log.Debug("Sending an error report to JFrog analytics...") - xscService, err := CreateXscService(xrayVersion, serverDetails) + xscService, err := CreateXscServiceBackwardCompatible(xrayVersion, serverDetails) if err != nil { return fmt.Errorf("failed to create an HTTP client: %s.\nReporting to JFrog analytics is skipped", err.Error()) } diff --git a/utils/xsc/xscmanager.go b/utils/xsc/xscmanager.go index 8650da88..c799243e 100644 --- a/utils/xsc/xscmanager.go +++ b/utils/xsc/xscmanager.go @@ -14,14 +14,14 @@ import ( const MinXscVersionForErrorReport = "1.7.7" -func CreateXscService(xrayVersion string, serviceDetails *config.ServerDetails) (xsc.XscService, error) { +func CreateXscServiceBackwardCompatible(xrayVersion string, serviceDetails *config.ServerDetails) (xsc.XscService, error) { if xscservicesutils.IsXscXrayInnerService(xrayVersion) { - return createXscService(serviceDetails) + return CreateXscService(serviceDetails) } return createDeprecatedXscServiceManager(serviceDetails) } -func createXscService(serviceDetails *config.ServerDetails) (*xscservices.XscInnerService, error) { +func CreateXscService(serviceDetails *config.ServerDetails) (*xscservices.XscInnerService, error) { xrayManager, err := xray.CreateXrayServiceManager(serviceDetails) if err != nil { return nil, err @@ -59,7 +59,7 @@ func GetJfrogServicesVersion(serverDetails *config.ServerDetails) (xrayVersion, return } log.Debug("Xray version: " + xrayVersion) - xscService, err := CreateXscService(xrayVersion, serverDetails) + xscService, err := CreateXscServiceBackwardCompatible(xrayVersion, serverDetails) if err != nil { return } diff --git a/xsc_test.go b/xsc_test.go index 0368eebc..b081d8f0 100644 --- a/xsc_test.go +++ b/xsc_test.go @@ -3,12 +3,17 @@ package main import ( "encoding/json" "errors" + "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/jfrog/jfrog-cli-core/v2/common/format" + "github.com/jfrog/jfrog-cli-core/v2/common/progressbar" + "github.com/jfrog/jfrog-cli-core/v2/plugins/components" + "github.com/jfrog/jfrog-cli-security/cli" + "github.com/jfrog/jfrog-cli-security/cli/docs" "github.com/jfrog/jfrog-cli-security/utils/formats" "github.com/jfrog/jfrog-cli-security/utils/validations" "github.com/jfrog/jfrog-cli-security/utils/xsc" @@ -17,7 +22,10 @@ import ( securityTestUtils "github.com/jfrog/jfrog-cli-security/tests/utils" "github.com/jfrog/jfrog-cli-security/tests/utils/integration" + "github.com/jfrog/jfrog-client-go/xray/services" + "github.com/jfrog/jfrog-client-go/xray/services/utils" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" + xscutils "github.com/jfrog/jfrog-client-go/xsc/services/utils" ) func TestReportError(t *testing.T) { @@ -33,8 +41,7 @@ func TestXscAuditNpmJsonWithWatch(t *testing.T) { defer cleanUp() output := testAuditNpm(t, string(format.Json), false) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - SecurityViolations: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Violations: 1}, }) } @@ -43,19 +50,119 @@ func TestXscAuditNpmSimpleJsonWithWatch(t *testing.T) { defer cleanUp() output := testAuditNpm(t, string(format.SimpleJson), true) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - SecurityViolations: 1, - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Violations: 1, Vulnerabilities: 1}, }) } +func TestXscAuditViolationsWithIgnoreRule(t *testing.T) { + // Init XSC tests also enabled analytics reporting. + _, _, cleanUpXsc := integration.InitXscTest(t, func() { securityTestUtils.ValidateXrayVersion(t, services.MinXrayVersionGitRepoKey) }) + defer cleanUpXsc() + // Create the audit command with git repo context injected. + cliToRun, cleanUpHome := integration.InitTestWithMockCommandOrParams(t, false, getAuditCommandWithXscGitContext(validations.TestMockGitInfo)) + defer cleanUpHome() + // Create the project to scan + _, cleanUpProject := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "projects", "jas", "jas")) + defer cleanUpProject() + // Create policy and watch for the git repo so we will also get violations (unknown = all vulnerabilities will be reported as violations) + policyName, cleanUpPolicy := securityTestUtils.CreateTestSecurityPolicy(t, "git-repo-ignore-rule-policy", utils.Unknown, true) + defer cleanUpPolicy() + _, cleanUpWatch := securityTestUtils.CreateWatchForTests(t, policyName, "git-repo-ignore-rule-watch", xscutils.GetGitRepoUrlKey(validations.TestMockGitInfo.GitRepoHttpsCloneUrl)) + defer cleanUpWatch() + // Run the audit command with git repo and verify violations are reported to the platform. + output := testAuditCommand(t, cliToRun, auditCommandTestParams{Format: string(format.SimpleJson), WithLicense: true, WithVuln: true}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ + Total: &validations.TotalCount{Licenses: 3, Violations: 26, Vulnerabilities: 39}, + // Check that we have at least one violation for each scan type. (IAC is not supported yet) + Violations: &validations.ViolationCount{ValidateScan: &validations.ScanCount{Sca: 1, Sast: 1, Secrets: 1}}, + }) + // Create an ignore rules for the git repo + cleanUpCveIgnoreRule := securityTestUtils.CreateTestIgnoreRules(t, "security cli tests - Sca ignore rule", utils.IgnoreFilters{ + GitRepositories: []string{xscutils.GetGitRepoUrlKey(validations.TestMockGitInfo.GitRepoHttpsCloneUrl)}, + CVEs: []string{"any"}, Licenses: []string{"any"}, + }) + defer cleanUpCveIgnoreRule() + cleanUpExposureIgnoreRule := securityTestUtils.CreateTestIgnoreRules(t, "security cli tests - Exposure ignore rule", utils.IgnoreFilters{ + GitRepositories: []string{xscutils.GetGitRepoUrlKey(validations.TestMockGitInfo.GitRepoHttpsCloneUrl)}, + Exposures: &utils.ExposuresFilterName{Categories: []utils.ExposureType{utils.SecretExposureType, utils.IacExposureType}}, + }) + defer cleanUpExposureIgnoreRule() + cleanSastUpIgnoreRule := securityTestUtils.CreateTestIgnoreRules(t, "security cli tests - Sast ignore rule", utils.IgnoreFilters{ + GitRepositories: []string{xscutils.GetGitRepoUrlKey(validations.TestMockGitInfo.GitRepoHttpsCloneUrl)}, + Sast: &utils.SastFilterName{Rule: []string{"any"}}, + }) + defer cleanSastUpIgnoreRule() + // Run the audit command and verify no issues. (all violations are ignored) + output = testAuditCommand(t, cliToRun, auditCommandTestParams{Format: string(format.SimpleJson)}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ExactResultsMatch: true, Total: &validations.TotalCount{}, Violations: &validations.ViolationCount{ValidateScan: &validations.ScanCount{}}}) +} + +func TestAuditJasViolationsProjectKeySimpleJson(t *testing.T) { + _, _, cleanUpXsc := integration.InitXscTest(t, func() { securityTestUtils.ValidateXrayVersion(t, services.MinXrayVersionGitRepoKey) }) + defer cleanUpXsc() + if tests.TestJfrogPlatformProjectKeyEnvVar == "" { + t.Skipf("skipping test. %s is not set.", tests.TestJfrogPlatformProjectKeyEnvVar) + } + // Create the audit command with git repo context injected. + cliToRun, cleanUpHome := integration.InitTestWithMockCommandOrParams(t, false, getAuditCommandWithXscGitContext(validations.TestMockGitInfo)) + defer cleanUpHome() + + // Create the project to scan + _, cleanUpProject := securityTestUtils.CreateTestProjectEnvAndChdir(t, filepath.Join(filepath.FromSlash(tests.GetTestResourcesPath()), "projects", "jas", "jas")) + defer cleanUpProject() + // Create policy and watch for the project so we will get violations (unknown = all vulnerabilities will be reported as violations) + policyName, cleanUpPolicy := securityTestUtils.CreateTestSecurityPolicy(t, "project-key-jas-violations-policy", utils.Unknown, false) + defer cleanUpPolicy() + _, cleanUpWatch := securityTestUtils.CreateTestProjectKeyWatch(t, policyName, "project-key-jas-violations-watch", *tests.JfrogTestProjectKey) + defer cleanUpWatch() + // Run the audit command with project key and verify violations are reported. + output := testAuditCommand(t, cliToRun, auditCommandTestParams{Format: string(format.SimpleJson), ProjectKey: *tests.JfrogTestProjectKey}) + validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ + Total: &validations.TotalCount{Violations: 14}, + // Check that we have at least one violation for each scan type. (IAC is not supported yet) + Violations: &validations.ViolationCount{ValidateScan: &validations.ScanCount{Sca: 1, Sast: 1, Secrets: 1}}, + }) +} + +// TODO: replace with 'Git Audit' command when it will be available. +// This method generate an audit command that will report analytics (if enabled) with the git info context provided. +// The method will generate multi-scan-id and provide it to the audit command. +// The method will also provide the git repo clone url to the audit command. +func getAuditCommandWithXscGitContext(gitInfoContext xscservices.XscGitInfoContext) func() components.Command { + return func() components.Command { + return components.Command{ + Name: docs.Audit, + Flags: docs.GetCommandFlags(docs.Audit), + Action: func(c *components.Context) error { + xrayVersion, xscVersion, serverDetails, auditCmd, err := cli.CreateAuditCmd(c) + if err != nil { + return err + } + // Generate the analytics event with the git info context. + event := xsc.CreateAnalyticsEvent(xscservices.CliProduct, xscservices.CliEventType, serverDetails) + event.GitInfo = &gitInfoContext + event.IsGitInfoFlow = true + // Report analytics and get the multi scan id that was generated and attached to the git context. + multiScanId, startTime := xsc.SendNewScanEvent(xrayVersion, xscVersion, serverDetails, event) + // Set the multi scan id to the audit command to be used in the scans. + auditCmd.SetMultiScanId(multiScanId) + // Set the git repo context to the audit command to pass to the scanners to create violations if applicable. + auditCmd.SetGitRepoHttpsCloneUrl(gitInfoContext.GitRepoHttpsCloneUrl) + err = progressbar.ExecWithProgress(auditCmd) + // Send the final event to the platform. + xsc.SendScanEndedEvent(xrayVersion, xscVersion, serverDetails, multiScanId, startTime, 0, err) + return err + }, + } + } +} + func TestXscAuditMavenJson(t *testing.T) { _, _, cleanUp := integration.InitXscTest(t) defer cleanUp() output := testAuditMaven(t, string(format.Json)) validations.VerifyJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) } @@ -64,8 +171,7 @@ func TestXscAuditMavenSimpleJson(t *testing.T) { defer cleanUp() output := testAuditMaven(t, string(format.SimpleJson)) validations.VerifySimpleJsonResults(t, output, validations.ValidationParams{ - Vulnerabilities: 1, - Licenses: 1, + Total: &validations.TotalCount{Licenses: 1, Vulnerabilities: 1}, }) }