github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/remediation/match_test.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 remediation_test 16 17 import ( 18 "testing" 19 20 "deps.dev/util/resolve" 21 "github.com/google/osv-scalibr/guidedremediation/internal/remediation" 22 "github.com/google/osv-scalibr/guidedremediation/internal/resolution" 23 "github.com/google/osv-scalibr/guidedremediation/options" 24 osvpb "github.com/ossf/osv-schema/bindings/go/osvschema" 25 ) 26 27 func TestMatchVuln(t *testing.T) { 28 var ( 29 // ID: VULN-001, Dev: false, Severity: 6.6, Depth: 3, Aliases: CVE-111, OSV-2 30 vuln1 = resolution.Vulnerability{ 31 OSV: &osvpb.Vulnerability{ 32 Id: "VULN-001", 33 Severity: []*osvpb.Severity{ 34 {Type: osvpb.Severity_CVSS_V3, Score: "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H"}, // 6.6 35 {Type: osvpb.Severity_CVSS_V2, Score: "AV:L/AC:L/Au:S/C:P/I:P/A:C"}, // 5.7 36 }, 37 Aliases: []string{"CVE-111", "OSV-2"}, 38 }, 39 DevOnly: false, 40 Subgraphs: []*resolution.DependencySubgraph{{ 41 Dependency: 3, 42 Nodes: map[resolve.NodeID]resolution.GraphNode{ 43 3: { 44 Distance: 0, 45 Parents: []resolve.Edge{{From: 2, To: 3}}, 46 Children: []resolve.Edge{}, 47 }, 48 2: { 49 Distance: 1, 50 Parents: []resolve.Edge{{From: 1, To: 2}}, 51 Children: []resolve.Edge{{From: 2, To: 3}}, 52 }, 53 1: { 54 Distance: 2, 55 Parents: []resolve.Edge{{From: 0, To: 1}}, 56 Children: []resolve.Edge{{From: 1, To: 2}}, 57 }, 58 0: { 59 Distance: 3, 60 Parents: []resolve.Edge{}, 61 Children: []resolve.Edge{{From: 0, To: 1}}, 62 }, 63 }, 64 }}, 65 } 66 // ID: VULN-002, Dev: true, Severity: N/A, Depth: 2 67 vuln2 = resolution.Vulnerability{ 68 OSV: &osvpb.Vulnerability{ 69 Id: "VULN-002", 70 // No severity 71 }, 72 DevOnly: true, 73 Subgraphs: []*resolution.DependencySubgraph{{ 74 Dependency: 3, 75 Nodes: map[resolve.NodeID]resolution.GraphNode{ 76 3: { 77 Distance: 0, 78 Parents: []resolve.Edge{{From: 2, To: 3}, {From: 1, To: 3}}, 79 Children: []resolve.Edge{}, 80 }, 81 2: { 82 Distance: 1, 83 Parents: []resolve.Edge{{From: 1, To: 2}}, 84 Children: []resolve.Edge{{From: 2, To: 3}}, 85 }, 86 1: { 87 Distance: 1, 88 Parents: []resolve.Edge{{From: 0, To: 1}}, 89 Children: []resolve.Edge{{From: 1, To: 2}, {From: 1, To: 3}}, 90 }, 91 0: { 92 Distance: 2, 93 Parents: []resolve.Edge{}, 94 Children: []resolve.Edge{{From: 0, To: 1}}, 95 }, 96 }, 97 }}, 98 } 99 100 // ID: VULN-003, Dev: false, Severity: 7.0, Depth: 1 101 vuln3 = resolution.Vulnerability{ 102 OSV: &osvpb.Vulnerability{ 103 Id: "VULN-003", 104 Aliases: []string{"CVE-111", "OSV-2"}, 105 Affected: []*osvpb.Affected{ 106 { 107 Package: &osvpb.Package{ 108 Ecosystem: "npm", 109 Name: "pkg", 110 }, 111 Ranges: []*osvpb.Range{ 112 { 113 Type: osvpb.Range_SEMVER, 114 Events: []*osvpb.Event{ 115 {Introduced: "0"}, 116 {Fixed: "1.9.1"}, 117 }, 118 }, 119 }, 120 Severity: []*osvpb.Severity{ 121 {Type: osvpb.Severity_CVSS_V4, Score: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:H/SA:H"}, // 9.9 122 }, 123 }, 124 { 125 Package: &osvpb.Package{ 126 Ecosystem: "npm", 127 Name: "pkg", 128 }, 129 Ranges: []*osvpb.Range{ 130 { 131 Type: osvpb.Range_SEMVER, 132 Events: []*osvpb.Event{ 133 {Introduced: "2.0.0"}, 134 {Fixed: "2.9.9"}, 135 }, 136 }, 137 }, 138 Severity: []*osvpb.Severity{ 139 {Type: osvpb.Severity_CVSS_V4, Score: "CVSS:4.0/AV:L/AC:H/AT:P/PR:H/UI:A/VC:H/VI:H/VA:L/SC:N/SI:H/SA:H"}, // 7.0 140 }, 141 }, 142 }, 143 }, 144 DevOnly: false, 145 Subgraphs: []*resolution.DependencySubgraph{{ 146 Dependency: 1, 147 Nodes: map[resolve.NodeID]resolution.GraphNode{ 148 1: { 149 Distance: 0, 150 Parents: []resolve.Edge{{From: 0, To: 1}}, 151 Version: resolve.VersionKey{ 152 PackageKey: resolve.PackageKey{ 153 System: resolve.NPM, 154 Name: "pkg", 155 }, 156 Version: "2.0.2", 157 }, 158 }, 159 0: { 160 Distance: 1, 161 Parents: []resolve.Edge{}, 162 Children: []resolve.Edge{{From: 0, To: 1}}, 163 }, 164 }, 165 }}, 166 } 167 ) 168 tests := []struct { 169 name string 170 vuln resolution.Vulnerability 171 opt options.RemediationOptions 172 want bool 173 }{ 174 { 175 name: "basic_match", 176 vuln: vuln1, 177 opt: options.RemediationOptions{ 178 DevDeps: true, 179 MaxDepth: -1, 180 }, 181 want: true, 182 }, 183 { 184 name: "accept_depth", 185 vuln: vuln2, 186 opt: options.RemediationOptions{ 187 DevDeps: true, 188 MaxDepth: 2, 189 }, 190 want: true, 191 }, 192 { 193 name: "reject_depth", 194 vuln: vuln2, 195 opt: options.RemediationOptions{ 196 DevDeps: true, 197 MaxDepth: 1, 198 }, 199 want: false, 200 }, 201 { 202 name: "accept_severity", 203 vuln: vuln1, 204 opt: options.RemediationOptions{ 205 DevDeps: true, 206 MaxDepth: -1, 207 MinSeverity: 6.6, 208 }, 209 want: true, 210 }, 211 { 212 name: "reject_severity", 213 vuln: vuln1, 214 opt: options.RemediationOptions{ 215 DevDeps: true, 216 MaxDepth: -1, 217 MinSeverity: 6.7, 218 }, 219 want: false, 220 }, 221 { 222 name: "accept_unknown_severity", 223 vuln: vuln2, 224 opt: options.RemediationOptions{ 225 DevDeps: true, 226 MaxDepth: -1, 227 MinSeverity: 10.0, 228 }, 229 want: true, 230 }, 231 { 232 name: "accept_non-dev", 233 vuln: vuln1, 234 opt: options.RemediationOptions{ 235 DevDeps: false, 236 MaxDepth: -1, 237 }, 238 want: true, 239 }, 240 { 241 name: "reject_dev", 242 vuln: vuln2, 243 opt: options.RemediationOptions{ 244 DevDeps: false, 245 MaxDepth: -1, 246 }, 247 want: false, 248 }, 249 { 250 name: "reject_ID_excluded", 251 vuln: vuln1, 252 opt: options.RemediationOptions{ 253 DevDeps: true, 254 MaxDepth: -1, 255 IgnoreVulns: []string{"VULN-001"}, 256 }, 257 want: false, 258 }, 259 { 260 name: "accept_matching_multiple", 261 vuln: vuln1, 262 opt: options.RemediationOptions{ 263 DevDeps: false, 264 MaxDepth: 3, 265 MinSeverity: 5.0, 266 IgnoreVulns: []string{"VULN-999"}, 267 }, 268 want: true, 269 }, 270 { 271 name: "reject_excluded_ID_in_alias", 272 vuln: vuln1, 273 opt: options.RemediationOptions{ 274 DevDeps: true, 275 MaxDepth: -1, 276 IgnoreVulns: []string{"OSV-2"}, 277 }, 278 want: false, 279 }, 280 { 281 name: "check_per-affected_severity", 282 vuln: vuln3, 283 opt: options.RemediationOptions{ 284 DevDeps: true, 285 MaxDepth: -1, 286 MinSeverity: 8.0, 287 }, 288 want: false, 289 }, 290 } 291 292 for _, tt := range tests { 293 t.Run(tt.name, func(t *testing.T) { 294 if got := remediation.MatchVuln(tt.opt, tt.vuln); got != tt.want { 295 t.Errorf("MatchVuln() = %v, want %v", got, tt.want) 296 } 297 }) 298 } 299 }