github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/resolution/vulnerabilities.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 resolution 16 17 import ( 18 "context" 19 "errors" 20 "slices" 21 "strings" 22 23 "deps.dev/util/resolve" 24 "github.com/google/osv-scalibr/enricher" 25 "github.com/google/osv-scalibr/extractor" 26 "github.com/google/osv-scalibr/guidedremediation/internal/manifest" 27 "github.com/google/osv-scalibr/guidedremediation/internal/vulns" 28 "github.com/google/osv-scalibr/inventory" 29 osvpb "github.com/ossf/osv-schema/bindings/go/osvschema" 30 ) 31 32 // Vulnerability represents a vulnerability found in a dependency graph. 33 type Vulnerability struct { 34 OSV *osvpb.Vulnerability 35 DevOnly bool 36 // Subgraphs are the collections of nodes and edges that reach the vulnerable node. 37 // Subgraphs all contain the root node (NodeID 0) with no incoming edges (Parents), 38 // and the vulnerable node (NodeID DependencySubgraph.Dependency) with no outgoing edges (Children). 39 Subgraphs []*DependencySubgraph 40 } 41 42 // FindVulnerabilities scans for vulnerabilities in a resolved graph. 43 // One Vulnerability is created per unique ID, which may affect multiple graph nodes. 44 func FindVulnerabilities(ctx context.Context, en enricher.Enricher, depGroups map[manifest.RequirementKey][]string, graph *resolve.Graph) ([]Vulnerability, error) { 45 if en == nil || !strings.HasPrefix(en.Name(), "vulnmatch/") { 46 return nil, errors.New("vulnmatch/ enricher is required") 47 } 48 49 // vulnmatch/ enrichers (so far) do not require ScanInput. 50 inv, pkgsToNodes := graphToInventory(graph) 51 if err := en.Enrich(ctx, nil, inv); err != nil { 52 return nil, err 53 } 54 55 // The root node is of the graph is excluded from the inventory. 56 // So we need to prepend a nil slice to nodeVulns so that the indices line up with graph.Nodes[i] <=> nodeVulns[i] 57 nodeVulns := make([][]*osvpb.Vulnerability, len(graph.Nodes)) 58 for _, pVuln := range inv.PackageVulns { 59 for _, nID := range pkgsToNodes[pVuln.Package] { 60 nodeVulns[nID] = append(nodeVulns[nID], pVuln.Vulnerability) 61 } 62 } 63 64 // Find the dependency subgraphs of the vulnerable dependencies. 65 var vulnerableNodes []resolve.NodeID 66 uniqueVulns := make(map[string]*osvpb.Vulnerability) 67 for i, vulns := range nodeVulns { 68 if len(vulns) > 0 { 69 vulnerableNodes = append(vulnerableNodes, resolve.NodeID(i)) 70 } 71 for _, vuln := range vulns { 72 uniqueVulns[vuln.Id] = vuln 73 } 74 } 75 76 nodeSubgraphs := ComputeSubgraphs(graph, vulnerableNodes) 77 vulnSubgraphs := make(map[string][]*DependencySubgraph) 78 for i, nID := range vulnerableNodes { 79 for _, vuln := range nodeVulns[nID] { 80 vulnSubgraphs[vuln.Id] = append(vulnSubgraphs[vuln.Id], nodeSubgraphs[i]) 81 } 82 } 83 84 // Construct the Vulnerabilities 85 vulns := make([]Vulnerability, 0, len(uniqueVulns)) 86 for id, vuln := range uniqueVulns { 87 vuln := Vulnerability{OSV: vuln, DevOnly: true} 88 vuln.Subgraphs = vulnSubgraphs[id] 89 vuln.DevOnly = !slices.ContainsFunc(vuln.Subgraphs, func(ds *DependencySubgraph) bool { return !ds.IsDevOnly(depGroups) }) 90 vulns = append(vulns, vuln) 91 } 92 93 return vulns, nil 94 } 95 96 // packageKey is a map key for uniquely identifying a package by its name and version. 97 type packageKey struct { 98 name string 99 version string 100 } 101 102 // graphToInventory is a helper function to convert a Graph into an Inventory for use with Enrichers. 103 // It also returns a map of packages to the graph nodes that they were found at. 104 func graphToInventory(g *resolve.Graph) (*inventory.Inventory, map[*extractor.Package][]resolve.NodeID) { 105 // Inventories / packages expect unique packages, so we need to keep track of which are duplicates 106 // (e.g. multiple versions of the same package in npm) 107 uniquePkgs := make(map[packageKey]*extractor.Package) 108 pkgsToNodes := make(map[*extractor.Package][]resolve.NodeID) 109 // g.Nodes[0] is the root node of the graph that should be excluded. 110 pkgs := make([]*extractor.Package, 0, len(g.Nodes)-1) 111 for i, n := range g.Nodes[1:] { 112 checkPkg := vulns.VKToPackage(n.Version) 113 var pkg *extractor.Package 114 var ok bool 115 key := packageKey{name: checkPkg.Name, version: checkPkg.Version} 116 if pkg, ok = uniquePkgs[key]; !ok { 117 pkg = checkPkg 118 uniquePkgs[key] = pkg 119 pkgs = append(pkgs, pkg) 120 } 121 pkgsToNodes[pkg] = append(pkgsToNodes[pkg], resolve.NodeID(i+1)) 122 } 123 124 return &inventory.Inventory{Packages: pkgs}, pkgsToNodes 125 }