Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 28 additions & 17 deletions internal/tool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,32 +81,43 @@ func (t codacyTrivy) Run(ctx context.Context, toolExecution codacy.ToolExecution
// This is the only way to suppress Trivy logs.
log.InitLogger(false, true)

report, err := t.runBaseScan(ctx, toolExecution.SourceDir)
if err != nil {
return nil, err
}
allIssues := []codacy.Result{}

sbom, err := t.getSBOM(ctx, report)
if err != nil {
return nil, err
}
// The dependency scan covers the whole source directory regardless of the requested files, and the SBOM is derived
// from it, so both are wasted work when the execution has no SCA pattern to report them under.
if scaScanningEnabled(*toolExecution.Patterns) {
report, err := t.runBaseScan(ctx, toolExecution.SourceDir)
if err != nil {
return nil, err
}

vulnerabilityScanningIssues, err := t.getVulnerabilities(ctx, report, toolExecution)
if err != nil {
return nil, err
}
sbom, err := t.getSBOM(ctx, report)
if err != nil {
return nil, err
}

secretScanningIssues := t.runSecretScanning(toolExecution)
vulnerabilityScanningIssues, err := t.getVulnerabilities(ctx, report, toolExecution)
if err != nil {
return nil, err
}

maliciousPackagesIssues := t.maliciousPackagesScanner.Scan(report, toolExecution)
allIssues = append(allIssues, vulnerabilityScanningIssues...)
allIssues = append(allIssues, t.maliciousPackagesScanner.Scan(report, toolExecution)...)
allIssues = append(allIssues, sbom)
}

allIssues := append(vulnerabilityScanningIssues, secretScanningIssues...)
allIssues = append(allIssues, maliciousPackagesIssues...)
allIssues = append(allIssues, sbom)
allIssues = append(allIssues, t.runSecretScanning(toolExecution)...)

return allIssues, nil
}

// scaScanningEnabled returns whether any of the given patterns needs the dependency scan.
func scaScanningEnabled(patterns []codacy.Pattern) bool {
return lo.SomeBy(patterns, func(p codacy.Pattern) bool {
return p.ID == ruleIDMaliciousPackages || lo.Contains(ruleIDsVulnerability, p.ID)
})
}

// runBaseScan will run a vulnerability scan that produces a report to be used for SBOM generation or for vulnerability issues.
func (t codacyTrivy) runBaseScan(ctx context.Context, sourceDir string) (ptypes.Report, error) {
config := flag.Options{
Expand Down
69 changes: 69 additions & 0 deletions internal/tool/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,75 @@ func TestRunVulnerabilityScanningNotEnabled(t *testing.T) {
assert.Empty(t, results)
}

func TestRunSkipsDependencyScanWhenScaNotEnabled(t *testing.T) {
// Arrange
srcDir, err := os.MkdirTemp("", "")
if err != nil {
assert.FailNow(t, "Failed to create tmp directory", err.Error())
}
defer os.RemoveAll(srcDir)

f, err := os.CreateTemp(srcDir, "file-")
if err != nil {
assert.FailNow(t, "Failed to create tmp file", err.Error())
}
defer f.Close()

if _, err := f.Write([]byte("AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF")); err != nil {
assert.FailNow(t, "Failed to write to tmp file", err.Error())
}

toolExecution := codacy.ToolExecution{
Patterns: &[]codacy.Pattern{{ID: ruleIDSecret}},
Files: &[]string{filepath.Base(f.Name())},
SourceDir: srcDir,
}

// A factory that only fails: reaching it at all would surface as an error from Run.
underTest := codacyTrivy{runnerFactory: errorRunnerFactory{err: assert.AnError}}

// Act
results, err := underTest.Run(context.Background(), toolExecution)

// Assert
assert.NoError(t, err)
assert.NotEmpty(t, results, "secret scanning still reports")

sboms := lo.Filter(results, func(result codacy.Result, _ int) bool {
_, isSBOM := result.(codacy.SBOM)
return isSBOM
})
assert.Empty(t, sboms, "the SBOM is derived from the dependency scan, so there is none")
}

func TestScaScanningEnabled(t *testing.T) {
// Arrange
type testData struct {
name string
patterns []codacy.Pattern
expected bool
}

tests := []testData{
{name: "no patterns", patterns: []codacy.Pattern{}, expected: false},
{name: "only secret", patterns: []codacy.Pattern{{ID: ruleIDSecret}}, expected: false},
{name: "only unknown", patterns: []codacy.Pattern{{ID: "unknown"}}, expected: false},
{name: "a vulnerability severity", patterns: []codacy.Pattern{{ID: ruleIDVulnerabilityMinor}}, expected: true},
{name: "malicious packages", patterns: []codacy.Pattern{{ID: ruleIDMaliciousPackages}}, expected: true},
{
name: "secret alongside a vulnerability severity",
patterns: []codacy.Pattern{{ID: ruleIDSecret}, {ID: ruleIDVulnerabilityCritical}},
expected: true,
},
}

for _, testData := range tests {
t.Run(testData.name, func(t *testing.T) {
assert.Equal(t, testData.expected, scaScanningEnabled(testData.patterns))
})
}
}

func TestRunSecretScanningNotEnabled(t *testing.T) {
toolExecution := codacy.ToolExecution{
Patterns: &[]codacy.Pattern{{ID: ruleIDVulnerabilityMedium}},
Expand Down