Skip to content

Conversation

@eyalk007
Copy link
Contributor

@eyalk007 eyalk007 commented Jan 13, 2026

  • The pull request is targeting the dev branch.
  • The code has been validated to compile successfully by running go vet ./....
  • The code has been formatted properly using go fmt ./....
  • All static analysis checks passed.
  • All tests have passed. If this feature is not already covered by the tests, new tests have been added.
  • Updated the Contributing page / ReadMe page / CI Workflow files if needed.
  • All changes are detailed at the description. if not already covered at JFrog Documentation, new documentation have been added.

Depends on:

- 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
@eyalk007 eyalk007 self-assigned this Jan 13, 2026
@eyalk007 eyalk007 added the improvement Automatically generated release notes label Jan 13, 2026
)

// UnifyScaAndJasResults merges SCA and JAS diff results into a single SecurityCommandResults.
func UnifyScaAndJasResults(scaResults, jasDiffResults *SecurityCommandResults) *SecurityCommandResults {
Copy link
Contributor Author

@eyalk007 eyalk007 Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

myabe needs to be moved to frogbot considering the specific use
and selected parallel audit implementation, your call @attiasas

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mergeing results could stay here if the logic is adjusted to be more general like MergeCommandResults

@eyalk007 eyalk007 added the safe to test Approve running integration tests on a pull request label Jan 13, 2026
@github-actions github-actions bot removed the safe to test Approve running integration tests on a pull request label Jan 13, 2026
Copy link
Contributor

@attiasas attiasas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! checkout my comments, also:

  1. Update the PR description with the changes
  2. Adjust tests base on changes

@@ -0,0 +1,41 @@
package audit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this should be at jfrog/jfrog-client-go#1297?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing code from repo is always good :)
If you already passing it in params, just use it no need for wrapper here


// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to remove replace after merging dependend PR

Comment on lines 76 to 82
currentLogger := log.GetLogger()
scaTask := createScaScanTaskWithRunner(params.Runner, strategy, params)
wrappedScaTask := func(threadId int) error {
log.SetLoggerForGoroutine(currentLogger)
defer log.ClearLoggerForGoroutine()
return scaTask(threadId)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not change in createScaScanTaskWithRunner directly?

I don't get the chnage (looking only here) you call log.GetLogger and setting it for the routine but at the end you clear it? code is not clear why you do it. (looks like you are just setting default log to be default and clear it).

I suggest improving this part or at least adding comment about this

func addModuleJasScanTask(scanType jasutils.JasScanType, securityParallelRunner *utils.SecurityParallelRunner, task parallel.TaskFunc, scanResults *results.TargetResults, allowSkippingErrors bool) (generalError error) {
securityParallelRunner.JasScannersWg.Add(1)
if _, addTaskErr := securityParallelRunner.Runner.AddTaskWithError(task, func(err error) {
currentLogger := log.GetLogger()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as in scascan.
If this logic is used multiple times (same code), try to create static method to be used in multiple places

)

// UnifyScaAndJasResults merges SCA and JAS diff results into a single SecurityCommandResults.
func UnifyScaAndJasResults(scaResults, jasDiffResults *SecurityCommandResults) *SecurityCommandResults {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mergeing results could stay here if the logic is adjusted to be more general like MergeCommandResults


// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK about the func name, it does not compare, it filters Source results that exists at target

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can write a todo and create a ticket for it, but i dont believe we should handle it now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we should

@@ -0,0 +1,41 @@
package audit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing code from repo is always good :)
If you already passing it in params, just use it no need for wrapper here

Comment on lines +632 to +641
// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use WrapTaskWithLoggerPropagation here as well

Comment on lines +201 to +211
// getSastFingerprint extracts the SAST fingerprint used for diff matching.
// Note: Uses "precise_sink_and_sink_function" key (from Analyzer Manager for diff purposes),
// which differs from jasutils.SastFingerprintKey ("significant_full_path") used elsewhere.
func getSastFingerprint(result *sarif.Result) string {
if result.Fingerprints != nil {
if value, ok := result.Fingerprints["precise_sink_and_sink_function"]; ok {
return value
}
}
return ""
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as mentioned before, should be moved to utils/formats/sarifutils/sarifutils.go
also adjust comments, make sure what to use and when precise_sink_and_sink_function or significant_full_path fingerprint

Comment on lines +160 to +161
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)

Comment on lines +192 to +199
func extractLocationsOnly(run *sarif.Run, targetKeys map[string]bool) {
for _, result := range run.Results {
for _, location := range result.Locations {
key := sarifutils.GetRelativeLocationFileName(location, run.Invocations) + sarifutils.GetLocationSnippetText(location)
targetKeys[key] = true
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func extractLocationsOnly(run *sarif.Run, targetKeys map[string]bool) {
for _, result := range run.Results {
for _, location := range result.Locations {
key := sarifutils.GetRelativeLocationFileName(location, run.Invocations) + sarifutils.GetLocationSnippetText(location)
targetKeys[key] = true
}
}
}
func extractLocationsOnly(targetKeys map[string]bool, runs ...*sarif.Run) {
for _, run := range runs {
for _, result := range run.Results {
for _, location := range result.Locations {
key := sarifutils.GetRelativeLocationFileName(location, run.Invocations) + sarifutils.GetLocationSnippetText(location)
targetKeys[key] = true
}
}
}
}

you can use it to reduce the amount of for loops in code where it is called

// 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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log.Info("[DIFF] Starting JAS diff calculation")
log.Debug("[DIFF] Starting JAS diff calculation")

}

// filterExistingFindings removes findings from source that already exist in target.
func filterExistingFindings(allTargetJasResults []*JasScansResults, sourceJasResults *JasScansResults) *JasScansResults {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func filterExistingFindings(allTargetJasResults []*JasScansResults, sourceJasResults *JasScansResults) *JasScansResults {
func excludeExistingFindingsInTargets(sourceJasResults *JasScansResults, targetJasResultsToExclude ...*JasScansResults) *JasScansResults {

outside user will have hard time understanding what the func does, consider adjusting the name.
I would also consider changing how params are passed

Comment on lines +101 to +126
targetKeys := make(map[string]bool)

for _, targetJasResults := range allTargetJasResults {
if targetJasResults == nil {
continue
}

for _, targetRun := range targetJasResults.GetVulnerabilitiesResults(jasutils.Secrets) {
extractLocationsOnly(targetRun, targetKeys)
}
for _, targetRun := range targetJasResults.GetViolationsResults(jasutils.Secrets) {
extractLocationsOnly(targetRun, targetKeys)
}
for _, targetRun := range targetJasResults.GetVulnerabilitiesResults(jasutils.IaC) {
extractLocationsOnly(targetRun, targetKeys)
}
for _, targetRun := range targetJasResults.GetViolationsResults(jasutils.IaC) {
extractLocationsOnly(targetRun, targetKeys)
}
for _, targetRun := range targetJasResults.GetVulnerabilitiesResults(jasutils.Sast) {
extractFingerprints(targetRun, targetKeys)
}
for _, targetRun := range targetJasResults.GetViolationsResults(jasutils.Sast) {
extractFingerprints(targetRun, targetKeys)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be extracted to a helper func

Comment on lines +128 to +134
sourceSecrets := countSarifResults(sourceJasResults.JasVulnerabilities.SecretsScanResults) +
countSarifResults(sourceJasResults.JasViolations.SecretsScanResults)
sourceIac := countSarifResults(sourceJasResults.JasVulnerabilities.IacScanResults) +
countSarifResults(sourceJasResults.JasViolations.IacScanResults)
sourceSast := countSarifResults(sourceJasResults.JasVulnerabilities.SastScanResults) +
countSarifResults(sourceJasResults.JasViolations.SastScanResults)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be extracted to a helper func, used twice

Copy link
Contributor

@attiasas attiasas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Automatically generated release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants