@@ -433,7 +433,7 @@ func GetVulnerabilitiesContent(vulnerabilities []formats.VulnerabilityOrViolatio
433433 if len (vulnerabilities ) == 0 {
434434 return []string {}
435435 }
436- content = append (content , writer . MarkInCenter ( getVulnerabilitiesSummaryTable (vulnerabilities , writer ) ))
436+ content = append (content , getVulnerabilitiesSummaryTable (vulnerabilities , writer ))
437437 content = append (content , getScaSecurityIssueDetailsContent (vulnerabilities , false , writer )... )
438438 return ConvertContentToComments (content , writer , getDecoratorWithScaVulnerabilitiesTitle (writer ))
439439}
@@ -454,24 +454,17 @@ func getVulnerabilitiesSummaryTable(vulnerabilities []formats.VulnerabilityOrVio
454454 if writer .IsShowingCaColumn () {
455455 columns = append (columns , "Contextual Analysis" )
456456 }
457- columns = append (columns , "Direct Dependencies" , "Impacted Dependency" , "Fixed Versions " )
457+ columns = append (columns , "Dependency Path " )
458458 table := NewMarkdownTable (columns ... ).SetDelimiter (writer .Separator ())
459- if _ , ok := writer .(* SimplifiedOutput ); ok {
460- // The values in this cell can be potentially large, since SimplifiedOutput does not support tags, we need to show each value in a separate row.
461- // It means that the first row will show the full details, and the following rows will show only the direct dependency.
462- // It makes it easier to read the table and less crowded with text in a single cell that could be potentially large.
463- table .GetColumnInfo ("Direct Dependencies" ).ColumnType = MultiRowColumn
464- }
459+ table .GetColumnInfo ("Dependency Path" ).Centered = false
465460 // Construct rows
466461 for _ , vulnerability := range vulnerabilities {
467462 row := []CellData {{writer .FormattedSeverity (vulnerability .Severity , vulnerability .Applicable )}, getCveIdsCellData (vulnerability .Cves , vulnerability .IssueId )}
468463 if writer .IsShowingCaColumn () {
469464 row = append (row , NewCellData (vulnerability .Applicable ))
470465 }
471466 row = append (row ,
472- getDirectDependenciesCellData (vulnerability .Components ),
473- NewCellData (fmt .Sprintf ("%s %s" , vulnerability .ImpactedDependencyName , vulnerability .ImpactedDependencyVersion )),
474- NewCellData (vulnerability .FixedVersions ... ),
467+ getDependencyPathCellData (vulnerability .ImpactPaths , writer ),
475468 )
476469 table .AddRowWithCellData (row ... )
477470 }
@@ -605,6 +598,83 @@ func getCveIdsCellData(cveRows []formats.CveRow, issueId string) (ids CellData)
605598 return
606599}
607600
601+ func getFinalApplicabilityStatus (cves []formats.CveRow ) string {
602+ if len (cves ) == 0 {
603+ return ""
604+ }
605+
606+ statuses := []jasutils.ApplicabilityStatus {}
607+ for _ , cve := range cves {
608+ if cve .Applicability != nil && cve .Applicability .Status != "" {
609+ status := jasutils .ConvertToApplicabilityStatus (cve .Applicability .Status )
610+ if status != "" {
611+ statuses = append (statuses , status )
612+ }
613+ }
614+ }
615+ if len (statuses ) == 0 {
616+ return ""
617+ }
618+ return results .GetFinalApplicabilityStatus (statuses ).String ()
619+ }
620+
621+ func getDependencyPathCellData (impactPaths [][]formats.ComponentRow , writer OutputWriter ) CellData {
622+ if len (impactPaths ) == 0 {
623+ return NewCellData ()
624+ }
625+
626+ // key: "name:version"
627+ directDeps := make (map [string ]formats.ComponentRow )
628+ transitiveDeps := make (map [string ]formats.ComponentRow )
629+
630+ // Extract dependencies from all impact paths
631+ for _ , path := range impactPaths {
632+ if len (path ) == 2 {
633+ direct := path [1 ]
634+ key := fmt .Sprintf ("%s:%s" , direct .Name , direct .Version )
635+ directDeps [key ] = direct
636+
637+ } else if len (path ) > 2 {
638+ transitive := path [len (path )- 1 ]
639+ key := fmt .Sprintf ("%s:%s" , transitive .Name , transitive .Version )
640+ transitiveDeps [key ] = transitive
641+ }
642+ }
643+
644+ var parts []string
645+ if len (directDeps ) > 0 {
646+ directList := make ([]string , 0 , len (directDeps ))
647+ for _ , dep := range directDeps {
648+ directList = append (directList , results .GetDependencyId (dep .Name , dep .Version ))
649+ }
650+ sort .Strings (directList )
651+ directCount := len (directList )
652+ directContent := strings .Join (directList , writer .Separator ())
653+ directSummary := fmt .Sprintf ("%d Direct" , directCount )
654+ directSection := writer .MarkAsDetails (directSummary , 0 , directContent )
655+ parts = append (parts , directSection )
656+ }
657+
658+ if len (transitiveDeps ) > 0 {
659+ transitiveList := make ([]string , 0 , len (transitiveDeps ))
660+ for _ , dep := range transitiveDeps {
661+ transitiveList = append (transitiveList , results .GetDependencyId (dep .Name , dep .Version ))
662+ }
663+ sort .Strings (transitiveList )
664+ transitiveCount := len (transitiveList )
665+ transitiveContent := strings .Join (transitiveList , writer .Separator ())
666+ transitiveSummary := fmt .Sprintf ("%d Transitive" , transitiveCount )
667+ transitiveSection := writer .MarkAsDetails (transitiveSummary , 0 , transitiveContent )
668+ parts = append (parts , transitiveSection )
669+ }
670+
671+ if len (parts ) == 0 {
672+ return NewCellData ()
673+ }
674+ content := strings .Join (parts , "" )
675+ return NewCellData (content )
676+ }
677+
608678func getScaSecurityIssueDetailsContent (issues []formats.VulnerabilityOrViolationRow , violations bool , writer OutputWriter ) (content []string ) {
609679 issuesWithDetails := getIssuesWithDetails (issues )
610680 if len (issuesWithDetails ) == 0 {
@@ -652,16 +722,68 @@ func getComponentIssueIdentifier(key, compName, version, watch string) (id strin
652722 return strings .Join (parts , " " )
653723}
654724
725+ func getDependencyPathDetailsContent (impactPaths [][]formats.ComponentRow , fixedVersions []string , writer OutputWriter ) string {
726+ if len (impactPaths ) == 0 {
727+ return ""
728+ }
729+
730+ type packageInfo struct {
731+ component formats.ComponentRow
732+ isDirect bool
733+ }
734+ packages := make (map [string ]packageInfo ) // key: "name:version"
735+
736+ for _ , path := range impactPaths {
737+ if len (path ) == 2 {
738+ direct := path [1 ]
739+ key := fmt .Sprintf ("%s:%s" , direct .Name , direct .Version )
740+ packages [key ] = packageInfo {component : direct , isDirect : true }
741+ } else if len (path ) > 2 {
742+ transitive := path [len (path )- 1 ]
743+ key := fmt .Sprintf ("%s:%s" , transitive .Name , transitive .Version )
744+ packages [key ] = packageInfo {component : transitive , isDirect : true }
745+ }
746+ }
747+
748+ if len (packages ) == 0 {
749+ return ""
750+ }
751+
752+ var directEntries []string
753+ var transitiveEntries []string
754+
755+ for _ , pkgInfo := range packages {
756+ depType := "(Transitive)" // Transitive
757+ if pkgInfo .isDirect {
758+ depType = "(Direct)" // Direct
759+ }
760+
761+ packageSummary := fmt .Sprintf ("%s: %s %s" , pkgInfo .component .Name , pkgInfo .component .Version , depType )
762+
763+ var packageContentParts []string
764+ if len (fixedVersions ) > 0 {
765+ packageContentParts = append (packageContentParts , fmt .Sprintf ("Fix Version: %s" , fixedVersions [0 ]))
766+ }
767+ packageContent := strings .Join (packageContentParts , writer .Separator ())
768+ packageEntry := writer .MarkAsDetails (packageSummary , 0 , packageContent )
769+
770+ if pkgInfo .isDirect {
771+ directEntries = append (directEntries , packageEntry )
772+ } else {
773+ transitiveEntries = append (transitiveEntries , packageEntry )
774+ }
775+ }
776+ sort .Strings (directEntries )
777+ sort .Strings (transitiveEntries )
778+ allEntries := append (directEntries , transitiveEntries ... )
779+
780+ return strings .Join (allEntries , "" )
781+ }
782+
655783func getScaSecurityIssueDetails (issue formats.VulnerabilityOrViolationRow , violations bool , writer OutputWriter ) (content string ) {
656784 var contentBuilder strings.Builder
657- // Title
658785 WriteNewLine (& contentBuilder )
659786 WriteContent (& contentBuilder , writer .MarkAsTitle (fmt .Sprintf ("%s Details" , getIssueType (violations )), 3 ))
660- // Details Table
661- directComponent := []string {}
662- for _ , component := range issue .ImpactedDependencyDetails .Components {
663- directComponent = append (directComponent , results .GetDependencyId (component .Name , component .Version ))
664- }
665787 noHeaderTable := NewNoHeaderMarkdownTable (2 , false )
666788 if len (issue .Policies ) > 0 {
667789 noHeaderTable .AddRowWithCellData (NewCellData (MarkAsBold ("Policies:" )), NewCellData (issue .Policies ... ))
@@ -673,18 +795,20 @@ func getScaSecurityIssueDetails(issue formats.VulnerabilityOrViolationRow, viola
673795 severity := severityutils .Severity (issue .JfrogResearchInformation .Severity )
674796 noHeaderTable .AddRow (MarkAsBold ("Jfrog Research Severity:" ), fmt .Sprintf ("%s %s" , writer .SeverityIcon (severity ), severity .String ()))
675797 }
676- if issue .Applicable != "" {
677- noHeaderTable .AddRow (MarkAsBold ("Contextual Analysis:" ), issue .Applicable )
798+ applicableStatus := getFinalApplicabilityStatus (issue .Cves )
799+ if applicableStatus != "" {
800+ noHeaderTable .AddRow (MarkAsBold ("Contextual Analysis:" ), applicableStatus )
678801 }
679- noHeaderTable .AddRowWithCellData (NewCellData (MarkAsBold ("Direct Dependencies:" )), NewCellData (directComponent ... ))
680- noHeaderTable .AddRow (MarkAsBold ("Impacted Dependency:" ), results .GetDependencyId (issue .ImpactedDependencyName , issue .ImpactedDependencyVersion ))
681- noHeaderTable .AddRowWithCellData (NewCellData (MarkAsBold ("Fixed Versions:" )), NewCellData (issue .FixedVersions ... ))
682802
683803 cvss := []string {}
684804 for _ , cve := range issue .Cves {
685805 cvss = append (cvss , cve .CvssV3 )
686806 }
687807 noHeaderTable .AddRowWithCellData (NewCellData (MarkAsBold ("CVSS V3:" )), NewCellData (cvss ... ))
808+ dependencyPathDetails := getDependencyPathDetailsContent (issue .ImpactPaths , issue .FixedVersions , writer )
809+ if dependencyPathDetails != "" {
810+ noHeaderTable .AddRowWithCellData (NewCellData (MarkAsBold ("Dependency Path:" )), NewCellData (dependencyPathDetails ))
811+ }
688812 WriteContent (& contentBuilder , noHeaderTable .Build ())
689813
690814 // Summary
0 commit comments