Skip to content

Commit 93371fd

Browse files
senglezouptzianos
authored andcommitted
construct uniform paths from existing supported sarif tools
path is of the form "file://<relative path to an option workspace root>"
1 parent 98e8723 commit 93371fd

3 files changed

Lines changed: 685 additions & 81 deletions

File tree

sarif/sarif.go

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ type SarifTransformer struct {
3232
ruleToEcosystem map[string]string
3333
richDescription bool
3434
dataSource *ocsffindinginfo.DataSource
35+
36+
// the root path to which all file paths should be relative to, will be ultimately removed from findings,
37+
// this is used to handle CI/CD cases where findings have absolute path to the filesystem as opposed to project root.
38+
workspacePath string
3539
}
3640

3741
var typeName = map[int64]string{
@@ -76,6 +80,7 @@ func NewTransformer(
7680
guidProvider StableUUIDProvider,
7781
richDescription bool,
7882
dataSource *ocsffindinginfo.DataSource,
83+
workspacePath string,
7984
) (*SarifTransformer, error) {
8085
if scanResult == nil {
8186
return nil, errors.Errorf("method 'NewTransformer called with nil scanResult")
@@ -97,6 +102,11 @@ func NewTransformer(
97102
return nil, errors.Errorf("invalid data source provider: %w", err)
98103
}
99104

105+
cleanedWorkspacePath := filepath.Clean(workspacePath)
106+
if !filepath.IsAbs(cleanedWorkspacePath) {
107+
return nil, errors.Errorf("workspace path must be an absolute path")
108+
}
109+
100110
return &SarifTransformer{
101111
clock: clock,
102112
sarifResult: *scanResult,
@@ -106,6 +116,7 @@ func NewTransformer(
106116
taxasByCWEID: make(map[string]sarif.ReportingDescriptor),
107117
richDescription: richDescription,
108118
dataSource: dataSource,
119+
workspacePath: cleanedWorkspacePath,
109120
}, nil
110121
}
111122

@@ -161,7 +172,10 @@ func (s *SarifTransformer) transformToOCSF(
161172
res *sarif.Result,
162173
) (*ocsf.VulnerabilityFinding, error) {
163174
slog.Debug("parsing run from", slog.String("toolname", toolName))
164-
affectedCode, affectedPackages := s.mapAffected(res)
175+
affectedCode, affectedPackages, err := s.mapAffected(res)
176+
if err != nil {
177+
return nil, errors.Errorf("could not map affected code/packages: %w", err)
178+
}
165179

166180
var (
167181
ruleID *string
@@ -346,6 +360,84 @@ func (s *SarifTransformer) isSnykURI(uri string) bool {
346360
return strings.HasPrefix(uri, "https_//")
347361
}
348362

363+
func (s *SarifTransformer) relativePath(path string) (string, error) {
364+
if s.workspacePath == "" {
365+
return path, nil
366+
}
367+
368+
if !strings.HasPrefix(path, s.workspacePath) {
369+
return "", errors.Errorf(
370+
"%s: result is not inside expected directory: %s",
371+
path,
372+
s.workspacePath,
373+
)
374+
}
375+
376+
relativePath, err := filepath.Rel(s.workspacePath, path)
377+
if err != nil {
378+
return "", errors.Errorf(
379+
"could not get relative path from path %s using prefix %q",
380+
path,
381+
s.workspacePath,
382+
)
383+
}
384+
385+
return relativePath, nil
386+
}
387+
388+
// normalisePath will take a given path and construct a file url pointing to
389+
// the file relative to the workspacePath
390+
func (s *SarifTransformer) normalisePath(
391+
path string,
392+
uriBaseId *string,
393+
) (*ocsf.File, error) {
394+
parsedPath, err := url.Parse(path)
395+
if err != nil {
396+
return nil, errors.Errorf("%s: could not parse path: %w", path, err)
397+
}
398+
399+
cleanedPath := filepath.Clean(
400+
filepath.Join(parsedPath.Host, parsedPath.Path),
401+
)
402+
403+
switch {
404+
case uriBaseId == nil:
405+
if s.workspacePath != "" && filepath.IsAbs(cleanedPath) {
406+
relativePath, err := s.relativePath(cleanedPath)
407+
if err != nil {
408+
return nil, err
409+
}
410+
411+
cleanedPath = relativePath
412+
}
413+
case strings.ToLower(*uriBaseId) == "%srcroot%" && filepath.IsAbs(cleanedPath):
414+
// this should be a relative path and it's not, so we should return an error
415+
return nil, errors.Errorf("%s: path was expected to be relative but it's absolute", cleanedPath)
416+
case strings.ToLower(*uriBaseId) == "rootpath":
417+
if !filepath.IsAbs(cleanedPath) {
418+
return nil, errors.Errorf("%s: path was expected to be asbolute", cleanedPath)
419+
}
420+
421+
relativePath, err := s.relativePath(cleanedPath)
422+
if err != nil {
423+
return nil, err
424+
}
425+
426+
cleanedPath = relativePath
427+
}
428+
429+
// validate that we created a URL correctly
430+
finalPath := "file://" + cleanedPath
431+
if _, err := url.Parse(finalPath); err != nil {
432+
return nil, errors.Errorf("could not parse final path %s as url: %w", finalPath, err)
433+
}
434+
435+
return &ocsf.File{
436+
Name: cleanedPath,
437+
Path: utils.Ptr(finalPath),
438+
}, nil
439+
}
440+
349441
func (s *SarifTransformer) mapAffectedPackage(fixes []sarif.Fix, purl packageurl.PackageURL) *ocsf.AffectedPackage {
350442
affectedPackage := &ocsf.AffectedPackage{
351443
Purl: utils.Ptr(purl.String()),
@@ -440,11 +532,11 @@ func (s *SarifTransformer) rulesToEcosystem() map[string]string {
440532
return result
441533
}
442534

443-
func (s *SarifTransformer) mapAffected(res *sarif.Result) ([]*ocsf.AffectedCode, []*ocsf.AffectedPackage) {
535+
func (s *SarifTransformer) mapAffected(res *sarif.Result) ([]*ocsf.AffectedCode, []*ocsf.AffectedPackage, error) {
444536
var affectedCode []*ocsf.AffectedCode
445537
var affectedPackages []*ocsf.AffectedPackage
446538
if s.dataSource.TargetType == ocsffindinginfo.DataSource_TARGET_TYPE_WEBSITE { // websites do not carry code or package info
447-
return nil, nil
539+
return nil, nil, nil
448540
}
449541

450542
for _, location := range res.Locations {
@@ -469,15 +561,21 @@ func (s *SarifTransformer) mapAffected(res *sarif.Result) ([]*ocsf.AffectedCode,
469561
}
470562

471563
if physicalLocation.ArtifactLocation != nil && physicalLocation.ArtifactLocation.Uri != nil {
564+
uri := *location.PhysicalLocation.ArtifactLocation.Uri
472565
if p := s.detectPackageFromPhysicalLocation(*physicalLocation, pkgType); p != nil {
473566
affectedPackages = append(affectedPackages, s.mapAffectedPackage(res.Fixes, *p))
474-
} else if !s.isSnykURI(*location.PhysicalLocation.ArtifactLocation.Uri) {
475567
// Snyk special case, they use the repo url with some weird replacement as the artifact location
568+
} else if !s.isSnykURI(uri) {
569+
finalFile, err := s.normalisePath(
570+
uri,
571+
location.PhysicalLocation.ArtifactLocation.UriBaseId,
572+
)
573+
if err != nil {
574+
return nil, nil, errors.Errorf("could not construct path for affected code: %w", err)
575+
}
576+
476577
ac := &ocsf.AffectedCode{
477-
File: &ocsf.File{
478-
Name: *location.PhysicalLocation.ArtifactLocation.Uri,
479-
Path: utils.Ptr(fmt.Sprintf("file://%s", *location.PhysicalLocation.ArtifactLocation.Uri)),
480-
},
578+
File: finalFile,
481579
}
482580

483581
if physicalLocation.Region != nil {
@@ -500,7 +598,7 @@ func (s *SarifTransformer) mapAffected(res *sarif.Result) ([]*ocsf.AffectedCode,
500598
}
501599
}
502600

503-
return affectedCode, affectedPackages
601+
return affectedCode, affectedPackages, nil
504602
}
505603

506604
func (s *SarifTransformer) mapSeverity(sarifResLevel sarif.ResultLevel) ocsf.VulnerabilityFinding_SeverityId {
@@ -648,9 +746,17 @@ func (s *SarifTransformer) mergeDataSources(
648746
if s.isSnykURI(*location.PhysicalLocation.ArtifactLocation.Uri) {
649747
dataSource.Uri = nil
650748
} else {
749+
finalPath, err := s.normalisePath(
750+
*location.PhysicalLocation.ArtifactLocation.Uri,
751+
location.PhysicalLocation.ArtifactLocation.UriBaseId,
752+
)
753+
if err != nil {
754+
return nil, errors.Errorf("could not construct path for repository data source: %w", err)
755+
}
756+
651757
dataSource.Uri = &ocsffindinginfo.DataSource_URI{
652758
UriSchema: ocsffindinginfo.DataSource_URI_SCHEMA_FILE,
653-
Path: "file://" + filepath.Clean(*location.PhysicalLocation.ArtifactLocation.Uri),
759+
Path: *finalPath.Path,
654760
}
655761
}
656762

0 commit comments

Comments
 (0)