github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/vulns/vulns.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 vulns implements local matching for OSV records.
    16  package vulns
    17  
    18  import (
    19  	"slices"
    20  
    21  	"deps.dev/util/resolve"
    22  	"github.com/google/osv-scalibr/extractor"
    23  	"github.com/google/osv-scalibr/guidedremediation/internal/util"
    24  	"github.com/google/osv-scalibr/purl"
    25  	"github.com/ossf/osv-schema/bindings/go/osvconstants"
    26  	osvpb "github.com/ossf/osv-schema/bindings/go/osvschema"
    27  )
    28  
    29  // VKToPackage converts a resolve.VersionKey to an *extractor.Package
    30  func VKToPackage(vk resolve.VersionKey) *extractor.Package {
    31  	ecosystem := string(util.DepsDevToOSVEcosystem(vk.System))
    32  	p := &extractor.Package{
    33  		Name:     vk.Name,
    34  		Version:  vk.Version,
    35  		PURLType: toPURLType(ecosystem),
    36  		Metadata: vk.System,
    37  	}
    38  	return p
    39  }
    40  
    41  // toPURLType an OSV ecosystem into a PURL type.
    42  func toPURLType(ecosystem string) string {
    43  	switch ecosystem {
    44  	case string(osvconstants.EcosystemNPM):
    45  		return purl.TypeNPM
    46  	case string(osvconstants.EcosystemMaven):
    47  		return purl.TypeMaven
    48  	case string(osvconstants.EcosystemPyPI):
    49  		return purl.TypePyPi
    50  	}
    51  	return ""
    52  }
    53  
    54  // IsAffected returns true if the Vulnerability applies to the package version of the Package.
    55  func IsAffected(vuln *osvpb.Vulnerability, pkg *extractor.Package) bool {
    56  	resolveSys := util.OSVToDepsDevEcosystem(pkg.Ecosystem().Ecosystem)
    57  	if resolveSys == resolve.UnknownSystem {
    58  		return false
    59  	}
    60  	sys := resolveSys.Semver()
    61  	for _, affected := range vuln.Affected {
    62  		if affected.Package.Ecosystem != pkg.Ecosystem().String() ||
    63  			affected.Package.Name != pkg.Name {
    64  			continue
    65  		}
    66  		if slices.Contains(affected.Versions, pkg.Version) {
    67  			return true
    68  		}
    69  		for _, r := range affected.Ranges {
    70  			if r.Type != osvpb.Range_ECOSYSTEM &&
    71  				!(r.Type == osvpb.Range_SEMVER && affected.Package.Ecosystem == "npm") {
    72  				continue
    73  			}
    74  			events := slices.Clone(r.Events)
    75  			eventVersion := func(e *osvpb.Event) string {
    76  				if e.Introduced != "" {
    77  					return e.Introduced
    78  				}
    79  				if e.Fixed != "" {
    80  					return e.Fixed
    81  				}
    82  				return e.LastAffected
    83  			}
    84  			slices.SortFunc(events, func(a, b *osvpb.Event) int {
    85  				aVer := eventVersion(a)
    86  				bVer := eventVersion(b)
    87  				if aVer == "0" {
    88  					if bVer == "0" {
    89  						return 0
    90  					}
    91  					return -1
    92  				}
    93  				if bVer == "0" {
    94  					return 1
    95  				}
    96  				// sys.Compare on strings is expensive, should consider precomputing sys.Parse
    97  				return sys.Compare(aVer, bVer)
    98  			})
    99  			idx, exact := slices.BinarySearchFunc(events, pkg.Version, func(e *osvpb.Event, v string) int {
   100  				eVer := eventVersion(e)
   101  				if eVer == "0" {
   102  					return -1
   103  				}
   104  				return sys.Compare(eVer, v)
   105  			})
   106  			if exact {
   107  				e := events[idx]
   108  				// Version is exactly on a range-inclusive event
   109  				if e.Introduced != "" || e.LastAffected != "" {
   110  					return true
   111  				}
   112  				// Version is between events, only match if previous event is Introduced
   113  			} else if idx != 0 && events[idx-1].Introduced != "" {
   114  				return true
   115  			}
   116  		}
   117  	}
   118  	return false
   119  }