diff --git a/commands/audit/audit.go b/commands/audit/audit.go index a29570a6c..6de28407f 100644 --- a/commands/audit/audit.go +++ b/commands/audit/audit.go @@ -474,6 +474,13 @@ func isEntitledForSnippetDetection(isEntitledForJas bool, xrayManager *xray.Xray func populateScanTargets(cmdResults *results.SecurityCommandResults, params *AuditParams) { // Populate x scan targets based on the provided parameters. detectScanTargets(cmdResults, params) + if detectedTechsGuardCallback := params.DetectedTechnologiesGuardCallback(); detectedTechsGuardCallback != nil { + if err := detectedTechsGuardCallback(cmdResults.GetTechnologies()); err != nil { + // allowSkippingError is hardcoded to false: this check must never be bypassable via AllowPartialResults. + cmdResults.AddGeneralError(err, false) + return + } + } // Populate target information for the scans for _, targetResult := range cmdResults.Targets { // Generate SBOM for the target if requested or for SCA scans. diff --git a/commands/audit/audit_test.go b/commands/audit/audit_test.go index e760c9faa..2256e844f 100644 --- a/commands/audit/audit_test.go +++ b/commands/audit/audit_test.go @@ -1,6 +1,7 @@ package audit import ( + "errors" "fmt" "net/http" "os" @@ -603,6 +604,49 @@ func TestDetectScanTargetsNewFlowCliExcludedCwdWithNonExcludedInclude(t *testing assert.True(t, hasNpm, "expected Npm among detected technologies") } +func TestPopulateScanTargetsDetectedTechnologiesGuardCallbackIsNotSkippable(t *testing.T) { + baseDir, cleanUp := createTestDir(t) + defer cleanUp() + + mavenDir := filepath.Join(baseDir, "maven-wd") + assert.NoError(t, os.MkdirAll(mavenDir, 0o755)) + createEmptyFile(t, filepath.Join(mavenDir, "pom.xml")) + + callbackErr := errors.New("environment guard failed") + + tests := []struct { + name string + allowPartialResults bool + }{ + { + name: "Partial results disabled - fail upon every error", + allowPartialResults: false, + }, + { + name: "allowPartialResults=true - callback error must still propagate", + allowPartialResults: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmdRes := results.NewCommandResults(utils.SourceCode).SetEntitledForJas(true).SetSecretValidation(true).SetAllowPartialResults(tt.allowPartialResults) + params := NewAuditParams() + params.SetWorkingDirs([]string{mavenDir}) + params.SetIsRecursiveScan(false) + params.SetBomGenerator(xrayplugin.NewXrayLibBomGenerator()) + params.SetDetectedTechnologiesGuardCallback(func(detected []techutils.Technology) error { + return callbackErr + }) + + populateScanTargets(cmdRes, params) + + // The callback error must surface via GetErrors() regardless of AllowPartialResults + assert.ErrorContains(t, cmdRes.GetErrors(), callbackErr.Error()) + }) + } +} + func TestShouldGenerateSbom(t *testing.T) { configProfileModulesWithSca := []services.Module{{ ScanConfig: services.ScanConfig{ diff --git a/commands/audit/auditparams.go b/commands/audit/auditparams.go index 80ff8e26f..67087d107 100644 --- a/commands/audit/auditparams.go +++ b/commands/audit/auditparams.go @@ -24,12 +24,14 @@ type AuditParams struct { appsConfig *jfrogappsconfig.JFrogAppsConfig workingDirs []string // Common params to all scan routines - resultsContext results.ResultContext - gitContext *xscServices.XscGitInfoContext - rootDir string - installFunc func(tech string) error - fixableOnly bool - minSeverityFilter severityutils.Severity + resultsContext results.ResultContext + gitContext *xscServices.XscGitInfoContext + rootDir string + installFunc func(tech string) error + // Optional hook invoked once technologies are detected for all targets, before any SBOM/dependency-tree generation runs (i.e. before any build-tool plugin executes untrusted code). A non-nil error aborts the scan. + detectedTechnologiesGuardCallback func(detectedTechnologies []techutils.Technology) error + fixableOnly bool + minSeverityFilter severityutils.Severity *AuditBasicParams multiScanId string // Include third party dependencies source code in the applicability scan. @@ -163,6 +165,15 @@ func (params *AuditParams) SetInstallFunc(installFunc func(tech string) error) * return params } +func (params *AuditParams) SetDetectedTechnologiesGuardCallback(callback func(detectedTechnologies []techutils.Technology) error) *AuditParams { + params.detectedTechnologiesGuardCallback = callback + return params +} + +func (params *AuditParams) DetectedTechnologiesGuardCallback() func(detectedTechnologies []techutils.Technology) error { + return params.detectedTechnologiesGuardCallback +} + func (params *AuditParams) FixableOnly() bool { return params.fixableOnly }