Add diff logic and parallel logger support for audit#642
Add diff logic and parallel logger support for audit#642
Conversation
…Results) - based on v1.24.2
- Add branchdiff.go with RunBranchDiffAudit function - Scans target branch first, then source branch (sequential) - SCA diff handled internally by CLI - JAS diff handled by CompareJasResults - Add MergeStatusCodes for partial results filtering - Status codes merged (worst of target + source) for frogbot filtering
- Add logger field to AuditBasicParams with getter/setter - RunAudit swaps global logger if custom logger provided - Enables frogbot to capture logs per-scan for ordered output
- Add LogCollector that captures logs in isolated buffer per audit - Enables parallel audits without log interleaving - Uses goroutine-local logger from jfrog-client-go - Propagate logger to child goroutines in JAS runner and SCA scan - Remove unused diff completion log message
attiasas
left a comment
There was a problem hiding this comment.
Nice work! checkout my comments, also:
- Update the PR description with the changes
- Adjust tests base on changes
commands/audit/logcollector.go
Outdated
| @@ -0,0 +1,41 @@ | |||
| package audit | |||
There was a problem hiding this comment.
maybe this should be at jfrog/jfrog-client-go#1297?
There was a problem hiding this comment.
these are the options
Move LogCollector to client-go - but it's really just a wrapper
Delete LogCollector entirely - Frogbot can use BufferedLogger directly from client-go
or we Keep it as is
There was a problem hiding this comment.
Removing code from repo is always good :)
If you already passing it in params, just use it no need for wrapper here
There was a problem hiding this comment.
doen tell me if you like it now
|
|
||
| // replace github.com/jfrog/froggit-go => github.com/jfrog/froggit-go master | ||
|
|
||
| replace github.com/jfrog/jfrog-client-go => github.com/eyalk007/jfrog-client-go v0.0.0-20260114112951-67b77f49255f |
There was a problem hiding this comment.
Reminder to remove replace after merging dependend PR
utils/results/diff.go
Outdated
|
|
||
| // CompareJasResults computes the diff between target and source JAS results. | ||
| // Returns only NEW findings in source that don't exist in target. | ||
| func CompareJasResults(targetResults, sourceResults *SecurityCommandResults) *SecurityCommandResults { |
There was a problem hiding this comment.
IDK about the func name, it does not compare, it filters Source results that exists at target
utils/results/diff.go
Outdated
| func extractLocationsOnly(run *sarif.Run, targetKeys map[string]bool) { | ||
| for _, result := range run.Results { | ||
| for _, location := range result.Locations { | ||
| key := getRelativeLocationFileName(location, run.Invocations) + getLocationSnippetText(location) |
There was a problem hiding this comment.
what about file name changes? it will not catch this...
why are we first filtering only locations and than by fingerprints, can we do it in one go? (reducing go over results multile times)
There was a problem hiding this comment.
For SAST: Uses fingerprints (precise_sink_and_sink_function) which are content-based, so renames don't matter
for secrets and iac -
This is a known limitation - AM doesn't handle file renames either. If you rename a file, existing findings in that file will show as "new" in the diff. This is acceptable
There was a problem hiding this comment.
We can write a todo and create a ticket for it, but i dont believe we should handle it now
commands/audit/logcollector.go
Outdated
| @@ -0,0 +1,41 @@ | |||
| package audit | |||
There was a problem hiding this comment.
Removing code from repo is always good :)
If you already passing it in params, just use it no need for wrapper here
commands/audit/audit.go
Outdated
| // Worker goroutines need this propagated so their logs are captured in the same buffer. | ||
| currentLogger := log.GetLogger() | ||
| jasTask := createJasScansTask(auditParallelRunner, scanResults, serverDetails, auditParams, jasScanner) | ||
| wrappedJasTask := func(threadId int) error { | ||
| // Propagate parent's logger to this worker goroutine for isolated log capture | ||
| log.SetLoggerForGoroutine(currentLogger) | ||
| defer log.ClearLoggerForGoroutine() | ||
| return jasTask(threadId) | ||
| } | ||
| if _, jasErr := auditParallelRunner.Runner.AddTaskWithError(wrappedJasTask, func(taskErr error) { |
There was a problem hiding this comment.
I think you can use WrapTaskWithLoggerPropagation here as well
| log.Info("[DIFF] New findings after diff - Secrets:", diffSecrets, "| IaC:", diffIac, "| SAST:", diffSast) | ||
| log.Info("[DIFF] Filtered out - Secrets:", sourceSecrets-diffSecrets, "| IaC:", sourceIac-diffIac, "| SAST:", sourceSast-diffSast) |
There was a problem hiding this comment.
| log.Info("[DIFF] New findings after diff - Secrets:", diffSecrets, "| IaC:", diffIac, "| SAST:", diffSast) | |
| log.Info("[DIFF] Filtered out - Secrets:", sourceSecrets-diffSecrets, "| IaC:", sourceIac-diffIac, "| SAST:", sourceSast-diffSast) | |
| log.Debug("[DIFF] New findings after diff - Secrets:", diffSecrets, "| IaC:", diffIac, "| SAST:", diffSast) | |
| log.Debug("[DIFF] Filtered out - Secrets:", sourceSecrets-diffSecrets, "| IaC:", sourceIac-diffIac, "| SAST:", sourceSast-diffSast) |
There was a problem hiding this comment.
i acutally think they are good info logs
its for hte userr to undestand the state post diff, im waiting on or to tell me what she thinks on logs @orto17
There was a problem hiding this comment.
talked to or she wants them as info
| // CompareJasResults computes the diff between target and source JAS results. | ||
| // Returns only NEW findings in source that don't exist in target. | ||
| func CompareJasResults(targetResults, sourceResults *SecurityCommandResults) *SecurityCommandResults { | ||
| log.Info("[DIFF] Starting JAS diff calculation") |
There was a problem hiding this comment.
| log.Info("[DIFF] Starting JAS diff calculation") | |
| log.Debug("[DIFF] Starting JAS diff calculation") |
There was a problem hiding this comment.
dont know if i agrree with it, i think it should be info, we can maybe do it in frogbot and log it info there
@orto17
There was a problem hiding this comment.
talked to or she wants them on info
attiasas
left a comment
There was a problem hiding this comment.
If we are moving diff logic here, do we still need the logic in:
jas/sast/jas/secrets/jas/iac?
talking about:
log.Debug("Diff mode - SAST results to compare provided")
manager.resultsToCompareFileName = filepath.Join(scannerTempDir, "target.sarif")
// Save the sast results to compare as a report
err = jas.SaveScanResultsToCompareAsReport(manager.resultsToCompareFileName, resultsToCompare...)
make sure if not needed to delete related logic
- Rename filterExistingFindings to excludeExistingFindingsInTargets - Extract countJasFindings and extractAllJasResultKeys helpers - Move getSastFingerprint to sarifutils.GetSastDiffFingerprint - Use WrapTaskWithLoggerPropagation helper in audit.go - Remove LogCollector wrapper - use BufferedLogger directly from client-go
When merging SCA and JAS diff results, the ResultsStatus field was not being copied, causing all status codes (SastStatusCode, IacStatusCode, etc.) to remain nil. This resulted in Frogbot displaying 'Not Scanned' for JAS scans even when they ran successfully but produced 0 new findings after diff. Now properly merging SCA status codes (including ContextualAnalysis) and JAS-only status codes (SAST, Secrets, IaC) into the unified results.
5e7efdc to
bccd932
Compare
When computing JAS diff, the ResultsStatus was not being copied to the diff results, causing all JAS scan status codes (SAST, Secrets, IaC) to be nil. This led to 'Not Scanned' being displayed in Frogbot PR comments even when scans ran successfully. Now copying ResultsStatus from source to diff results so status codes are preserved and merged correctly.
- Rename CompareJasResults → FilterNewJasFindings (more accurate name) - Rename test functions: TestFilterSarifRuns_* → TestFilterNewSarifFindings_* - Update test calls to use variadic signatures for extractLocationsOnly/extractFingerprints - Use sarifutils.GetSastDiffFingerprint instead of local getSastFingerprint Addresses code review feedback about misleading function names.
attiasas
left a comment
There was a problem hiding this comment.
Nice! take a look at my comments
| if collector := auditParams.GetLogCollector(); collector != nil { | ||
| log.SetLoggerForGoroutine(collector) | ||
| defer log.ClearLoggerForGoroutine() | ||
| } |
There was a problem hiding this comment.
the collector is running on main routine so it will first record all the logs from main and it will collect the other after?
| // Wrap task to propagate parent's logger for isolated parallel logging | ||
| wrappedJasTask := utils.WrapTaskWithLoggerPropagation(jasTask) | ||
| if _, jasErr := auditParallelRunner.Runner.AddTaskWithError(wrappedJasTask, func(taskErr error) { |
There was a problem hiding this comment.
we only want to wrap and change logs if collector is set.
why are we always wrapping?
| // SastDiffFingerprintKey is the fingerprint key used by Analyzer Manager for SAST diff matching. | ||
| // This differs from jasutils.SastFingerprintKey ("significant_full_path") which is used for general SAST operations. | ||
| const SastDiffFingerprintKey = "precise_sink_and_sink_function" |
There was a problem hiding this comment.
lets move the const to jasutils to be near the other used fingerprint value (easy to maintain and see what we use)
There was a problem hiding this comment.
if you can also add to GetResultFingerprint check for result nil case as well. just saw its missing
| filteredSbom := cdxutils.Exclude(*sbom, *params.TargetResultToCompare.ScaResults.Sbom.Components...) | ||
| return filteredSbom |
There was a problem hiding this comment.
| filteredSbom := cdxutils.Exclude(*sbom, *params.TargetResultToCompare.ScaResults.Sbom.Components...) | |
| return filteredSbom | |
| return cdxutils.Exclude(*sbom, *params.TargetResultToCompare.ScaResults.Sbom.Components...) |
why change is needed?
|
|
||
| sourceSecrets, sourceIac, sourceSast := countJasFindings(sourceJasResults) | ||
|
|
||
| log.Debug("[DIFF] Source findings before diff - Secrets:", sourceSecrets, "| IaC:", sourceIac, "| SAST:", sourceSast) |
There was a problem hiding this comment.
maybe you missed that, info as well?
| count := 0 | ||
| for _, run := range runs { | ||
| if run != nil { | ||
| count += len(run.Results) |
There was a problem hiding this comment.
I don't want to count on JAS, the correct impl should be the SARIF spec.
They have to follow it, so we should do the same.
| count := 0 | ||
| for _, run := range runs { | ||
| if run != nil { | ||
| count += len(run.Results) |
There was a problem hiding this comment.
at the end, the CLI result parser to format goes over each location as diff issue
take a look at ForEachJasIssue
| func strPtr(s string) *string { | ||
| return &s | ||
| } |
There was a problem hiding this comment.
remove and use utils.NewStringPtr() instead
| // Note: Tests for extractRelativePath, getLocationSnippetText, getLocationFileName, and | ||
| // getInvocationWorkingDirectory have been removed as these now use sarifutils functions. | ||
|
|
||
| func TestGetSastFingerprint(t *testing.T) { |
There was a problem hiding this comment.
move to sarifutils_test.go
devbranch.go vet ./....go fmt ./....Depends on: