Skip to content

Commit

Permalink
feat(report): add dependency locations to sarif format (#3210)
Browse files Browse the repository at this point in the history
  • Loading branch information
DmitriyLewen authored Dec 1, 2022
1 parent 967e32f commit d29b0ed
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 21 deletions.
5 changes: 5 additions & 0 deletions pkg/flag/report_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependenc
log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", report.SupportedSBOMFormats)
return true
}
// We need this flag to insert dependency locations into Sarif('Package' struct contains 'Locations')
if format == report.FormatSarif && !listAllPkgs {
log.Logger.Debugf("Sarif format automatically enables '--list-all-pkgs' to get locations")
return true
}
if dependencyTree && !listAllPkgs {
log.Logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.")
return true
Expand Down
80 changes: 61 additions & 19 deletions pkg/report/sarif.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/owenrumney/go-sarif/v2/sarif"
"golang.org/x/xerrors"

ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/types"
)

Expand Down Expand Up @@ -40,9 +41,10 @@ var (

// SarifWriter implements result Writer
type SarifWriter struct {
Output io.Writer
Version string
run *sarif.Run
Output io.Writer
Version string
run *sarif.Run
locationCache map[string][]location
}

type sarifData struct {
Expand All @@ -60,8 +62,12 @@ type sarifData struct {
locationMessage string
message string
cvssScore string
startLine int
endLine int
locations []location
}

type location struct {
startLine int
endLine int
}

func (sw *SarifWriter) addSarifRule(data *sarifData) {
Expand Down Expand Up @@ -94,20 +100,11 @@ func (sw *SarifWriter) addSarifRule(data *sarifData) {
func (sw *SarifWriter) addSarifResult(data *sarifData) {
sw.addSarifRule(data)

region := sarif.NewRegion().WithStartLine(1).WithEndLine(1)
if data.startLine > 0 {
region = sarif.NewSimpleRegion(data.startLine, data.endLine)
}
region.WithStartColumn(1).WithEndColumn(1)

location := sarif.NewPhysicalLocation().
WithArtifactLocation(sarif.NewSimpleArtifactLocation(data.artifactLocation).WithUriBaseId("ROOTPATH")).
WithRegion(region)
result := sarif.NewRuleResult(data.vulnerabilityId).
WithRuleIndex(data.resultIndex).
WithMessage(sarif.NewTextMessage(data.message)).
WithLevel(toSarifErrorLevel(data.severity)).
WithLocations([]*sarif.Location{sarif.NewLocation().WithMessage(sarif.NewTextMessage(data.locationMessage)).WithPhysicalLocation(location)})
WithLocations(toSarifLocations(data.locations, data.artifactLocation, data.locationMessage))
sw.run.AddResult(result)
}

Expand All @@ -129,6 +126,7 @@ func (sw SarifWriter) Write(report types.Report) error {
sw.run = sarif.NewRunWithInformationURI("Trivy", "https://github.com/aquasecurity/trivy")
sw.run.Tool.Driver.WithVersion(sw.Version)
sw.run.Tool.Driver.WithFullName("Trivy Vulnerability Scanner")
sw.locationCache = map[string][]location{}

ruleIndexes := map[string]int{}
for _, res := range report.Results {
Expand All @@ -152,6 +150,7 @@ func (sw SarifWriter) Write(report types.Report) error {
resourceClass: string(res.Class),
artifactLocation: path,
locationMessage: fmt.Sprintf("%v: %v@%v", path, vuln.PkgName, vuln.InstalledVersion),
locations: sw.getLocations(vuln.PkgName, vuln.InstalledVersion, path, res.Packages),
resultIndex: getRuleIndex(vuln.VulnerabilityID, ruleIndexes),
shortDescription: html.EscapeString(vuln.Title),
fullDescription: html.EscapeString(fullDescription),
Expand All @@ -173,8 +172,7 @@ func (sw SarifWriter) Write(report types.Report) error {
resourceClass: string(res.Class),
artifactLocation: target,
locationMessage: target,
startLine: misconf.CauseMetadata.StartLine,
endLine: misconf.CauseMetadata.EndLine,
locations: []location{{startLine: misconf.CauseMetadata.StartLine, endLine: misconf.CauseMetadata.EndLine}},
resultIndex: getRuleIndex(misconf.ID, ruleIndexes),
shortDescription: html.EscapeString(misconf.Title),
fullDescription: html.EscapeString(misconf.Description),
Expand All @@ -196,8 +194,7 @@ func (sw SarifWriter) Write(report types.Report) error {
resourceClass: string(res.Class),
artifactLocation: target,
locationMessage: target,
startLine: secret.StartLine,
endLine: secret.EndLine,
locations: []location{{startLine: secret.StartLine, endLine: secret.EndLine}},
resultIndex: getRuleIndex(secret.RuleID, ruleIndexes),
shortDescription: html.EscapeString(secret.Title),
fullDescription: html.EscapeString(secret.Match),
Expand All @@ -218,6 +215,33 @@ func (sw SarifWriter) Write(report types.Report) error {
return sarifReport.PrettyWrite(sw.Output)
}

func toSarifLocations(locations []location, artifactLocation, locationMessage string) []*sarif.Location {
var sarifLocs []*sarif.Location
// add default (hardcoded) location for vulnerabilities that don't support locations
if len(locations) == 0 {
locations = append(locations, location{startLine: 1, endLine: 1})
}

// some dependencies can be placed in multiple places.
// e.g.https://github.com/aquasecurity/go-dep-parser/pull/134#discussion_r985353240
// create locations for each place.

for _, l := range locations {
// location is missed. Use default (hardcoded) value (misconfigurations have this case)
if l.startLine == 0 && l.endLine == 0 {
l.startLine = 1
l.endLine = 1
}
region := sarif.NewRegion().WithStartLine(l.startLine).WithEndLine(l.endLine).WithStartColumn(1).WithEndColumn(1)
loc := sarif.NewPhysicalLocation().
WithArtifactLocation(sarif.NewSimpleArtifactLocation(artifactLocation).WithUriBaseId("ROOTPATH")).
WithRegion(region)
sarifLocs = append(sarifLocs, sarif.NewLocation().WithMessage(sarif.NewTextMessage(locationMessage)).WithPhysicalLocation(loc))
}

return sarifLocs
}

func toSarifRuleName(class string) string {
switch class {
case types.ClassOSPkg:
Expand Down Expand Up @@ -259,6 +283,24 @@ func ToPathUri(input string) string {
return strings.ReplaceAll(input, "\\", "/")
}

func (sw SarifWriter) getLocations(name, version, path string, pkgs []ftypes.Package) []location {
id := fmt.Sprintf("%s@%s@%s", path, name, version)
locs, ok := sw.locationCache[id]
if !ok {
for _, pkg := range pkgs {
if name == pkg.Name && version == pkg.Version {
for _, l := range pkg.Locations {
loc := location{startLine: l.StartLine, endLine: l.EndLine}
locs = append(locs, loc)
}
sw.locationCache[id] = locs
return locs
}
}
}
return locs
}

func getCVSSScore(vuln types.DetectedVulnerability) string {
// Take the vendor score
if cvss, ok := vuln.CVSS[vuln.SeveritySource]; ok {
Expand Down
35 changes: 33 additions & 2 deletions pkg/report/sarif_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ func TestReportWriter_Sarif(t *testing.T) {
{
Target: "library/test",
Class: types.ClassOSPkg,
Packages: []ftypes.Package{
{
Name: "foo",
Version: "1.2.3",
Locations: []ftypes.Location{
{
StartLine: 5,
EndLine: 10,
},
{
StartLine: 15,
EndLine: 20,
},
},
},
},
Vulnerabilities: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2020-0001",
Expand Down Expand Up @@ -103,8 +119,23 @@ func TestReportWriter_Sarif(t *testing.T) {
URIBaseId: toPtr("ROOTPATH"),
},
Region: &sarif.Region{
StartLine: toPtr(1),
EndLine: toPtr(1),
StartLine: toPtr(5),
EndLine: toPtr(10),
StartColumn: toPtr(1),
EndColumn: toPtr(1),
},
},
},
{
Message: &sarif.Message{Text: toPtr("library/test: foo@1.2.3")},
PhysicalLocation: &sarif.PhysicalLocation{
ArtifactLocation: &sarif.ArtifactLocation{
URI: toPtr("library/test"),
URIBaseId: toPtr("ROOTPATH"),
},
Region: &sarif.Region{
StartLine: toPtr(15),
EndLine: toPtr(20),
StartColumn: toPtr(1),
EndColumn: toPtr(1),
},
Expand Down

0 comments on commit d29b0ed

Please sign in to comment.