@@ -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
3741var 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+
349441func (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
506604func (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