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 }