github.com/google/osv-scalibr@v0.4.1/detector/detectorrunner/detectorrunner.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package detectorrunner provides a Run function to help with running detectors 16 package detectorrunner 17 18 import ( 19 "context" 20 "fmt" 21 "reflect" 22 "time" 23 24 "github.com/google/osv-scalibr/detector" 25 scalibrfs "github.com/google/osv-scalibr/fs" 26 "github.com/google/osv-scalibr/inventory" 27 "github.com/google/osv-scalibr/packageindex" 28 "github.com/google/osv-scalibr/plugin" 29 "github.com/google/osv-scalibr/stats" 30 ) 31 32 // Run runs the specified detectors and returns their findings, 33 // as well as info about whether the plugin runs completed successfully. 34 func Run(ctx context.Context, c stats.Collector, detectors []detector.Detector, scanRoot *scalibrfs.ScanRoot, index *packageindex.PackageIndex) (inventory.Finding, []*plugin.Status, error) { 35 findings := inventory.Finding{} 36 status := []*plugin.Status{} 37 for _, d := range detectors { 38 if ctx.Err() != nil { 39 return inventory.Finding{}, nil, ctx.Err() 40 } 41 start := time.Now() 42 result, err := d.Scan(ctx, scanRoot, index) 43 c.AfterDetectorRun(d.Name(), time.Since(start), err) 44 for _, v := range result.PackageVulns { 45 v.Plugins = []string{d.Name()} 46 } 47 for _, f := range result.GenericFindings { 48 f.Plugins = []string{d.Name()} 49 } 50 findings.PackageVulns = append(findings.PackageVulns, result.PackageVulns...) 51 findings.GenericFindings = append(findings.GenericFindings, result.GenericFindings...) 52 status = append(status, plugin.StatusFromErr(d, false, err, nil)) 53 } 54 if err := validateAdvisories(findings.GenericFindings); err != nil { 55 return inventory.Finding{}, status, err 56 } 57 return findings, status, nil 58 } 59 60 func validateAdvisories(findings []*inventory.GenericFinding) error { 61 // Check that findings with the same advisory ID have identical advisories. 62 ids := make(map[inventory.AdvisoryID]inventory.GenericFindingAdvisory) 63 for _, f := range findings { 64 if f.Adv == nil { 65 return fmt.Errorf("finding has no advisory set: %v", f) 66 } 67 if f.Adv.ID == nil { 68 return fmt.Errorf("finding has no advisory ID set: %v", f) 69 } 70 if adv, ok := ids[*f.Adv.ID]; ok { 71 if !reflect.DeepEqual(adv, *f.Adv) { 72 return fmt.Errorf("multiple non-identical advisories with ID %v", f.Adv.ID) 73 } 74 } 75 ids[*f.Adv.ID] = *f.Adv 76 } 77 return nil 78 }