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  }