mirror of
https://github.com/safedep/vet.git
synced 2025-12-11 01:01:10 -06:00
feat: Add command line arg for group by direct dependencies
This commit is contained in:
parent
79d7cf9599
commit
7f2f729418
@ -50,6 +50,10 @@ type summaryReporterRemediationData struct {
|
||||
pkg *models.Package
|
||||
score int
|
||||
tags []string
|
||||
|
||||
// Used in group by primitive, where remediating the pkg
|
||||
// leads to remediating all the packages in the array
|
||||
remediates []*summaryReporterRemediationData
|
||||
}
|
||||
|
||||
type summaryReporterVulnerabilityData struct {
|
||||
@ -349,6 +353,68 @@ func (r *summaryReporter) sortedRemediations() []*summaryReporterRemediationData
|
||||
return sortedPackages
|
||||
}
|
||||
|
||||
// To be able to group by direct dependencies, we need to:
|
||||
// - Enumerate through all package risks
|
||||
// - Group by direct dependency if available
|
||||
// - Track the packages that are remediated by the direct dependency
|
||||
func (r *summaryReporter) sortedRemediationsGroupByDirectDependency() []*summaryReporterRemediationData {
|
||||
groupedRemediationPackages := map[string]*summaryReporterRemediationData{}
|
||||
for _, value := range r.remediationScores {
|
||||
// Get the package and dependency graph
|
||||
pkg := value.pkg
|
||||
dg := pkg.GetDependencyGraph()
|
||||
|
||||
// If dependency graph is available
|
||||
if dg != nil {
|
||||
// Find the top level dependency that may result in upgrading affected package
|
||||
remediationPath := dg.PathToRoot(pkg)
|
||||
|
||||
if len(remediationPath) > 1 {
|
||||
// Package has atleast 1 parent so we will group by the root pkg
|
||||
pkg = remediationPath[len(remediationPath)-1]
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := groupedRemediationPackages[pkg.Id()]; !ok {
|
||||
groupedRemediationPackages[pkg.Id()] = &summaryReporterRemediationData{
|
||||
pkg: pkg,
|
||||
score: 0,
|
||||
tags: make([]string, 0),
|
||||
remediates: []*summaryReporterRemediationData{},
|
||||
}
|
||||
}
|
||||
|
||||
groupedRemediationPackages[pkg.Id()].score = groupedRemediationPackages[pkg.Id()].score + value.score
|
||||
|
||||
// TODO: Merge without duplicates
|
||||
groupedRemediationPackages[pkg.Id()].tags = append(groupedRemediationPackages[pkg.Id()].tags, value.tags...)
|
||||
|
||||
// If the root package is not same as the current package, then the root package remediates
|
||||
// the current package
|
||||
if pkg.Id() != value.pkg.Id() {
|
||||
groupedRemediationPackages[pkg.Id()].remediates = append(groupedRemediationPackages[pkg.Id()].remediates, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the remediated packages by score
|
||||
for _, pkg := range groupedRemediationPackages {
|
||||
slices.SortFunc(pkg.remediates, func(a, b *summaryReporterRemediationData) int {
|
||||
return cmp.Compare(b.score, a.score)
|
||||
})
|
||||
}
|
||||
|
||||
remediationPackages := make([]*summaryReporterRemediationData, 0)
|
||||
for _, rd := range groupedRemediationPackages {
|
||||
remediationPackages = append(remediationPackages, rd)
|
||||
}
|
||||
|
||||
slices.SortFunc(remediationPackages, func(a, b *summaryReporterRemediationData) int {
|
||||
return cmp.Compare(b.score, a.score)
|
||||
})
|
||||
|
||||
return remediationPackages
|
||||
}
|
||||
|
||||
func (r *summaryReporter) renderRemediationAdvice() {
|
||||
fmt.Println(text.Bold.Sprint("Consider upgrading the following libraries for maximum impact:"))
|
||||
fmt.Println()
|
||||
@ -357,10 +423,30 @@ func (r *summaryReporter) renderRemediationAdvice() {
|
||||
tbl.SetOutputMirror(os.Stdout)
|
||||
tbl.SetStyle(table.StyleLight)
|
||||
|
||||
tbl.AppendHeader(table.Row{"Ecosystem", "Package", "Update To", "Impact Score", "Vuln Risk"})
|
||||
var sortedPackages []*summaryReporterRemediationData
|
||||
if r.config.GroupByDirectDependency {
|
||||
sortedPackages = r.sortedRemediationsGroupByDirectDependency()
|
||||
} else {
|
||||
sortedPackages = r.sortedRemediations()
|
||||
}
|
||||
r.addRemediationAdviceTableRows(tbl, sortedPackages, r.config.MaxAdvice)
|
||||
|
||||
sortedPackages := r.sortedRemediations()
|
||||
maxAdvice := r.config.MaxAdvice
|
||||
tbl.Render()
|
||||
|
||||
if len(sortedPackages) > summaryReportMaxUpgradeAdvice {
|
||||
fmt.Println()
|
||||
fmt.Println(text.FgHiYellow.Sprint(
|
||||
fmt.Sprintf("There are %d more libraries that should be upgraded to reduce risk",
|
||||
len(sortedPackages)-summaryReportMaxUpgradeAdvice),
|
||||
))
|
||||
|
||||
fmt.Println(text.Bold.Sprint("Run vet with `--report-markdown=/path/to/report.md` for details"))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *summaryReporter) addRemediationAdviceTableRows(tbl table.Writer,
|
||||
sortedPackages []*summaryReporterRemediationData, maxAdvice int) {
|
||||
tbl.AppendHeader(table.Row{"Ecosystem", "Package", "Update To", "Impact Score", "Vuln Risk"})
|
||||
|
||||
for idx, sp := range sortedPackages {
|
||||
if idx >= maxAdvice {
|
||||
@ -380,32 +466,46 @@ func (r *summaryReporter) renderRemediationAdvice() {
|
||||
tagText += text.BgMagenta.Sprint(" "+t+" ") + " "
|
||||
}
|
||||
|
||||
tbl.AppendRow(table.Row{
|
||||
"", tagText, "", "",
|
||||
r.packageVulnerabilitySampleText(sp.pkg),
|
||||
})
|
||||
if len(sp.remediates) > 0 {
|
||||
remediatesSample := sp.remediates[0:slices.Min([]int{len(sp.remediates), 5})]
|
||||
|
||||
pathToRoot := text.Faint.Sprint(r.pathToPackageRoot(sp.pkg))
|
||||
if pathToRoot != "" {
|
||||
// This is a grouped dependency so we will render the children
|
||||
for _, rd := range remediatesSample {
|
||||
remediatedPkgName := text.Faint.Sprint(r.packageNameForRemediationAdvice(rd.pkg))
|
||||
vulnRisk := r.packageVulnerabilityRiskText(rd.pkg)
|
||||
vulnRiskSample := r.packageVulnerabilitySampleText(rd.pkg)
|
||||
|
||||
if vulnRiskSample != "" {
|
||||
vulnRisk = fmt.Sprintf("%s (%s)", vulnRisk, vulnRiskSample)
|
||||
}
|
||||
|
||||
tbl.AppendRow(table.Row{
|
||||
"", remediatedPkgName, "", "", vulnRisk,
|
||||
})
|
||||
}
|
||||
|
||||
if len(sp.remediates) > len(remediatesSample) {
|
||||
tbl.AppendRow(table.Row{
|
||||
"", fmt.Sprintf("... and %d more", len(sp.remediates)-len(remediatesSample)), "", "", "",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// This is a direct dependency or do not remediate anything else (not grouped)
|
||||
tbl.AppendRow(table.Row{
|
||||
"", pathToRoot, "", "", "",
|
||||
"", tagText, "", "",
|
||||
r.packageVulnerabilitySampleText(sp.pkg),
|
||||
})
|
||||
|
||||
pathToRoot := text.Faint.Sprint(r.pathToPackageRoot(sp.pkg))
|
||||
if pathToRoot != "" {
|
||||
tbl.AppendRow(table.Row{
|
||||
"", pathToRoot, "", "", "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tbl.AppendSeparator()
|
||||
}
|
||||
|
||||
tbl.Render()
|
||||
|
||||
if len(sortedPackages) > summaryReportMaxUpgradeAdvice {
|
||||
fmt.Println()
|
||||
fmt.Println(text.FgHiYellow.Sprint(
|
||||
fmt.Sprintf("There are %d more libraries that should be upgraded to reduce risk",
|
||||
len(sortedPackages)-summaryReportMaxUpgradeAdvice),
|
||||
))
|
||||
|
||||
fmt.Println(text.Bold.Sprint("Run vet with `--report-markdown=/path/to/report.md` for details"))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *summaryReporter) packageVulnerabilityRiskText(pkg *models.Package) string {
|
||||
|
||||
34
query.go
34
query.go
@ -12,20 +12,21 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
queryFilterExpression string
|
||||
queryFilterSuiteFile string
|
||||
queryFilterFailOnMatch bool
|
||||
queryLoadDirectory string
|
||||
queryEnableConsoleReport bool
|
||||
queryEnableSummaryReport bool
|
||||
querySummaryReportMaxAdvice int
|
||||
queryMarkdownReportPath string
|
||||
queryJsonReportPath string
|
||||
queryGraphReportPath string
|
||||
queryCsvReportPath string
|
||||
queryExceptionsFile string
|
||||
queryExceptionsTill string
|
||||
queryExceptionsFilter string
|
||||
queryFilterExpression string
|
||||
queryFilterSuiteFile string
|
||||
queryFilterFailOnMatch bool
|
||||
queryLoadDirectory string
|
||||
queryEnableConsoleReport bool
|
||||
queryEnableSummaryReport bool
|
||||
querySummaryReportMaxAdvice int
|
||||
querySummaryReportGroupByDirectDeps bool
|
||||
queryMarkdownReportPath string
|
||||
queryJsonReportPath string
|
||||
queryGraphReportPath string
|
||||
queryCsvReportPath string
|
||||
queryExceptionsFile string
|
||||
queryExceptionsTill string
|
||||
queryExceptionsFilter string
|
||||
|
||||
queryDefaultExceptionExpiry = time.Now().Add(90 * 24 * time.Hour)
|
||||
)
|
||||
@ -61,6 +62,8 @@ func newQueryCommand() *cobra.Command {
|
||||
"Show an actionable summary based on scan data")
|
||||
cmd.Flags().IntVarP(&querySummaryReportMaxAdvice, "report-summary-max-advice", "", 5,
|
||||
"Maximum number of package risk advice to show")
|
||||
cmd.Flags().BoolVarP(&querySummaryReportGroupByDirectDeps, "report-summary-group-by-direct-deps", "", false,
|
||||
"Group summary by direct dependencies")
|
||||
cmd.Flags().StringVarP(&queryMarkdownReportPath, "report-markdown", "", "",
|
||||
"Generate markdown report to file")
|
||||
cmd.Flags().StringVarP(&queryJsonReportPath, "report-json", "", "",
|
||||
@ -134,7 +137,8 @@ func internalStartQuery() error {
|
||||
|
||||
if queryEnableSummaryReport {
|
||||
rp, err := reporter.NewSummaryReporter(reporter.SummaryReporterConfig{
|
||||
MaxAdvice: querySummaryReportMaxAdvice,
|
||||
MaxAdvice: querySummaryReportMaxAdvice,
|
||||
GroupByDirectDependency: querySummaryReportGroupByDirectDeps,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
70
scan.go
70
scan.go
@ -20,38 +20,39 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
lockfiles []string
|
||||
lockfileAs string
|
||||
enrich bool
|
||||
baseDirectory string
|
||||
purlSpec string
|
||||
githubRepoUrls []string
|
||||
githubOrgUrl string
|
||||
githubOrgMaxRepositories int
|
||||
scanExclude []string
|
||||
transitiveAnalysis bool
|
||||
transitiveDepth int
|
||||
concurrency int
|
||||
dumpJsonManifestDir string
|
||||
celFilterExpression string
|
||||
celFilterSuiteFile string
|
||||
celFilterFailOnMatch bool
|
||||
markdownReportPath string
|
||||
jsonReportPath string
|
||||
consoleReport bool
|
||||
summaryReport bool
|
||||
summaryReportMaxAdvice int
|
||||
csvReportPath string
|
||||
silentScan bool
|
||||
disableAuthVerifyBeforeScan bool
|
||||
syncReport bool
|
||||
syncReportProject string
|
||||
graphReportDirectory string
|
||||
syncReportStream string
|
||||
listExperimentalParsers bool
|
||||
failFast bool
|
||||
trustedRegistryUrls []string
|
||||
scannerExperimental bool
|
||||
lockfiles []string
|
||||
lockfileAs string
|
||||
enrich bool
|
||||
baseDirectory string
|
||||
purlSpec string
|
||||
githubRepoUrls []string
|
||||
githubOrgUrl string
|
||||
githubOrgMaxRepositories int
|
||||
scanExclude []string
|
||||
transitiveAnalysis bool
|
||||
transitiveDepth int
|
||||
concurrency int
|
||||
dumpJsonManifestDir string
|
||||
celFilterExpression string
|
||||
celFilterSuiteFile string
|
||||
celFilterFailOnMatch bool
|
||||
markdownReportPath string
|
||||
jsonReportPath string
|
||||
consoleReport bool
|
||||
summaryReport bool
|
||||
summaryReportMaxAdvice int
|
||||
summaryReportGroupByDirectDeps bool
|
||||
csvReportPath string
|
||||
silentScan bool
|
||||
disableAuthVerifyBeforeScan bool
|
||||
syncReport bool
|
||||
syncReportProject string
|
||||
graphReportDirectory string
|
||||
syncReportStream string
|
||||
listExperimentalParsers bool
|
||||
failFast bool
|
||||
trustedRegistryUrls []string
|
||||
scannerExperimental bool
|
||||
)
|
||||
|
||||
func newScanCommand() *cobra.Command {
|
||||
@ -115,6 +116,8 @@ func newScanCommand() *cobra.Command {
|
||||
"Print a summary report with actionable advice")
|
||||
cmd.Flags().IntVarP(&summaryReportMaxAdvice, "report-summary-max-advice", "", 5,
|
||||
"Maximum number of package risk advice to show")
|
||||
cmd.Flags().BoolVarP(&summaryReportGroupByDirectDeps, "report-summary-group-by-direct-deps", "", false,
|
||||
"Group summary report by direct dependencies")
|
||||
cmd.Flags().StringVarP(&csvReportPath, "report-csv", "", "",
|
||||
"Generate CSV report of filtered packages")
|
||||
cmd.Flags().StringVarP(&jsonReportPath, "report-json", "", "",
|
||||
@ -277,7 +280,8 @@ func internalStartScan() error {
|
||||
|
||||
if summaryReport {
|
||||
rp, err := reporter.NewSummaryReporter(reporter.SummaryReporterConfig{
|
||||
MaxAdvice: summaryReportMaxAdvice,
|
||||
MaxAdvice: summaryReportMaxAdvice,
|
||||
GroupByDirectDependency: summaryReportGroupByDirectDeps,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user