Skip to content

Commit b3269a8

Browse files
authored
Add root dir to determine paths for sast changed files (#743)
1 parent 1ef672f commit b3269a8

4 files changed

Lines changed: 43 additions & 60 deletions

File tree

commands/audit/audit.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ func (auditCmd *AuditCommand) Run() (err error) {
268268
SetThirdPartyApplicabilityScan(auditCmd.thirdPartyApplicabilityScan).
269269
SetThreads(auditCmd.Threads).
270270
SetScansResultsOutputDir(auditCmd.scanResultsOutputDir).SetStartTime(startTime).SetMultiScanId(multiScanId).
271-
SetSastChangedFilesMode(auditCmd.sastChangedFilesMode).SetSastRules(auditCmd.sastRules)
271+
SetRootDir(auditCmd.rootDir).SetSastChangedFilesMode(auditCmd.sastChangedFilesMode).SetSastRules(auditCmd.sastRules)
272272
auditParams.SetIsRecursiveScan(isRecursiveScan).SetExclusions(auditCmd.Exclusions())
273273

274274
auditResults := RunAudit(auditParams)
@@ -759,7 +759,7 @@ func createJasScansTask(auditParallelRunner *utils.SecurityParallelRunner, scanR
759759
SignedDescriptions: getSignedDescriptions(auditParams.OutputFormat()),
760760
SastRules: auditParams.SastRules(),
761761
SastChangedFilesMode: auditParams.SastChangedFilesMode(),
762-
SastChangedFiles: sast.SastChangedFilesForTarget(auditParams.SastChangedFilesMode(), scanResults.GitContext, targetResult.Target, scanResults.GetCommonParentPath()),
762+
SastChangedFiles: sast.SastChangedFilesForTarget(auditParams.SastChangedFilesMode(), scanResults.GitContext, targetResult.Target, getRootDir(auditParams.rootDir, scanResults)),
763763
ScanResults: targetResult,
764764
TargetCount: len(scanResults.Targets),
765765
TargetOutputDir: auditParams.scanResultsOutputDir,
@@ -775,6 +775,13 @@ func createJasScansTask(auditParallelRunner *utils.SecurityParallelRunner, scanR
775775
}
776776
}
777777

778+
func getRootDir(rootDir string, scanResults *results.SecurityCommandResults) string {
779+
if rootDir != "" {
780+
return rootDir
781+
}
782+
return scanResults.GetCommonParentPath()
783+
}
784+
778785
func getSignedDescriptions(currentFormat format.OutputFormat) bool {
779786
allowEmojis, err := strconv.ParseBool(os.Getenv(utils.IsAllowEmojis))
780787
if err != nil {

commands/audit/auditparams.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type AuditParams struct {
2222
resultsContext results.ResultContext
2323
gitContext *xscServices.XscGitInfoContext
2424
workingDirs []string
25+
rootDir string
2526
installFunc func(tech string) error
2627
fixableOnly bool
2728
minSeverityFilter severityutils.Severity
@@ -71,6 +72,15 @@ func (params *AuditParams) WorkingDirs() []string {
7172
return params.workingDirs
7273
}
7374

75+
func (params *AuditParams) SetRootDir(rootDir string) *AuditParams {
76+
params.rootDir = rootDir
77+
return params
78+
}
79+
80+
func (params *AuditParams) RootDir() string {
81+
return params.rootDir
82+
}
83+
7484
func (params *AuditParams) SetMultiScanId(msi string) *AuditParams {
7585
params.multiScanId = msi
7686
return params

jas/sast/sastscanner.go

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -216,23 +216,15 @@ func (s sastChangedFileDropStats) anyDrops() bool {
216216
}
217217

218218
// collectSastChangedAbsPaths maps repo-relative (or absolute-under-repo) changed file paths to clean absolute
219-
// paths under commonAbs that belong to targetRel, deduplicating by absolute path.
220219
func collectSastChangedAbsPaths(commonAbs, targetRel string, changedFiles []string) (out []string, stats sastChangedFileDropStats) {
221220
seen := datastructures.MakeSet[string]()
222221
for _, cf := range changedFiles {
223-
cfSlash, ok := normalizeRepoRelativeChangedPath(commonAbs, cf)
224-
if !ok {
225-
log.Verbose(fmt.Sprintf("SAST changed files: invalid path: %s", cf))
226-
stats.invalidPath++
227-
continue
228-
}
229-
if !changedFileBelongsToTarget(targetRel, cfSlash) {
222+
if !changedFileBelongsToTarget(targetRel, cf) {
230223
log.Verbose(fmt.Sprintf("SAST changed files: outside target: %s", cf))
231224
stats.outsideTarget++
232225
continue
233226
}
234-
joined := filepath.Join(commonAbs, filepath.FromSlash(cfSlash))
235-
absPath, err := filepath.Abs(filepath.Clean(joined))
227+
absPath, err := filepath.Abs(filepath.Clean(filepath.Join(commonAbs, filepath.FromSlash(cf))))
236228
if err != nil {
237229
log.Verbose(fmt.Sprintf("SAST changed files: absolute path error: %s", err.Error()))
238230
stats.absError++
@@ -254,29 +246,22 @@ func collectSastChangedAbsPaths(commonAbs, targetRel string, changedFiles []stri
254246
return out, stats
255247
}
256248

257-
// SastChangedFilesForTarget returns absolute paths of changed files under commonParent that belong to targetPath
258-
// (paths from git are repo-relative or absolute under the repo). Only runs when changedFilesMode is true; only paths
259-
// that exist on disk are returned. Returns nil if nothing matches or if gitCtx, commonParent, or targetPath are unusable.
260-
func SastChangedFilesForTarget(changedFilesMode bool, gitCtx *xscservices.XscGitInfoContext, targetPath, commonParent string) []string {
249+
// SastChangedFilesForTarget returns absolute paths of changed files under the root directory that belong to the target path.
250+
func SastChangedFilesForTarget(changedFilesMode bool, gitCtx *xscservices.XscGitInfoContext, targetPath, rootDir string) []string {
261251
if gitCtx == nil || !changedFilesMode {
262252
return nil
263253
}
264254
if len(gitCtx.ChangedFiles) == 0 {
265255
log.Debug("SAST changed files: git context has no changed files; skipping per-file roots")
266256
return nil
267257
}
268-
if strings.TrimSpace(commonParent) == "" || strings.TrimSpace(targetPath) == "" {
258+
if strings.TrimSpace(rootDir) == "" || strings.TrimSpace(targetPath) == "" {
269259
log.Debug("SAST changed files: empty common parent or target path; skipping per-file roots")
270260
return nil
271261
}
272-
commonAbs, err := filepath.Abs(filepath.Clean(commonParent))
273-
if err != nil {
274-
log.Debug(fmt.Sprintf("SAST changed files: could not resolve common parent: %s", err.Error()))
275-
return nil
276-
}
277-
targetRel := filepath.ToSlash(utils.GetRelativePath(targetPath, commonParent))
262+
targetRel := filepath.ToSlash(utils.GetRelativePath(targetPath, rootDir))
278263
inputCount := len(gitCtx.ChangedFiles)
279-
out, stats := collectSastChangedAbsPaths(commonAbs, targetRel, gitCtx.ChangedFiles)
264+
out, stats := collectSastChangedAbsPaths(rootDir, targetRel, gitCtx.ChangedFiles)
280265
if stats.anyDrops() {
281266
log.Debug(fmt.Sprintf("SAST changed files: kept %d of %d changed-file entries (dropped: %d invalid/unsafe path, %d outside target, %d path resolution error, %d duplicate after normalization)",
282267
len(out), inputCount, stats.invalidPath, stats.outsideTarget, stats.absError, stats.duplicate))
@@ -285,26 +270,6 @@ func SastChangedFilesForTarget(changedFilesMode bool, gitCtx *xscservices.XscGit
285270
return out
286271
}
287272

288-
func normalizeRepoRelativeChangedPath(commonAbs, cf string) (slashPath string, ok bool) {
289-
cf = strings.TrimSpace(cf)
290-
if cf == "" {
291-
return "", false
292-
}
293-
if filepath.IsAbs(cf) {
294-
cleaned := filepath.Clean(cf)
295-
r, err := filepath.Rel(commonAbs, cleaned)
296-
if err != nil {
297-
return "", false
298-
}
299-
r = filepath.ToSlash(filepath.Clean(r))
300-
if r == ".." || strings.HasPrefix(r, "../") {
301-
return "", false
302-
}
303-
return r, true
304-
}
305-
return filepath.ToSlash(filepath.Clean(cf)), true
306-
}
307-
308273
func changedFileBelongsToTarget(targetRel, cfSlash string) bool {
309274
if targetRel == "" {
310275
return true

jas/sast/sastscanner_test.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ func TestSastRules(t *testing.T) {
209209
assert.Equal(t, filepath.Join(scannerTempDir, "results.sarif"), sastScanManager.resultsFileName)
210210
}
211211

212-
// xscGitInfoWithChanged builds an XscGitInfoContext the way the client defines it (GitDiffContext with changed_files).
212+
// xscGitInfoWithChanged builds an XscGitInfoContext the way the client defines it (GitDiffContext with changed files).
213213
// Must match the shape expected by SastChangedFilesForTarget in sastscanner.go.
214214
func xscGitInfoWithChanged(t *testing.T, files ...string) *xscservices.XscGitInfoContext {
215215
t.Helper()
@@ -237,61 +237,62 @@ func TestSastChangedFilesForTarget(t *testing.T) {
237237
name string
238238
gitCtx *xscservices.XscGitInfoContext
239239
targetPath string
240-
commonParent string
240+
rootDir string
241241
changedFilesMode bool
242242
// wantEmpty: expect no file roots (nil or empty slice) when mode is off or there is nothing to return.
243243
wantEmpty bool
244244
want []string
245245
}{
246-
{name: "nil_context", gitCtx: nil, targetPath: base, commonParent: base, changedFilesMode: true, wantEmpty: true},
247-
{name: "changed_files_mode_off", gitCtx: threeFiles, targetPath: modA, commonParent: base, changedFilesMode: false, wantEmpty: true},
248-
{name: "empty_changed_files", gitCtx: xscGitInfoWithChanged(t), targetPath: modA, commonParent: base, changedFilesMode: true, wantEmpty: true},
249-
{name: "empty_common_parent", gitCtx: threeFiles, targetPath: modA, commonParent: "", changedFilesMode: true, wantEmpty: true},
250-
{name: "empty_target_path", gitCtx: threeFiles, targetPath: "", commonParent: base, changedFilesMode: true, wantEmpty: true},
246+
{name: "nil_context", gitCtx: nil, targetPath: base, rootDir: base, changedFilesMode: true, wantEmpty: true},
247+
{name: "changed_files_mode_off", gitCtx: threeFiles, targetPath: modA, rootDir: base, changedFilesMode: false, wantEmpty: true},
248+
{name: "empty_changed_files", gitCtx: xscGitInfoWithChanged(t), targetPath: modA, rootDir: base, changedFilesMode: true, wantEmpty: true},
249+
{name: "empty_root_dir", gitCtx: threeFiles, targetPath: modA, rootDir: "", changedFilesMode: true, wantEmpty: true},
250+
{name: "empty_target_path", gitCtx: threeFiles, targetPath: "", rootDir: base, changedFilesMode: true, wantEmpty: true},
251251
{
252-
name: "target_is_common_parent_returns_all_as_abs",
252+
name: "target_is_repo_root_returns_all_as_abs",
253253
gitCtx: threeFiles,
254254
targetPath: base,
255-
commonParent: base,
255+
rootDir: base,
256256
changedFilesMode: true,
257257
want: []string{filepath.Join(base, "modA", "a.go"), filepath.Join(base, "modA", "b.go"), filepath.Join(base, "modB", "x.go")},
258258
},
259259
{
260260
name: "filters_to_modA_only",
261261
gitCtx: threeFiles,
262262
targetPath: modA,
263-
commonParent: base,
263+
rootDir: base,
264264
changedFilesMode: true,
265265
want: []string{filepath.Join(base, "modA", "a.go"), filepath.Join(base, "modA", "b.go")},
266266
},
267267
{
268268
name: "prefix_foo_does_not_match_foobar",
269269
gitCtx: &xscservices.XscGitInfoContext{GitDiffContext: xscservices.GitDiffContext{ChangedFiles: []string{"foo/x.go", "foobar/y.go"}}},
270270
targetPath: filepath.Join(base, "foo"),
271-
commonParent: base,
271+
rootDir: base,
272272
changedFilesMode: true,
273273
want: []string{filepath.Join(base, "foo", "x.go")},
274274
},
275275
{
276-
name: "absolute_changed_file_under_repo",
277-
gitCtx: xscGitInfoWithChanged(t, filepath.Join(base, "modA", "abs.go")),
276+
// belong-to-target matching uses repo-relative paths (as git reports); resolve to absolute under rootDir afterward.
277+
name: "repo_relative_changed_file_under_target",
278+
gitCtx: xscGitInfoWithChanged(t, "modA/abs.go"),
278279
targetPath: modA,
279-
commonParent: base,
280+
rootDir: base,
280281
changedFilesMode: true,
281282
want: []string{filepath.Join(base, "modA", "abs.go")},
282283
},
283284
{
284285
name: "deduplicates_same_paths",
285286
gitCtx: &xscservices.XscGitInfoContext{GitDiffContext: xscservices.GitDiffContext{ChangedFiles: []string{"modA/a.go", "modA/a.go", "./modA/a.go"}}},
286287
targetPath: modA,
287-
commonParent: base,
288+
rootDir: base,
288289
changedFilesMode: true,
289290
want: []string{filepath.Join(base, "modA", "a.go")},
290291
},
291292
}
292293
for _, tt := range tests {
293294
t.Run(tt.name, func(t *testing.T) {
294-
got := SastChangedFilesForTarget(tt.changedFilesMode, tt.gitCtx, tt.targetPath, tt.commonParent)
295+
got := SastChangedFilesForTarget(tt.changedFilesMode, tt.gitCtx, tt.targetPath, tt.rootDir)
295296
if tt.wantEmpty {
296297
assert.Empty(t, got, "SastChangedFilesForTarget should not return any paths in this case")
297298
} else {

0 commit comments

Comments
 (0)