github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/internal/cpegenerate/generate_test.go (about) 1 package cpegenerate 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "testing" 8 9 "github.com/scylladb/go-set" 10 "github.com/scylladb/go-set/strset" 11 "github.com/stretchr/testify/assert" 12 13 "github.com/anchore/syft/syft/cpe" 14 "github.com/anchore/syft/syft/pkg" 15 ) 16 17 func keyValues(m map[string]string) []pkg.KeyValue { 18 var kvs []pkg.KeyValue 19 for k, v := range m { 20 kvs = append(kvs, pkg.KeyValue{ 21 Key: k, 22 Value: v, 23 }) 24 } 25 return kvs 26 } 27 28 func TestGeneratePackageCPEs(t *testing.T) { 29 tests := []struct { 30 name string 31 p pkg.Package 32 expected []string 33 }{ 34 { 35 name: "hyphen replacement", 36 p: pkg.Package{ 37 Name: "name-part", 38 Version: "3.2", 39 FoundBy: "some-analyzer", 40 Language: pkg.Python, 41 Type: pkg.DebPkg, 42 }, 43 expected: []string{ 44 "cpe:2.3:a:name-part:name-part:3.2:*:*:*:*:*:*:*", 45 "cpe:2.3:a:name-part:name_part:3.2:*:*:*:*:*:*:*", 46 "cpe:2.3:a:name-part:python-name-part:3.2:*:*:*:*:*:*:*", 47 "cpe:2.3:a:name-part:python_name_part:3.2:*:*:*:*:*:*:*", 48 "cpe:2.3:a:name:name-part:3.2:*:*:*:*:*:*:*", 49 "cpe:2.3:a:name:name_part:3.2:*:*:*:*:*:*:*", 50 "cpe:2.3:a:name:python-name-part:3.2:*:*:*:*:*:*:*", 51 "cpe:2.3:a:name:python_name_part:3.2:*:*:*:*:*:*:*", 52 "cpe:2.3:a:name_part:name-part:3.2:*:*:*:*:*:*:*", 53 "cpe:2.3:a:name_part:name_part:3.2:*:*:*:*:*:*:*", 54 "cpe:2.3:a:name_part:python-name-part:3.2:*:*:*:*:*:*:*", 55 "cpe:2.3:a:name_part:python_name_part:3.2:*:*:*:*:*:*:*", 56 "cpe:2.3:a:python-name-part:name-part:3.2:*:*:*:*:*:*:*", 57 "cpe:2.3:a:python-name-part:name_part:3.2:*:*:*:*:*:*:*", 58 "cpe:2.3:a:python-name-part:python-name-part:3.2:*:*:*:*:*:*:*", 59 "cpe:2.3:a:python-name-part:python_name_part:3.2:*:*:*:*:*:*:*", 60 "cpe:2.3:a:python-name:name-part:3.2:*:*:*:*:*:*:*", 61 "cpe:2.3:a:python-name:name_part:3.2:*:*:*:*:*:*:*", 62 "cpe:2.3:a:python-name:python-name-part:3.2:*:*:*:*:*:*:*", 63 "cpe:2.3:a:python-name:python_name_part:3.2:*:*:*:*:*:*:*", 64 "cpe:2.3:a:python:name-part:3.2:*:*:*:*:*:*:*", 65 "cpe:2.3:a:python:name_part:3.2:*:*:*:*:*:*:*", 66 "cpe:2.3:a:python:python-name-part:3.2:*:*:*:*:*:*:*", 67 "cpe:2.3:a:python:python_name_part:3.2:*:*:*:*:*:*:*", 68 "cpe:2.3:a:python_name:name-part:3.2:*:*:*:*:*:*:*", 69 "cpe:2.3:a:python_name:name_part:3.2:*:*:*:*:*:*:*", 70 "cpe:2.3:a:python_name:python-name-part:3.2:*:*:*:*:*:*:*", 71 "cpe:2.3:a:python_name:python_name_part:3.2:*:*:*:*:*:*:*", 72 "cpe:2.3:a:python_name_part:name-part:3.2:*:*:*:*:*:*:*", 73 "cpe:2.3:a:python_name_part:name_part:3.2:*:*:*:*:*:*:*", 74 "cpe:2.3:a:python_name_part:python-name-part:3.2:*:*:*:*:*:*:*", 75 "cpe:2.3:a:python_name_part:python_name_part:3.2:*:*:*:*:*:*:*", 76 }, 77 }, 78 { 79 name: "python language", 80 p: pkg.Package{ 81 Name: "name", 82 Version: "3.2", 83 FoundBy: "some-analyzer", 84 Language: pkg.Python, 85 Type: pkg.DebPkg, 86 Metadata: pkg.PythonPackage{ 87 Author: "alex goodman", 88 AuthorEmail: "william.goodman@anchore.com", 89 }, 90 }, 91 expected: []string{ 92 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 93 "cpe:2.3:a:name:python-name:3.2:*:*:*:*:*:*:*", 94 "cpe:2.3:a:name:python_name:3.2:*:*:*:*:*:*:*", 95 "cpe:2.3:a:python-name:name:3.2:*:*:*:*:*:*:*", 96 "cpe:2.3:a:python-name:python-name:3.2:*:*:*:*:*:*:*", 97 "cpe:2.3:a:python-name:python_name:3.2:*:*:*:*:*:*:*", 98 "cpe:2.3:a:python:name:3.2:*:*:*:*:*:*:*", 99 "cpe:2.3:a:python:python-name:3.2:*:*:*:*:*:*:*", 100 "cpe:2.3:a:python:python_name:3.2:*:*:*:*:*:*:*", 101 "cpe:2.3:a:python_name:name:3.2:*:*:*:*:*:*:*", 102 "cpe:2.3:a:python_name:python-name:3.2:*:*:*:*:*:*:*", 103 "cpe:2.3:a:python_name:python_name:3.2:*:*:*:*:*:*:*", 104 "cpe:2.3:a:alex_goodman:name:3.2:*:*:*:*:*:*:*", 105 "cpe:2.3:a:alex_goodman:python-name:3.2:*:*:*:*:*:*:*", 106 "cpe:2.3:a:alex_goodman:python_name:3.2:*:*:*:*:*:*:*", 107 "cpe:2.3:a:william-goodman:name:3.2:*:*:*:*:*:*:*", 108 "cpe:2.3:a:william-goodman:python-name:3.2:*:*:*:*:*:*:*", 109 "cpe:2.3:a:william-goodman:python_name:3.2:*:*:*:*:*:*:*", 110 "cpe:2.3:a:william_goodman:name:3.2:*:*:*:*:*:*:*", 111 "cpe:2.3:a:william_goodman:python-name:3.2:*:*:*:*:*:*:*", 112 "cpe:2.3:a:william_goodman:python_name:3.2:*:*:*:*:*:*:*", 113 "cpe:2.3:a:alex_goodman_project:python_name:3.2:*:*:*:*:*:*:*", 114 "cpe:2.3:a:alex_goodman_project:name:3.2:*:*:*:*:*:*:*", 115 "cpe:2.3:a:alex_goodman_project:python-name:3.2:*:*:*:*:*:*:*", 116 "cpe:2.3:a:alex_goodmanproject:name:3.2:*:*:*:*:*:*:*", 117 "cpe:2.3:a:alex_goodmanproject:python-name:3.2:*:*:*:*:*:*:*", 118 "cpe:2.3:a:alex_goodmanproject:python_name:3.2:*:*:*:*:*:*:*", 119 "cpe:2.3:a:william_goodman_project:name:3.2:*:*:*:*:*:*:*", 120 "cpe:2.3:a:william_goodman_project:python-name:3.2:*:*:*:*:*:*:*", 121 "cpe:2.3:a:william_goodman_project:python_name:3.2:*:*:*:*:*:*:*", 122 "cpe:2.3:a:william_goodmanproject:name:3.2:*:*:*:*:*:*:*", 123 "cpe:2.3:a:william_goodmanproject:python-name:3.2:*:*:*:*:*:*:*", 124 "cpe:2.3:a:william_goodmanproject:python_name:3.2:*:*:*:*:*:*:*", 125 }, 126 }, 127 { 128 name: "javascript language", 129 p: pkg.Package{ 130 Name: "name", 131 Version: "3.2", 132 FoundBy: "some-analyzer", 133 Language: pkg.JavaScript, 134 Metadata: pkg.NpmPackage{ 135 Author: "jon", 136 URL: "https://github.com/bob/npm-name", 137 }, 138 }, 139 expected: []string{ 140 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 141 "cpe:2.3:a:bob:name:3.2:*:*:*:*:*:*:*", 142 }, 143 }, 144 { 145 name: "ruby language", 146 p: pkg.Package{ 147 Name: "name", 148 Version: "3.2", 149 FoundBy: "some-analyzer", 150 Language: pkg.Ruby, 151 Type: pkg.DebPkg, 152 Metadata: pkg.RubyGemspec{ 153 Authors: []string{ 154 "someones name", 155 "someones.elses.name@gmail.com", 156 }, 157 Homepage: "https://github.com/tom/ruby-name", 158 }, 159 }, 160 expected: []string{ 161 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 162 "cpe:2.3:a:ruby-lang:name:3.2:*:*:*:*:*:*:*", 163 "cpe:2.3:a:ruby:name:3.2:*:*:*:*:*:*:*", 164 "cpe:2.3:a:ruby_lang:name:3.2:*:*:*:*:*:*:*", 165 "cpe:2.3:a:someones-elses-name:name:3.2:*:*:*:*:*:*:*", 166 "cpe:2.3:a:someones-name:name:3.2:*:*:*:*:*:*:*", 167 "cpe:2.3:a:someones_elses_name:name:3.2:*:*:*:*:*:*:*", 168 "cpe:2.3:a:someones_name:name:3.2:*:*:*:*:*:*:*", 169 "cpe:2.3:a:tom:name:3.2:*:*:*:*:*:*:*", 170 }, 171 }, 172 { 173 name: "java language", 174 p: pkg.Package{ 175 Name: "name", 176 Version: "3.2", 177 FoundBy: "some-analyzer", 178 Language: pkg.Java, 179 Type: pkg.JavaPkg, 180 }, 181 expected: []string{ 182 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 183 }, 184 }, 185 { 186 name: "java language with groupID", 187 p: pkg.Package{ 188 Name: "name", 189 Version: "3.2", 190 FoundBy: "some-analyzer", 191 Language: pkg.Java, 192 Type: pkg.JavaPkg, 193 Metadata: pkg.JavaArchive{ 194 PomProperties: &pkg.JavaPomProperties{ 195 GroupID: "org.sonatype.nexus", 196 }, 197 }, 198 }, 199 expected: []string{ 200 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 201 "cpe:2.3:a:name:nexus:3.2:*:*:*:*:*:*:*", 202 "cpe:2.3:a:nexus:name:3.2:*:*:*:*:*:*:*", 203 "cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:*:*:*", 204 "cpe:2.3:a:sonatype:name:3.2:*:*:*:*:*:*:*", 205 "cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:*:*:*", 206 "cpe:2.3:a:org.sonatype.nexus:name:3.2:*:*:*:*:*:*:*", 207 "cpe:2.3:a:org.sonatype.nexus:nexus:3.2:*:*:*:*:*:*:*", 208 }, 209 }, 210 { 211 name: "java with URL in metadata", // regression: https://github.com/anchore/grype/issues/417 212 p: pkg.Package{ 213 Name: "wstx-asl", 214 Version: "3.2.7", 215 Type: pkg.JavaPkg, 216 Metadata: pkg.JavaArchive{ 217 Manifest: &pkg.JavaManifest{ 218 Main: keyValues(map[string]string{ 219 "Ant-Version": "Apache Ant 1.6.5", 220 "Built-By": "tatu", 221 "Created-By": "1.4.2_03-b02 (Sun Microsystems Inc.)", 222 "Implementation-Title": "WoodSToX XML-processor", 223 "Implementation-Vendor": "woodstox.codehaus.org", 224 "Implementation-Version": "3.2.7", 225 "Manifest-Version": "1.0", 226 "Specification-Title": "StAX 1.0 API", 227 "Specification-Vendor": "http://jcp.org/en/jsr/detail?id=173", 228 "Specification-Version": "1.0", 229 }), 230 }, 231 }, 232 }, 233 expected: []string{ 234 "cpe:2.3:a:woodstox_codehaus_org:wstx-asl:3.2.7:*:*:*:*:*:*:*", 235 "cpe:2.3:a:woodstox_codehaus_org:wstx_asl:3.2.7:*:*:*:*:*:*:*", 236 "cpe:2.3:a:woodstox-codehaus-org:wstx_asl:3.2.7:*:*:*:*:*:*:*", 237 "cpe:2.3:a:woodstox-codehaus-org:wstx-asl:3.2.7:*:*:*:*:*:*:*", 238 "cpe:2.3:a:wstx_asl:wstx-asl:3.2.7:*:*:*:*:*:*:*", 239 "cpe:2.3:a:wstx-asl:wstx-asl:3.2.7:*:*:*:*:*:*:*", 240 "cpe:2.3:a:wstx-asl:wstx_asl:3.2.7:*:*:*:*:*:*:*", 241 "cpe:2.3:a:wstx_asl:wstx_asl:3.2.7:*:*:*:*:*:*:*", 242 "cpe:2.3:a:wstx:wstx_asl:3.2.7:*:*:*:*:*:*:*", 243 "cpe:2.3:a:wstx:wstx-asl:3.2.7:*:*:*:*:*:*:*", 244 }, 245 }, 246 { 247 name: "jenkins package identified via pkg type", 248 p: pkg.Package{ 249 Name: "name", 250 Version: "3.2", 251 FoundBy: "some-analyzer", 252 Language: pkg.Java, 253 Type: pkg.JenkinsPluginPkg, 254 }, 255 expected: []string{ 256 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 257 }, 258 }, 259 { 260 name: "java language - multi tier manifest fields", 261 p: pkg.Package{ 262 Name: "cxf-rt-bindings-xml", 263 Version: "3.3.10", 264 FoundBy: "java-cataloger", 265 Language: pkg.Java, 266 Type: pkg.JavaPkg, 267 Metadata: pkg.JavaArchive{ 268 VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar", 269 Manifest: &pkg.JavaManifest{ 270 Main: keyValues(map[string]string{ 271 "Automatic-Module-Name": "org.apache.cxf.binding.xml", 272 "Bnd-LastModified": "1615836524860", 273 "Build-Jdk": "1.8.0_261", 274 "Built-By": "dkulp", 275 "Bundle-ActivationPolicy": "lazy", 276 "Bundle-Description": "Apache CXF Runtime XML Binding", 277 "Bundle-DocURL": "http://cxf.apache.org", 278 "Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt", 279 "Bundle-ManifestVersion": "2", 280 "Bundle-Name": "Apache CXF Runtime XML Binding", 281 "Bundle-SymbolicName": "org.apache.cxf.cxf-rt-bindings-xml", 282 "Bundle-Vendor": "The Apache Software Foundation", 283 "Bundle-Version": "3.3.10", 284 "Created-By": "Apache Maven Bundle Plugin", 285 "Export-Package": "org.apache.cxf.binding.xml;version=\"3.3.10\",org.apache.cxf.binding.xml.wsdl11;version=\"3.3.10\",org.apache.cxf.binding.xml.interceptor;version=\"3.3.10\",org.apache.cxf.bindings.xformat;version=\"3.3.10\"", 286 "Implementation-Vendor": "The Apache Software Foundation", 287 "Implementation-Vendor-Id": "org.apache", 288 "Implementation-Version": "3.3.10", 289 "Import-Package": "javax.xml.bind;version=\"[0,3)\",javax.xml.bind.annotation;version=\"[0,3)\",javax.wsdl;resolution:=optional,javax.wsdl.extensions;resolution:=optional,javax.wsdl.extensions.http;resolution:=optional,javax.xml.namespace,javax.xml.stream,org.apache.cxf;version=\"[3.3,4)\",org.apache.cxf.binding;version=\"[3.3,4)\",org.apache.cxf.binding.xml,org.apache.cxf.binding.xml.interceptor,org.apache.cxf.bindings.xformat,org.apache.cxf.common.i18n;version=\"[3.3,4)\",org.apache.cxf.common.injection;version=\"[3.3,4)\",org.apache.cxf.common.logging;version=\"[3.3,4)\",org.apache.cxf.common.util;version=\"[3.3,4)\",org.apache.cxf.endpoint;version=\"[3.3,4)\",org.apache.cxf.helpers;version=\"[3.3,4)\",org.apache.cxf.interceptor;version=\"[3.3,4)\",org.apache.cxf.message;version=\"[3.3,4)\",org.apache.cxf.service.model;version=\"[3.3,4)\",org.apache.cxf.staxutils;version=\"[3.3,4)\",org.apache.cxf.tools.common;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.tools.validator;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.transport;version=\"[3.3,4)\",org.apache.cxf.wsdl;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.wsdl.http;version=\"[3.3,4)\",org.apache.cxf.wsdl.interceptors;version=\"[3.3,4)\";resolution:=optional,org.w3c.dom", 290 "Manifest-Version": "1.0", 291 "Require-Capability": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"", 292 "Specification-Vendor": "The Apache Software Foundation", 293 "Specification-Version": "3.3.10", 294 "Tool": "Bnd-4.2.0.201903051501", 295 }), 296 }, 297 PomProperties: &pkg.JavaPomProperties{ 298 Path: "META-INF/maven/org.apache.cxf/cxf-rt-bindings-xml/pom.properties", 299 GroupID: "org.apache.cxf", 300 ArtifactID: "cxf-rt-bindings-xml", 301 Version: "3.3.10", 302 }, 303 }, 304 }, 305 expected: []string{ 306 "cpe:2.3:a:apache:cxf-rt-bindings-xml:3.3.10:*:*:*:*:*:*:*", 307 "cpe:2.3:a:apache:cxf:3.3.10:*:*:*:*:*:*:*", 308 "cpe:2.3:a:apache:cxf_rt_bindings_xml:3.3.10:*:*:*:*:*:*:*", 309 }, 310 }, 311 { 312 name: "rpm archive vendor selection", 313 p: pkg.Package{ 314 Name: "name", 315 Version: "3.2", 316 FoundBy: "some-analyzer", 317 Type: pkg.RpmPkg, 318 Metadata: pkg.RpmArchive{ 319 Vendor: "some-vendor", 320 }, 321 }, 322 expected: []string{ 323 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 324 "cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*", 325 "cpe:2.3:a:some_vendor:name:3.2:*:*:*:*:*:*:*", 326 }, 327 }, 328 { 329 name: "rpm vendor selection", 330 p: pkg.Package{ 331 Name: "name", 332 Version: "3.2", 333 FoundBy: "some-analyzer", 334 Type: pkg.RpmPkg, 335 Metadata: pkg.RpmDBEntry{ 336 Vendor: "some-vendor", 337 }, 338 }, 339 expected: []string{ 340 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 341 "cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*", 342 "cpe:2.3:a:some_vendor:name:3.2:*:*:*:*:*:*:*", 343 }, 344 }, 345 { 346 name: "rpm with epoch", 347 p: pkg.Package{ 348 Name: "name", 349 Version: "1:3.2", 350 FoundBy: "some-analyzer", 351 Type: pkg.RpmPkg, 352 Metadata: pkg.RpmDBEntry{ 353 Vendor: "some-vendor", 354 }, 355 }, 356 expected: []string{ 357 "cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*", 358 "cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*", 359 "cpe:2.3:a:some_vendor:name:1\\:3.2:*:*:*:*:*:*:*", 360 }, 361 }, 362 { 363 name: "deb with epoch", 364 p: pkg.Package{ 365 Name: "name", 366 Version: "1:3.2", 367 FoundBy: "some-analyzer", 368 Type: pkg.DebPkg, 369 Metadata: pkg.DpkgDBEntry{}, 370 }, 371 expected: []string{ 372 "cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*", 373 }, 374 }, 375 { 376 name: "cloudbees jenkins package identified via groupId", 377 p: pkg.Package{ 378 Name: "name", 379 Version: "3.2", 380 FoundBy: "some-analyzer", 381 Language: pkg.Java, 382 Type: pkg.JenkinsPluginPkg, 383 Metadata: pkg.JavaArchive{ 384 PomProperties: &pkg.JavaPomProperties{ 385 GroupID: "com.cloudbees.jenkins.plugins", 386 }, 387 }, 388 }, 389 expected: []string{ 390 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 391 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 392 "cpe:2.3:a:cloudbees:name:3.2:*:*:*:*:*:*:*", 393 "cpe:2.3:a:com.cloudbees.jenkins.plugins:name:3.2:*:*:*:*:*:*:*", 394 }, 395 }, 396 { 397 name: "jenkins.io package identified via groupId prefix", 398 p: pkg.Package{ 399 Name: "name", 400 Version: "3.2", 401 FoundBy: "some-analyzer", 402 Language: pkg.Java, 403 Type: pkg.JenkinsPluginPkg, 404 Metadata: pkg.JavaArchive{ 405 PomProperties: &pkg.JavaPomProperties{ 406 GroupID: "io.jenkins.plugins.name.something", 407 }, 408 }, 409 }, 410 expected: []string{ 411 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 412 "cpe:2.3:a:name:something:3.2:*:*:*:*:*:*:*", 413 "cpe:2.3:a:something:name:3.2:*:*:*:*:*:*:*", 414 "cpe:2.3:a:something:something:3.2:*:*:*:*:*:*:*", 415 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 416 "cpe:2.3:a:jenkins:something:3.2:*:*:*:*:*:*:*", 417 "cpe:2.3:a:io.jenkins.plugins.name.something:name:3.2:*:*:*:*:*:*:*", 418 "cpe:2.3:a:io.jenkins.plugins.name.something:something:3.2:*:*:*:*:*:*:*", 419 }, 420 }, 421 { 422 name: "jenkins.io package identified via groupId", 423 p: pkg.Package{ 424 Name: "name", 425 Version: "3.2", 426 FoundBy: "some-analyzer", 427 Language: pkg.Java, 428 Type: pkg.JenkinsPluginPkg, 429 Metadata: pkg.JavaArchive{ 430 PomProperties: &pkg.JavaPomProperties{ 431 GroupID: "io.jenkins.plugins", 432 }, 433 }, 434 }, 435 expected: []string{ 436 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 437 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 438 "cpe:2.3:a:io.jenkins.plugins:name:3.2:*:*:*:*:*:*:*", 439 }, 440 }, 441 { 442 name: "jenkins-ci.io package identified via groupId", 443 p: pkg.Package{ 444 Name: "name", 445 Version: "3.2", 446 FoundBy: "some-analyzer", 447 Language: pkg.Java, 448 Type: pkg.JenkinsPluginPkg, 449 Metadata: pkg.JavaArchive{ 450 PomProperties: &pkg.JavaPomProperties{ 451 GroupID: "io.jenkins-ci.plugins", 452 }, 453 }, 454 }, 455 expected: []string{ 456 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 457 "cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*", 458 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 459 "cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*", 460 "cpe:2.3:a:io.jenkins-ci.plugins:name:3.2:*:*:*:*:*:*:*", 461 }, 462 }, 463 { 464 name: "jenkins-ci.org package identified via groupId", 465 p: pkg.Package{ 466 Name: "name", 467 Version: "3.2", 468 FoundBy: "some-analyzer", 469 Language: pkg.Java, 470 Type: pkg.JenkinsPluginPkg, 471 Metadata: pkg.JavaArchive{ 472 PomProperties: &pkg.JavaPomProperties{ 473 GroupID: "org.jenkins-ci.plugins", 474 }, 475 }, 476 }, 477 expected: []string{ 478 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 479 "cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*", 480 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 481 "cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*", 482 "cpe:2.3:a:org.jenkins-ci.plugins:name:3.2:*:*:*:*:*:*:*", 483 }, 484 }, 485 { 486 name: "jira-atlassian filtering", 487 p: pkg.Package{ 488 Name: "jira_client_core", 489 Version: "3.2", 490 FoundBy: "some-analyzer", 491 Language: pkg.Java, 492 Type: pkg.JavaPkg, 493 Metadata: pkg.JavaArchive{ 494 PomProperties: &pkg.JavaPomProperties{ 495 GroupID: "org.atlassian.jira", 496 ArtifactID: "jira_client_core", 497 }, 498 }, 499 }, 500 expected: []string{ 501 "cpe:2.3:a:atlassian:jira-client-core:3.2:*:*:*:*:*:*:*", 502 "cpe:2.3:a:atlassian:jira_client_core:3.2:*:*:*:*:*:*:*", 503 "cpe:2.3:a:jira-client-core:jira-client-core:3.2:*:*:*:*:*:*:*", 504 "cpe:2.3:a:jira-client-core:jira:3.2:*:*:*:*:*:*:*", 505 "cpe:2.3:a:jira-client-core:jira_client_core:3.2:*:*:*:*:*:*:*", 506 "cpe:2.3:a:jira-client:jira-client-core:3.2:*:*:*:*:*:*:*", 507 "cpe:2.3:a:jira-client:jira:3.2:*:*:*:*:*:*:*", 508 "cpe:2.3:a:jira-client:jira_client_core:3.2:*:*:*:*:*:*:*", 509 "cpe:2.3:a:jira:jira-client-core:3.2:*:*:*:*:*:*:*", 510 "cpe:2.3:a:jira:jira_client_core:3.2:*:*:*:*:*:*:*", 511 "cpe:2.3:a:jira_client:jira-client-core:3.2:*:*:*:*:*:*:*", 512 "cpe:2.3:a:jira_client:jira:3.2:*:*:*:*:*:*:*", 513 "cpe:2.3:a:jira_client:jira_client_core:3.2:*:*:*:*:*:*:*", 514 "cpe:2.3:a:jira_client_core:jira-client-core:3.2:*:*:*:*:*:*:*", 515 "cpe:2.3:a:jira_client_core:jira:3.2:*:*:*:*:*:*:*", 516 "cpe:2.3:a:jira_client_core:jira_client_core:3.2:*:*:*:*:*:*:*", 517 "cpe:2.3:a:org.atlassian.jira:jira:3.2:*:*:*:*:*:*:*", 518 "cpe:2.3:a:org.atlassian.jira:jira_client_core:3.2:*:*:*:*:*:*:*", 519 "cpe:2.3:a:org.atlassian.jira:jira-client-core:3.2:*:*:*:*:*:*:*", 520 }, 521 }, 522 { 523 name: "jenkins filtering", 524 p: pkg.Package{ 525 Name: "cloudbees-installation-manager", 526 Version: "2.89.0.33", 527 FoundBy: "some-analyzer", 528 Language: pkg.Java, 529 Type: pkg.JavaPkg, 530 Metadata: pkg.JavaArchive{ 531 PomProperties: &pkg.JavaPomProperties{ 532 GroupID: "com.cloudbees.jenkins.modules", 533 ArtifactID: "cloudbees-installation-manager", 534 }, 535 }, 536 }, 537 expected: []string{ 538 "cpe:2.3:a:cloudbees-installation-manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 539 "cpe:2.3:a:cloudbees-installation-manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 540 "cpe:2.3:a:cloudbees-installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 541 "cpe:2.3:a:cloudbees-installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 542 "cpe:2.3:a:cloudbees:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 543 "cpe:2.3:a:cloudbees:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 544 "cpe:2.3:a:cloudbees_installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 545 "cpe:2.3:a:cloudbees_installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 546 "cpe:2.3:a:cloudbees_installation_manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 547 "cpe:2.3:a:cloudbees_installation_manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 548 "cpe:2.3:a:jenkins:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 549 "cpe:2.3:a:jenkins:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 550 "cpe:2.3:a:modules:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 551 "cpe:2.3:a:modules:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 552 "cpe:2.3:a:com.cloudbees.jenkins.modules:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 553 "cpe:2.3:a:com.cloudbees.jenkins.modules:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 554 }, 555 }, 556 { 557 name: "go product and vendor candidates are wired up", 558 p: pkg.Package{ 559 Name: "github.com/someone/something", 560 Version: "3.2", 561 FoundBy: "go-cataloger", 562 Language: pkg.Go, 563 Type: pkg.GoModulePkg, 564 }, 565 expected: []string{ 566 "cpe:2.3:a:someone:something:3.2:*:*:*:*:*:*:*", 567 }, 568 }, 569 { 570 name: "go product with vendor candidates and an extra sub-item", 571 p: pkg.Package{ 572 Name: "github.com/someone/something/more", 573 Version: "3.2", 574 FoundBy: "go-cataloger", 575 Language: pkg.Go, 576 Type: pkg.GoModulePkg, 577 }, 578 expected: []string{ 579 "cpe:2.3:a:someone:something\\/more:3.2:*:*:*:*:*:*:*", 580 }, 581 }, 582 { 583 name: "generate no CPEs for indeterminate golang package name", 584 p: pkg.Package{ 585 Name: "github.com/what", 586 Version: "3.2", 587 FoundBy: "go-cataloger", 588 Language: pkg.Go, 589 Type: pkg.GoModulePkg, 590 }, 591 expected: []string{}, 592 }, 593 { 594 name: "regression: handlebars within java archive", 595 p: pkg.Package{ 596 Name: "handlebars", 597 Version: "3.0.8", 598 Type: pkg.JavaPkg, 599 Language: pkg.Java, 600 FoundBy: "java-cataloger", 601 Metadata: pkg.JavaArchive{ 602 Manifest: &pkg.JavaManifest{ 603 Main: keyValues(map[string]string{ 604 "Extension-Name": "handlebars", 605 "Group-Id": "org.jenkins-ci.ui", 606 "Hudson-Version": "2.204", 607 "Implementation-Title": "handlebars", 608 "Implementation-Version": "3.0.8", 609 "Plugin-Version": "3.0.8", 610 "Short-Name": "handlebars", 611 }), 612 }, 613 PomProperties: &pkg.JavaPomProperties{ 614 GroupID: "org.jenkins-ci.ui", 615 ArtifactID: "handlebars", 616 Version: "3.0.8", 617 }, 618 }, 619 }, 620 expected: []string{ 621 "cpe:2.3:a:handlebars:handlebars:3.0.8:*:*:*:*:*:*:*", 622 "cpe:2.3:a:handlebarsjs:handlebars:3.0.8:*:*:*:*:*:*:*", // important! 623 "cpe:2.3:a:jenkins-ci:handlebars:3.0.8:*:*:*:*:*:*:*", 624 "cpe:2.3:a:jenkins:handlebars:3.0.8:*:*:*:*:*:*:*", 625 "cpe:2.3:a:jenkins_ci:handlebars:3.0.8:*:*:*:*:*:*:*", 626 "cpe:2.3:a:ui:handlebars:3.0.8:*:*:*:*:*:*:*", 627 "cpe:2.3:a:org.jenkins-ci.ui:handlebars:3.0.8:*:*:*:*:*:*:*", 628 }, 629 }, 630 { 631 name: "regression: jenkins plugin active-directory", 632 p: pkg.Package{ 633 Name: "active-directory", 634 Version: "2.25.1", 635 Type: pkg.JenkinsPluginPkg, 636 FoundBy: "java-cataloger", 637 Language: pkg.Java, 638 Metadata: pkg.JavaArchive{ 639 Manifest: &pkg.JavaManifest{ 640 Main: keyValues(map[string]string{ 641 "Extension-Name": "active-directory", 642 "Group-Id": "org.jenkins-ci.plugins", 643 }), 644 }, 645 PomProperties: &pkg.JavaPomProperties{ 646 GroupID: "org.jenkins-ci.plugins", 647 ArtifactID: "org.jenkins-ci.plugins", 648 Version: "2.25.1", 649 }, 650 }, 651 }, 652 expected: []string{ 653 "cpe:2.3:a:active-directory:active-directory:2.25.1:*:*:*:*:*:*:*", 654 "cpe:2.3:a:active-directory:active_directory:2.25.1:*:*:*:*:*:*:*", 655 "cpe:2.3:a:active:active-directory:2.25.1:*:*:*:*:*:*:*", 656 "cpe:2.3:a:active:active_directory:2.25.1:*:*:*:*:*:*:*", 657 "cpe:2.3:a:active_directory:active-directory:2.25.1:*:*:*:*:*:*:*", 658 "cpe:2.3:a:active_directory:active_directory:2.25.1:*:*:*:*:*:*:*", 659 "cpe:2.3:a:jenkins-ci:active-directory:2.25.1:*:*:*:*:*:*:*", 660 "cpe:2.3:a:jenkins-ci:active_directory:2.25.1:*:*:*:*:*:*:*", 661 "cpe:2.3:a:jenkins:active-directory:2.25.1:*:*:*:*:*:*:*", // important! 662 "cpe:2.3:a:jenkins:active_directory:2.25.1:*:*:*:*:*:*:*", // important! 663 "cpe:2.3:a:jenkins_ci:active-directory:2.25.1:*:*:*:*:*:*:*", 664 "cpe:2.3:a:jenkins_ci:active_directory:2.25.1:*:*:*:*:*:*:*", 665 "cpe:2.3:a:org.jenkins-ci.plugins:active-directory:2.25.1:*:*:*:*:*:*:*", 666 "cpe:2.3:a:org.jenkins-ci.plugins:active_directory:2.25.1:*:*:*:*:*:*:*", 667 }, 668 }, 669 { 670 name: "regression: special characters in CPE should result in no generation", 671 p: pkg.Package{ 672 Name: "bundler", 673 Version: "2.1.4", 674 Type: pkg.GemPkg, 675 FoundBy: "gem-cataloger", 676 Language: pkg.Ruby, 677 Metadata: pkg.RubyGemspec{ 678 Name: "bundler", 679 Version: "2.1.4", 680 Authors: []string{ 681 "jessica lynn suttles", 682 "stephanie morillo", 683 "david rodrÃguez", 684 "andré medeiros", 685 }, 686 }, 687 }, 688 expected: []string{ 689 "cpe:2.3:a:bundler:bundler:2.1.4:*:*:*:*:*:*:*", 690 "cpe:2.3:a:ruby-lang:bundler:2.1.4:*:*:*:*:*:*:*", 691 "cpe:2.3:a:ruby:bundler:2.1.4:*:*:*:*:*:*:*", 692 "cpe:2.3:a:ruby_lang:bundler:2.1.4:*:*:*:*:*:*:*", 693 "cpe:2.3:a:jessica-lynn-suttles:bundler:2.1.4:*:*:*:*:*:*:*", 694 "cpe:2.3:a:jessica_lynn_suttles:bundler:2.1.4:*:*:*:*:*:*:*", 695 "cpe:2.3:a:stephanie-morillo:bundler:2.1.4:*:*:*:*:*:*:*", 696 "cpe:2.3:a:stephanie_morillo:bundler:2.1.4:*:*:*:*:*:*:*", 697 }, 698 }, 699 { 700 name: "regression: python redis shadows normal redis", 701 p: pkg.Package{ 702 Name: "redis", 703 Version: "2.1.4", 704 Type: pkg.PythonPkg, 705 FoundBy: "some-analyzer", 706 Language: pkg.Python, 707 }, 708 expected: []string{ 709 "cpe:2.3:a:python-redis:python-redis:2.1.4:*:*:*:*:*:*:*", 710 "cpe:2.3:a:python-redis:python_redis:2.1.4:*:*:*:*:*:*:*", 711 "cpe:2.3:a:python-redis:redis:2.1.4:*:*:*:*:*:*:*", 712 "cpe:2.3:a:python:python-redis:2.1.4:*:*:*:*:*:*:*", 713 "cpe:2.3:a:python:python_redis:2.1.4:*:*:*:*:*:*:*", 714 "cpe:2.3:a:python:redis:2.1.4:*:*:*:*:*:*:*", 715 "cpe:2.3:a:python_redis:python-redis:2.1.4:*:*:*:*:*:*:*", 716 "cpe:2.3:a:python_redis:python_redis:2.1.4:*:*:*:*:*:*:*", 717 "cpe:2.3:a:python_redis:redis:2.1.4:*:*:*:*:*:*:*", 718 }, 719 }, 720 { 721 name: "regression: ruby-rake apk missing expected ruby-lang:rake CPE", 722 p: pkg.Package{ 723 Name: "ruby-rake", 724 Version: "2.7.6-r0", 725 Type: pkg.ApkPkg, 726 FoundBy: "apk-db-analyzer", 727 Language: pkg.UnknownLanguage, 728 Metadata: pkg.ApkDBEntry{ 729 Package: "ruby-rake", 730 URL: "https://www.ruby-lang.org/", 731 OriginPackage: "ruby", 732 }, 733 }, 734 expected: []string{ 735 "cpe:2.3:a:ruby-lang:rake:2.7.6-r0:*:*:*:*:*:*:*", 736 "cpe:2.3:a:rake:rake:2.7.6-r0:*:*:*:*:*:*:*", 737 "cpe:2.3:a:rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 738 "cpe:2.3:a:rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 739 "cpe:2.3:a:ruby-lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 740 "cpe:2.3:a:ruby-lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 741 "cpe:2.3:a:ruby-rake:rake:2.7.6-r0:*:*:*:*:*:*:*", 742 "cpe:2.3:a:ruby-rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 743 "cpe:2.3:a:ruby-rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 744 "cpe:2.3:a:ruby:rake:2.7.6-r0:*:*:*:*:*:*:*", 745 "cpe:2.3:a:ruby:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 746 "cpe:2.3:a:ruby:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 747 "cpe:2.3:a:ruby_lang:rake:2.7.6-r0:*:*:*:*:*:*:*", 748 "cpe:2.3:a:ruby_lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 749 "cpe:2.3:a:ruby_lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 750 "cpe:2.3:a:ruby_rake:rake:2.7.6-r0:*:*:*:*:*:*:*", 751 "cpe:2.3:a:ruby_rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 752 "cpe:2.3:a:ruby_rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 753 }, 754 }, 755 { 756 name: "wordpress plugin", 757 p: pkg.Package{ 758 Name: "WP Coder", 759 Version: "2.5.1", 760 Type: pkg.WordpressPluginPkg, 761 Metadata: pkg.WordpressPluginEntry{ 762 PluginInstallDirectory: "wp-coder", 763 Author: "Wow-Company", 764 AuthorURI: "https://wow-estore.com", 765 }, 766 }, 767 expected: []string{ 768 "cpe:2.3:a:wow-company:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", 769 "cpe:2.3:a:wow-company:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", // this is the correct CPE relative to CVE-2021-25053 770 "cpe:2.3:a:wow-estore:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", 771 "cpe:2.3:a:wow-estore:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", 772 "cpe:2.3:a:wow:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", 773 "cpe:2.3:a:wow:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", 774 "cpe:2.3:a:wow_company:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", 775 "cpe:2.3:a:wow_company:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", 776 "cpe:2.3:a:wow_estore:wp-coder:2.5.1:*:*:*:*:wordpress:*:*", 777 "cpe:2.3:a:wow_estore:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", 778 }, 779 }, 780 { 781 name: "dotnet deps.json", 782 p: pkg.Package{ 783 Name: "Something", 784 Version: "2.5.1", 785 Type: pkg.DotnetPkg, 786 Metadata: pkg.DotnetDepsEntry{ 787 Name: "Something-Else", 788 789 Executables: map[string]pkg.DotnetPortableExecutableEntry{ 790 "1": { 791 AssemblyVersion: "assembly-version!", 792 LegalCopyright: "copyright!", 793 Comments: "comments!", 794 InternalName: "internal!", 795 CompanyName: "company!", 796 ProductName: "product!", 797 ProductVersion: "version!", 798 }, 799 }, 800 }, 801 }, 802 expected: []string{ 803 "cpe:2.3:a:company\\!:product\\!:2.5.1:*:*:*:*:*:*:*", 804 "cpe:2.3:a:company\\!:product\\!_.net:2.5.1:*:*:*:*:*:*:*", 805 "cpe:2.3:a:company\\!:something_else:2.5.1:*:*:*:*:*:*:*", 806 "cpe:2.3:a:company\\!:something_else_.net:2.5.1:*:*:*:*:*:*:*", 807 "cpe:2.3:a:something_else:product\\!:2.5.1:*:*:*:*:*:*:*", 808 "cpe:2.3:a:something_else:product\\!_.net:2.5.1:*:*:*:*:*:*:*", 809 "cpe:2.3:a:something_else:something_else:2.5.1:*:*:*:*:*:*:*", 810 "cpe:2.3:a:something_else:something_else_.net:2.5.1:*:*:*:*:*:*:*", 811 }, 812 }, 813 { 814 name: "dotnet executable", 815 p: pkg.Package{ 816 Name: "Something", 817 Version: "2.5.1", 818 Type: pkg.DotnetPkg, 819 Metadata: pkg.DotnetPortableExecutableEntry{ 820 AssemblyVersion: "assembly-version!", 821 LegalCopyright: "copyright!", 822 Comments: "comments!", 823 InternalName: "internal!", 824 CompanyName: "company!", 825 ProductName: "product!", 826 ProductVersion: "version!", 827 }, 828 }, 829 expected: []string{ 830 "cpe:2.3:a:company\\!:product\\!:2.5.1:*:*:*:*:*:*:*", 831 "cpe:2.3:a:company\\!:product\\!_.net:2.5.1:*:*:*:*:*:*:*", 832 }, 833 }, 834 { 835 name: "dotnet package.lock", 836 p: pkg.Package{ 837 Name: "Something", 838 Version: "2.5.1", 839 Type: pkg.DotnetPkg, 840 Metadata: pkg.DotnetPackagesLockEntry{ 841 Name: "Something-Else", 842 }, 843 }, 844 expected: []string{ 845 "cpe:2.3:a:something_else:something_else:2.5.1:*:*:*:*:*:*:*", 846 "cpe:2.3:a:something_else:something_else_.net:2.5.1:*:*:*:*:*:*:*", 847 }, 848 }, 849 { 850 name: "ML model package should generate no CPEs", 851 p: pkg.Package{ 852 Name: "llama3-8b", 853 Version: "3.0", 854 Type: pkg.ModelPkg, 855 }, 856 expected: []string{}, 857 }, 858 } 859 860 for _, test := range tests { 861 t.Run(test.name, func(t *testing.T) { 862 actual := FromPackageAttributes(test.p) 863 expectedCpeSet := set.NewStringSet() 864 for _, cpeStr := range test.expected { 865 expectedCpeSet.Add("syft-generated:" + cpeStr) 866 } 867 868 actualCpeSet := set.NewStringSet() 869 for _, a := range actual { 870 actualCpeSet.Add(fmt.Sprintf("%s:%s", a.Source.String(), a.Attributes.String())) 871 } 872 873 extra := strset.Difference(actualCpeSet, expectedCpeSet).List() 874 sort.Strings(extra) 875 if len(extra) > 0 { 876 t.Errorf("found extra CPEs:") 877 for _, d := range extra { 878 t.Logf(" %q,\n", d) 879 } 880 } 881 882 missing := strset.Difference(expectedCpeSet, actualCpeSet).List() 883 sort.Strings(missing) 884 if len(missing) > 0 { 885 t.Errorf("missing CPEs:") 886 for _, d := range missing { 887 t.Logf(" %q,\n", d) 888 } 889 } 890 }) 891 } 892 } 893 894 func TestCandidateProducts(t *testing.T) { 895 tests := []struct { 896 name string 897 p pkg.Package 898 expected []string 899 }{ 900 { 901 name: "apache-cassandra", 902 p: pkg.Package{ 903 Name: "apache-cassandra", 904 Type: pkg.JavaPkg, 905 }, 906 expected: []string{"cassandra" /* <-- known good names | default guess --> */, "apache-cassandra", "apache_cassandra"}, 907 }, 908 { 909 name: "springframework", 910 p: pkg.Package{ 911 Name: "springframework", 912 Type: pkg.JavaPkg, 913 }, 914 expected: []string{"spring_framework", "springsource_spring_framework" /* <-- known good names | default guess --> */, "springframework"}, 915 }, 916 { 917 name: "spring-security-core", 918 p: pkg.Package{ 919 Name: "spring-security-core", 920 Type: pkg.JavaPkg, 921 }, 922 expected: []string{"spring-security-core", "spring_security", "spring_security_core"}, 923 }, 924 { 925 name: "java", 926 p: pkg.Package{ 927 Name: "some-java-package-with-group-id", 928 Type: pkg.JavaPkg, 929 Language: pkg.Java, 930 Metadata: pkg.JavaArchive{ 931 PomProperties: &pkg.JavaPomProperties{ 932 GroupID: "com.apple.itunes", 933 }, 934 }, 935 }, 936 expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"}, 937 }, 938 { 939 name: "java-with-asterisk", 940 p: pkg.Package{ 941 Name: "some-java-package-with-group-id", 942 Type: pkg.JavaPkg, 943 Language: pkg.Java, 944 Metadata: pkg.JavaArchive{ 945 PomProperties: &pkg.JavaPomProperties{ 946 GroupID: "com.apple.itunes.*", 947 }, 948 }, 949 }, 950 expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"}, 951 }, 952 { 953 name: "jenkins-plugin", 954 p: pkg.Package{ 955 Name: "some-jenkins-plugin", 956 Type: pkg.JenkinsPluginPkg, 957 Language: pkg.Java, 958 Metadata: pkg.JavaArchive{ 959 PomProperties: &pkg.JavaPomProperties{ 960 GroupID: "com.cloudbees.jenkins.plugins", 961 }, 962 }, 963 }, 964 expected: []string{"some-jenkins-plugin", "some_jenkins_plugin", "jenkins"}, 965 }, 966 { 967 name: "javascript", 968 p: pkg.Package{ 969 Name: "handlebars.js", 970 Type: pkg.NpmPkg, 971 }, 972 expected: []string{"handlebars" /* <-- known good names | default guess --> */, "handlebars.js"}, 973 }, 974 { 975 name: "gem", 976 p: pkg.Package{ 977 Name: "RedCloth", 978 Type: pkg.GemPkg, 979 }, 980 expected: []string{"redcloth_library" /* <-- known good names | default guess --> */, "RedCloth"}, 981 }, 982 { 983 name: "python", 984 p: pkg.Package{ 985 Name: "python-rrdtool", 986 Type: pkg.PythonPkg, 987 }, 988 expected: []string{"rrdtool" /* <-- known good names | default guess --> */, "python-rrdtool", "python_rrdtool"}, 989 }, 990 } 991 992 for _, test := range tests { 993 t.Run(test.name, func(t *testing.T) { 994 assert.ElementsMatch(t, test.expected, candidateProducts(test.p)) 995 }) 996 } 997 } 998 999 func TestCandidateVendor(t *testing.T) { 1000 tests := []struct { 1001 name string 1002 p pkg.Package 1003 expected []string 1004 }{ 1005 { 1006 name: "elasticsearch", 1007 p: pkg.Package{ 1008 Name: "elasticsearch", 1009 Type: pkg.JavaPkg, 1010 }, 1011 expected: []string{"elastic" /* <-- known good names | default guess --> */, "elasticsearch"}, 1012 }, 1013 { 1014 name: "spring-security", 1015 p: pkg.Package{ 1016 Name: "spring-security-core", 1017 Type: pkg.JavaPkg, 1018 }, 1019 expected: []string{"vmware" /* <-- known good names | default guess --> */, "spring", "spring-security", "spring-security-core", "spring_security_core", "spring_security"}, 1020 }, 1021 { 1022 name: "log4j", 1023 p: pkg.Package{ 1024 Name: "log4j", 1025 Type: pkg.JavaPkg, 1026 }, 1027 expected: []string{"apache"}, 1028 }, 1029 { 1030 name: "Django", 1031 p: pkg.Package{ 1032 Name: "Django", 1033 Type: pkg.PythonPkg, 1034 }, 1035 expected: []string{"djangoproject", "python-Django", "python_Django" /* <-- known good names | default guess --> */, "python", "Django"}, 1036 }, 1037 } 1038 1039 for _, test := range tests { 1040 t.Run(test.name, func(t *testing.T) { 1041 assert.ElementsMatch(t, test.expected, candidateVendors(test.p)) 1042 }) 1043 } 1044 } 1045 1046 func Test_generateSubSelections(t *testing.T) { 1047 tests := []struct { 1048 field string 1049 expected []string 1050 }{ 1051 { 1052 field: "jenkins", 1053 expected: []string{"jenkins"}, 1054 }, 1055 { 1056 field: "jenkins-ci", 1057 expected: []string{"jenkins", "jenkins-ci"}, 1058 }, 1059 { 1060 field: "jenkins--ci", 1061 expected: []string{"jenkins", "jenkins-ci"}, 1062 }, 1063 { 1064 field: "jenkins_ci_tools", 1065 expected: []string{"jenkins", "jenkins_ci", "jenkins_ci_tools"}, 1066 }, 1067 { 1068 field: "-jenkins", 1069 expected: []string{"jenkins"}, 1070 }, 1071 { 1072 field: "jenkins_", 1073 expected: []string{"jenkins"}, 1074 }, 1075 { 1076 field: "", 1077 expected: nil, 1078 }, 1079 { 1080 field: "-", 1081 expected: nil, 1082 }, 1083 { 1084 field: "_", 1085 expected: nil, 1086 }, 1087 } 1088 for _, test := range tests { 1089 t.Run(test.field, func(t *testing.T) { 1090 assert.ElementsMatch(t, test.expected, generateSubSelections(test.field)) 1091 }) 1092 } 1093 } 1094 1095 func Test_addSeparatorVariations(t *testing.T) { 1096 tests := []struct { 1097 input []string 1098 expected []string 1099 }{ 1100 { 1101 input: []string{"jenkins-ci"}, 1102 expected: []string{"jenkins-ci", "jenkins_ci"}, //, "jenkinsci"}, 1103 }, 1104 { 1105 input: []string{"jenkins_ci"}, 1106 expected: []string{"jenkins_ci", "jenkins-ci"}, //, "jenkinsci"}, 1107 }, 1108 { 1109 input: []string{"jenkins"}, 1110 expected: []string{"jenkins"}, 1111 }, 1112 { 1113 input: []string{"jenkins-ci", "circle-ci"}, 1114 expected: []string{"jenkins-ci", "jenkins_ci", "circle-ci", "circle_ci"}, //, "jenkinsci", "circleci"}, 1115 }, 1116 } 1117 for _, test := range tests { 1118 t.Run(strings.Join(test.input, ","), func(t *testing.T) { 1119 val := newFieldCandidateSet(test.input...) 1120 addDelimiterVariations(val) 1121 assert.ElementsMatch(t, test.expected, val.values()) 1122 }) 1123 } 1124 } 1125 1126 func TestDictionaryFindIsWired(t *testing.T) { 1127 1128 tests := []struct { 1129 name string 1130 pkg pkg.Package 1131 want []cpe.CPE 1132 wantExists bool 1133 }{ 1134 { 1135 name: "sanity check that cpe data is wired up", 1136 pkg: pkg.Package{ 1137 Name: "openssl", 1138 Version: "1.0.2k", 1139 Type: pkg.GemPkg, 1140 }, 1141 want: []cpe.CPE{ 1142 cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource), 1143 cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:ruby:*:*", cpe.NVDDictionaryLookupSource), 1144 }, 1145 // without the cpe data wired up, this would be empty (generation also creates cpe:2.3:a:openssl:openssl:1.0.2k:*:*:*:*:*:*:*) 1146 wantExists: true, 1147 }, 1148 { 1149 name: "ML model packages should not have dictionary CPEs", 1150 pkg: pkg.Package{ 1151 Name: "llama3-8b", 1152 Version: "3.0", 1153 Type: pkg.ModelPkg, 1154 }, 1155 want: []cpe.CPE{}, 1156 wantExists: false, 1157 }, 1158 } 1159 for _, tt := range tests { 1160 t.Run(tt.name, func(t *testing.T) { 1161 got, gotExists := FromDictionaryFind(tt.pkg) 1162 assert.ElementsMatch(t, tt.want, got) 1163 assert.Equal(t, tt.wantExists, gotExists) 1164 }) 1165 } 1166 } 1167 1168 // TestAddBinaryPackageDigitVariations tests the heuristic for binary package types 1169 // where names ending with digits get variations with all suffix-digits removed (e.g. Qt5 -> Qt). 1170 // This improves vulnerability matching for binary packages like Qt6, libfoo123, etc. 1171 func TestAddBinaryPackageDigitVariations(t *testing.T) { 1172 tests := []struct { 1173 name string 1174 packageType pkg.Type 1175 inputCandidates []string 1176 expectedPresent []string // These should be present in the result 1177 expectedAbsent []string // These should NOT be present in the result 1178 }{ 1179 { 1180 name: "Qt5 binary package example", 1181 packageType: pkg.BinaryPkg, 1182 inputCandidates: []string{"Qt5"}, 1183 expectedPresent: []string{"Qt5", "Qt"}, 1184 expectedAbsent: []string{}, 1185 }, 1186 { 1187 name: "package with trailing digits", 1188 packageType: pkg.BinaryPkg, 1189 inputCandidates: []string{"Qt5", "libfoo123", "bar42", "baz"}, 1190 expectedPresent: []string{"Qt5", "Qt", "libfoo123", "libfoo", "bar42", "bar", "baz"}, 1191 expectedAbsent: []string{}, 1192 }, 1193 { 1194 name: "multiple trailing digits", 1195 inputCandidates: []string{"Qt872", "package999"}, 1196 expectedPresent: []string{"Qt872", "Qt", "package999", "package"}, 1197 expectedAbsent: []string{}, 1198 }, 1199 { 1200 name: "package without trailing digits", 1201 inputCandidates: []string{"QtCore", "libfoo", "bar"}, 1202 expectedPresent: []string{"QtCore", "libfoo", "bar"}, 1203 expectedAbsent: []string{"QtCor", "libfo", "ba"}, 1204 }, 1205 { 1206 name: "empty candidate set", 1207 packageType: pkg.BinaryPkg, 1208 inputCandidates: []string{}, 1209 expectedPresent: []string{}, 1210 expectedAbsent: []string{}, 1211 }, 1212 } 1213 1214 for _, test := range tests { 1215 t.Run(test.name, func(t *testing.T) { 1216 fields := newFieldCandidateSet(test.inputCandidates...) 1217 addBinaryPackageDigitVariations(fields) 1218 1219 values := fields.uniqueValues() 1220 1221 for _, expected := range test.expectedPresent { 1222 assert.Contains(t, values, expected, "expected %q to be present", expected) 1223 } 1224 1225 for _, notExpected := range test.expectedAbsent { 1226 assert.NotContains(t, values, notExpected, "expected %q to be absent", notExpected) 1227 } 1228 }) 1229 } 1230 }