Repository: anchore/grype
Pull request #2183 (2024-10-14 15:16:55)

chore(deps): bump github/codeql-action from 3.26.12 to 3.26.13
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.12 to 3.26.13.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action's changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a href="https://github.com/github/codeql-action/releases">releases page</a> for the relevant changes to the CodeQL CLI and language packs.</p>
<p>Note that the only difference between <code>v2</code> and <code>v3</code> of the CodeQL Action is the node version they support, with <code>v3</code> running on node 20 while we continue to release <code>v2</code> to support running on node 16. For example <code>3.22.11</code> was the first <code>v3</code> release and is functionally identical to <code>2.22.11</code>. This approach ensures an easy way to track exactly which features are included in different versions, indicated by the minor and patch version numbers.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>3.26.13 - 14 Oct 2024</h2>
<p>No user facing changes.</p>
<h2>3.26.12 - 07 Oct 2024</h2>
<ul>
<li>
<p><em>Upcoming breaking change</em>: Add a deprecation warning for customers using CodeQL version 2.14.5 and earlier. These versions of CodeQL were discontinued on 24 September 2024 alongside GitHub Enterprise Server 3.10, and will be unsupported by CodeQL Action versions 3.27.0 and later and versions 2.27.0 and later. <a href="https://redirect.github.com/github/codeql-action/pull/2520">#2520</a></p>
<ul>
<li>
<p>If you are using one of these versions, please update to CodeQL CLI version 2.14.6 or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version.</p>
</li>
<li>
<p>Alternatively, if you want to continue using a version of the CodeQL CLI between 2.13.5 and 2.14.5, you can replace <code>github/codeql-action/*@v3</code> by <code>github/codeql-action/*@v3.26.11</code> and <code>github/codeql-action/*@v2</code> by <code>github/codeql-action/*@v2.26.11</code> in your code scanning workflow to ensure you continue using this version of the CodeQL Action.</p>
</li>
</ul>
</li>
</ul>
<h2>3.26.11 - 03 Oct 2024</h2>
<ul>
<li>
<p><em>Upcoming breaking change</em>: Add support for using <code>actions/download-artifact@v4</code> to programmatically consume CodeQL Action debug artifacts.</p>
<p>Starting November 30, 2024, GitHub.com customers will <a href="https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/">no longer be able to use <code>actions/download-artifact@v3</code></a>. Therefore, to avoid breakage, customers who programmatically download the CodeQL Action debug artifacts should set the <code>CODEQL_ACTION_ARTIFACT_V4_UPGRADE</code> environment variable to <code>true</code> and bump <code>actions/download-artifact@v3</code> to <code>actions/download-artifact@v4</code> in their workflows. The CodeQL Action will enable this behavior by default in early November and workflows that have not yet bumped to <code>actions/download-artifact@v3</code> to <code>actions/download-artifact@v4</code> will begin failing then.</p>
<p>This change is currently unavailable for GitHub Enterprise Server customers, as <code>actions/upload-artifact@v4</code> and <code>actions/download-artifact@v4</code> are not yet compatible with GHES.</p>
</li>
<li>
<p>Update default CodeQL bundle version to 2.19.1. <a href="https://redirect.github.com/github/codeql-action/pull/2519">#2519</a></p>
</li>
</ul>
<h2>3.26.10 - 30 Sep 2024</h2>
<ul>
<li>We are rolling out a feature in September/October 2024 that sets up CodeQL using a bundle compressed with <a href="http://facebook.github.io/zstd/">Zstandard</a>. Our aim is to improve the performance of setting up CodeQL. <a href="https://redirect.github.com/github/codeql-action/pull/2502">#2502</a></li>
</ul>
<h2>3.26.9 - 24 Sep 2024</h2>
<p>No user facing changes.</p>
<h2>3.26.8 - 19 Sep 2024</h2>
<ul>
<li>Update default CodeQL bundle version to 2.19.0. <a href="https://redirect.github.com/github/codeql-action/pull/2483">#2483</a></li>
</ul>
<h2>3.26.7 - 13 Sep 2024</h2>
<ul>
<li>Update default CodeQL bundle version to 2.18.4. <a href="https://redirect.github.com/github/codeql-action/pull/2471">#2471</a></li>
</ul>
<h2>3.26.6 - 29 Aug 2024</h2>
<ul>
<li>Update default CodeQL bundle version to 2.18.3. <a href="https://redirect.github.com/github/codeql-action/pull/2449">#2449</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/github/codeql-action/commit/f779452ac5af1c261dce0346a8f964149f49322b"><code>f779452</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2539">#2539</a> from github/update-v3.26.13-0c3e00641</li>
<li><a href="https://github.com/github/codeql-action/commit/532932479b339a320b8f0f106cf17cb6f27625ab"><code>5329324</code></a> Update CHANGELOG.md</li>
<li><a href="https://github.com/github/codeql-action/commit/007ba256485136f6179e827b998b3c9125fcbcee"><code>007ba25</code></a> Update changelog for v3.26.13</li>
<li><a href="https://github.com/github/codeql-action/commit/0c3e0064168c8e68650d4d5d2eaa7f5b14c2e089"><code>0c3e006</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2536">#2536</a> from yoff/python/ff-std-lib-extraction</li>
<li><a href="https://github.com/github/codeql-action/commit/38469af22844c7bb1302fd0bde937f95fae05417"><code>38469af</code></a> Merge pull request <a href="https://redirect.github.com/github/codeql-action/issues/2537">#2537</a> from github/henrymercer/no-zstd-windows</li>
<li><a href="https://github.com/github/codeql-action/commit/5b6984ee4d8d4337aa84b946951459213dd36daf"><code>5b6984e</code></a> Assert that Windows downloads gzip</li>
<li><a href="https://github.com/github/codeql-action/commit/eefb943f7e5f85e6e26fff781c173475ecfe9f4f"><code>eefb943</code></a> Don't use Zstandard bundles on Windows</li>
<li><a href="https://github.com/github/codeql-action/commit/201e02efe22c245c1d4fc9b579583c1ef037dfe6"><code>201e02e</code></a> rebuild the action</li>
<li><a href="https://github.com/github/codeql-action/commit/ce5f900bfc4f4f7d32fd4106942380d167a1e43c"><code>ce5f900</code></a> formatting</li>
<li><a href="https://github.com/github/codeql-action/commit/65dd816de1c1c6fc94d0885d009da0db1545df07"><code>65dd816</code></a> remove unused import</li>
<li>Additional commits viewable in <a href="https://github.com/github/codeql-action/compare/c36620d31ac7c881962c3d9dd939c40ec9434f2b...f779452ac5af1c261dce0346a8f964149f49322b">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action&package-manager=github_actions&previous-version=3.26.12&new-version=3.26.13)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a4175ebb..ca5c40fc 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -63,7 +63,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
+      uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -85,4 +85,4 @@ jobs:
       run: make grype
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
+      uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 22e7f27f..56ae61ed 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -158,7 +158,7 @@ jobs:
           # for updating brew formula in anchore/homebrew-syft
           GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
 
-      - uses: anchore/sbom-action@61119d458adab75f756bc0b9e4bde25725f86a7a # v0.17.2
+      - uses: anchore/sbom-action@f5e124a5e5e1d497a692818ae907d3c45829d033 # v0.17.3
         continue-on-error: true
         with:
           artifact-name: sbom.spdx.json
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 7c68b343..4f905525 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -38,6 +38,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v1.0.26
+        uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v1.0.26
         with:
           sarif_file: results.sarif
diff --git a/cmd/grype/cli/commands/root.go b/cmd/grype/cli/commands/root.go
index 90d2ecd9..e70a8c90 100644
--- a/cmd/grype/cli/commands/root.go
+++ b/cmd/grype/cli/commands/root.go
@@ -36,6 +36,7 @@ import (
 	"github.com/anchore/grype/internal/log"
 	"github.com/anchore/grype/internal/stringutil"
 	"github.com/anchore/syft/syft"
+	"github.com/anchore/syft/syft/cataloging"
 	"github.com/anchore/syft/syft/linux"
 	syftPkg "github.com/anchore/syft/syft/pkg"
 	"github.com/anchore/syft/syft/sbom"
@@ -306,6 +307,11 @@ func getProviderConfig(opts *options.Grype) pkg.ProviderConfig {
 	cfg.Packages.JavaArchive.IncludeIndexedArchives = opts.Search.IncludeIndexedArchives
 	cfg.Packages.JavaArchive.IncludeUnindexedArchives = opts.Search.IncludeUnindexedArchives
 
+	// when we run into a package with missing information like version, then this is not useful in the context
+	// of vulnerability matching. Though there will be downstream processing to handle this case, we can still
+	// save us the effort of ever attempting to match with these packages as early as possible.
+	cfg.Compliance.MissingVersion = cataloging.ComplianceActionDrop
+
 	return pkg.ProviderConfig{
 		SyftProviderConfig: pkg.SyftProviderConfig{
 			RegistryOptions:        opts.Registry.ToOptions(),
diff --git a/go.mod b/go.mod
index 2ff40de6..f9d3c1b1 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,7 @@ require (
 	github.com/docker/docker v27.3.1+incompatible
 	github.com/dustin/go-humanize v1.0.1
 	github.com/facebookincubator/nvdtools v0.1.5
-	github.com/gabriel-vasile/mimetype v1.4.5
+	github.com/gabriel-vasile/mimetype v1.4.6
 	github.com/gkampitakis/go-snaps v0.5.7
 	github.com/glebarez/sqlite v1.11.0
 	github.com/go-test/deep v1.1.1
diff --git a/go.sum b/go.sum
index 5b172224..07f289a8 100644
--- a/go.sum
+++ b/go.sum
@@ -435,8 +435,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
-github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
+github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
+github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/github/go-spdx/v2 v2.3.2 h1:IfdyNHTqzs4zAJjXdVQfRnxt1XMfycXoHBE2Vsm1bjs=
 github.com/github/go-spdx/v2 v2.3.2/go.mod h1:2ZxKsOhvBp+OYBDlsGnUMcchLeo2mrpEBn2L1C+U3IQ=
diff --git a/grype/matcher/rpm/matcher.go b/grype/matcher/rpm/matcher.go
index 9840f0c1..3003f582 100644
--- a/grype/matcher/rpm/matcher.go
+++ b/grype/matcher/rpm/matcher.go
@@ -126,7 +126,7 @@ func (m *Matcher) matchPackage(store vulnerability.ProviderByDistro, d *distro.D
 	// we want to ensure that the version ALWAYS has an epoch specified...
 	originalPkg := p
 
-	p.Version = addZeroEpicIfApplicable(p.Version)
+	addEpochIfApplicable(&p)
 
 	matches, err := search.ByPackageDistro(store, d, p, m.Type())
 	if err != nil {
@@ -141,9 +141,18 @@ func (m *Matcher) matchPackage(store vulnerability.ProviderByDistro, d *distro.D
 	return matches, nil
 }
 
-func addZeroEpicIfApplicable(version string) string {
-	if strings.Contains(version, ":") {
-		return version
+func addEpochIfApplicable(p *pkg.Package) {
+	meta, ok := p.Metadata.(pkg.RpmMetadata)
+	version := p.Version
+	switch {
+	case strings.Contains(version, ":"):
+		// we already have an epoch embedded in the version string
+		return
+	case ok && meta.Epoch != nil:
+		// we have an explicit epoch in the metadata
+		p.Version = fmt.Sprintf("%d:%s", *meta.Epoch, version)
+	default:
+		// no epoch was found, so we will add one
+		p.Version = "0:" + version
 	}
-	return "0:" + version
 }
diff --git a/grype/search/cpe.go b/grype/search/cpe.go
index d876b0e4..b709e7b7 100644
--- a/grype/search/cpe.go
+++ b/grype/search/cpe.go
@@ -14,6 +14,7 @@ import (
 	"github.com/anchore/grype/grype/pkg"
 	"github.com/anchore/grype/grype/version"
 	"github.com/anchore/grype/grype/vulnerability"
+	"github.com/anchore/grype/internal/log"
 	"github.com/anchore/syft/syft/cpe"
 	syftPkg "github.com/anchore/syft/syft/pkg"
 )
@@ -102,10 +103,19 @@ func ByPackageCPE(store vulnerability.ProviderByCPE, d *distro.Distro, p pkg.Pac
 			searchVersion = alpineCPEComparableVersion(searchVersion)
 		}
 
-		if searchVersion == wfn.NA || searchVersion == wfn.Any {
+		if searchVersion == wfn.NA || searchVersion == wfn.Any || isUnknownVersion(searchVersion) {
 			searchVersion = p.Version
 		}
 
+		if isUnknownVersion(searchVersion) {
+			log.WithFields("package", p.Name).Trace("skipping package with unknown version")
+			continue
+		}
+
+		// we should always show the exact CPE we searched by, not just what's in the component analysis (since we
+		// may alter the version based on above processing)
+		c.Attributes.Version = searchVersion
+
 		format := version.FormatFromPkg(p)
 
 		if format == version.JVMFormat {
diff --git a/grype/search/distro.go b/grype/search/distro.go
index 2a5cd9d3..ed59a919 100644
--- a/grype/search/distro.go
+++ b/grype/search/distro.go
@@ -3,6 +3,7 @@ package search
 import (
 	"errors"
 	"fmt"
+	"strings"
 
 	"github.com/anchore/grype/grype/distro"
 	"github.com/anchore/grype/grype/match"
@@ -17,6 +18,11 @@ func ByPackageDistro(store vulnerability.ProviderByDistro, d *distro.Distro, p p
 		return nil, nil
 	}
 
+	if isUnknownVersion(p.Version) {
+		log.WithFields("package", p.Name).Trace("skipping package with unknown version")
+		return nil, nil
+	}
+
 	verObj, err := version.NewVersionFromPkg(p)
 	if err != nil {
 		if errors.Is(err, version.ErrUnsupportedVersion) {
@@ -77,3 +83,7 @@ func ByPackageDistro(store vulnerability.ProviderByDistro, d *distro.Distro, p p
 
 	return matches, err
 }
+
+func isUnknownVersion(v string) bool {
+	return v == "" || strings.ToLower(v) == "unknown"
+}
diff --git a/grype/search/language.go b/grype/search/language.go
index b0a80646..e4034b52 100644
--- a/grype/search/language.go
+++ b/grype/search/language.go
@@ -13,6 +13,12 @@ import (
 )
 
 func ByPackageLanguage(store vulnerability.ProviderByLanguage, d *distro.Distro, p pkg.Package, upstreamMatcher match.MatcherType) ([]match.Match, error) {
+	if isUnknownVersion(p.Version) {
+		log.WithFields("package", p.Name).Trace("skipping package with unknown version")
+
+		return nil, nil
+	}
+
 	verObj, err := version.NewVersionFromPkg(p)
 	if err != nil {
 		if errors.Is(err, version.ErrUnsupportedVersion) {
diff --git a/grype/version/rpm_version.go b/grype/version/rpm_version.go
index 430b5e03..af77467f 100644
--- a/grype/version/rpm_version.go
+++ b/grype/version/rpm_version.go
@@ -2,7 +2,6 @@ package version
 
 import (
 	"fmt"
-	"math"
 	"reflect"
 	"regexp"
 	"strconv"
@@ -151,10 +150,11 @@ func compareRpmVersions(a, b string) int {
 	// get alpha/numeric segments
 	segsa := alphanumPattern.FindAllString(a, -1)
 	segsb := alphanumPattern.FindAllString(b, -1)
-	segs := int(math.Min(float64(len(segsa)), float64(len(segsb))))
+	maxSegs := max(len(segsa), len(segsb))
+	minSegs := min(len(segsa), len(segsb))
 
 	// compare each segment
-	for i := 0; i < segs; i++ {
+	for i := 0; i < minSegs; i++ {
 		a := segsa[i]
 		b := segsb[i]
 
@@ -204,11 +204,26 @@ func compareRpmVersions(a, b string) int {
 	}
 
 	// If there is a tilde in a segment past the min number of segments, find it.
-	if len(segsa) > segs && []rune(segsa[segs])[0] == '~' {
+	if len(segsa) > minSegs && []rune(segsa[minSegs])[0] == '~' {
 		return -1
-	} else if len(segsb) > segs && []rune(segsb[segs])[0] == '~' {
+	} else if len(segsb) > minSegs && []rune(segsb[minSegs])[0] == '~' {
 		return 1
 	}
+	// are the remaining segments 0s?
+	segaAll0s := true
+	segbAll0s := true
+	for i := minSegs; i < maxSegs; i++ {
+		if i < len(segsa) && segsa[i] != "0" {
+			segaAll0s = false
+		}
+		if i < len(segsb) && segsb[i] != "0" {
+			segbAll0s = false
+		}
+	}
+
+	if segaAll0s && segbAll0s {
+		return 0
+	}
 
 	// whoever has the most segments wins
 	if len(segsa) > len(segsb) {


Test patch:
diff --git a/cmd/grype/cli/commands/root_test.go b/cmd/grype/cli/commands/root_test.go
index 82ed09b7..bd1d27b8 100644
--- a/cmd/grype/cli/commands/root_test.go
+++ b/cmd/grype/cli/commands/root_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/anchore/grype/grype/pkg"
 	"github.com/anchore/stereoscope/pkg/image"
 	"github.com/anchore/syft/syft"
+	"github.com/anchore/syft/syft/cataloging"
 	"github.com/anchore/syft/syft/pkg/cataloger/binary"
 )
 
@@ -54,14 +55,18 @@ func Test_getProviderConfig(t *testing.T) {
 		want pkg.ProviderConfig
 	}{
 		{
-			name: "default-options-are-set",
+			name: "syft default api options are used",
 			opts: options.DefaultGrype(clio.Identification{
 				Name:    "test",
 				Version: "1.0",
 			}),
 			want: pkg.ProviderConfig{
 				SyftProviderConfig: pkg.SyftProviderConfig{
-					SBOMOptions: syft.DefaultCreateSBOMConfig(),
+					SBOMOptions: func() *syft.CreateSBOMConfig {
+						cfg := syft.DefaultCreateSBOMConfig()
+						cfg.Compliance.MissingVersion = cataloging.ComplianceActionDrop
+						return cfg
+					}(),
 					RegistryOptions: &image.RegistryOptions{
 						Credentials: []image.RegistryCredentials{},
 					},
diff --git a/grype/matcher/apk/matcher_test.go b/grype/matcher/apk/matcher_test.go
index 4b5ead53..475bd217 100644
--- a/grype/matcher/apk/matcher_test.go
+++ b/grype/matcher/apk/matcher_test.go
@@ -1003,7 +1003,7 @@ func TestNVDMatchBySourceIndirection(t *testing.T) {
 					Type:       match.CPEMatch,
 					Confidence: 0.9,
 					SearchedBy: search.CPEParameters{
-						CPEs:      []string{"cpe:2.3:a:musl:musl:*:*:*:*:*:*:*:*"},
+						CPEs:      []string{"cpe:2.3:a:musl:musl:1.3.2-r0:*:*:*:*:*:*:*"},
 						Namespace: "nvd:cpe",
 						Package: search.CPEPackageParameter{
 							Name:    "musl",
diff --git a/grype/matcher/rpm/matcher_test.go b/grype/matcher/rpm/matcher_test.go
index f15a5daf..ea101c42 100644
--- a/grype/matcher/rpm/matcher_test.go
+++ b/grype/matcher/rpm/matcher_test.go
@@ -360,24 +360,52 @@ func TestMatcherRpm(t *testing.T) {
 	}
 }
 
-func Test_addZeroEpicIfApplicable(t *testing.T) {
+func Test_addEpochIfApplicable(t *testing.T) {
 	tests := []struct {
-		version  string
+		name     string
+		pkg      pkg.Package
 		expected string
 	}{
 		{
-			version:  "3.26.0-6.el8",
+			name: "assume 0 epoch",
+			pkg: pkg.Package{
+				Version: "3.26.0-6.el8",
+			},
 			expected: "0:3.26.0-6.el8",
 		},
 		{
-			version:  "7:3.26.0-6.el8",
+			name: "epoch already exists in version string",
+			pkg: pkg.Package{
+				Version: "7:3.26.0-6.el8",
+			},
+			expected: "7:3.26.0-6.el8",
+		},
+		{
+			name: "epoch only exists in metadata",
+			pkg: pkg.Package{
+				Version: "3.26.0-6.el8",
+				Metadata: pkg.RpmMetadata{
+					Epoch: intRef(7),
+				},
+			},
 			expected: "7:3.26.0-6.el8",
 		},
+		{
+			name: "epoch does not exist in metadata",
+			pkg: pkg.Package{
+				Version: "3.26.0-6.el8",
+				Metadata: pkg.RpmMetadata{
+					Epoch: nil,
+				},
+			},
+			expected: "0:3.26.0-6.el8",
+		},
 	}
 	for _, test := range tests {
-		t.Run(test.version, func(t *testing.T) {
-			actualVersion := addZeroEpicIfApplicable(test.version)
-			assert.Equal(t, test.expected, actualVersion)
+		t.Run(test.name, func(t *testing.T) {
+			p := test.pkg
+			addEpochIfApplicable(&p)
+			assert.Equal(t, test.expected, p.Version)
 		})
 	}
 }
diff --git a/grype/search/cpe_test.go b/grype/search/cpe_test.go
index 65d5bea2..676b5bad 100644
--- a/grype/search/cpe_test.go
+++ b/grype/search/cpe_test.go
@@ -215,6 +215,71 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "fallback to package version",
+			p: pkg.Package{
+				CPEs: []cpe.CPE{
+					cpe.Must("cpe:2.3:*:activerecord:activerecord:unknown:rando1:*:ra:*:ruby:*:*", ""),
+					cpe.Must("cpe:2.3:*:activerecord:activerecord:unknown:rando4:*:re:*:rails:*:*", ""),
+				},
+				Name:     "activerecord",
+				Version:  "3.7.5",
+				Language: syftPkg.Ruby,
+				Type:     syftPkg.GemPkg,
+			},
+			expected: []match.Match{
+				{
+
+					Vulnerability: vulnerability.Vulnerability{
+						ID: "CVE-2017-fake-1",
+					},
+					Package: pkg.Package{
+						CPEs: []cpe.CPE{
+							cpe.Must("cpe:2.3:*:activerecord:activerecord:unknown:rando1:*:ra:*:ruby:*:*", ""),
+							cpe.Must("cpe:2.3:*:activerecord:activerecord:unknown:rando4:*:re:*:rails:*:*", ""),
+						},
+						Name:     "activerecord",
+						Version:  "3.7.5",
+						Language: syftPkg.Ruby,
+						Type:     syftPkg.GemPkg,
+					},
+					Details: []match.Detail{
+						{
+							Type:       match.CPEMatch,
+							Confidence: 0.9,
+							SearchedBy: CPEParameters{
+								Namespace: "nvd:cpe",
+								CPEs:      []string{"cpe:2.3:*:activerecord:activerecord:3.7.5:rando4:*:re:*:rails:*:*"},
+								Package: CPEPackageParameter{
+									Name:    "activerecord",
+									Version: "3.7.5",
+								},
+							},
+							Found: CPEResult{
+								CPEs:              []string{"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*"},
+								VersionConstraint: "< 3.7.6 (semver)",
+								VulnerabilityID:   "CVE-2017-fake-1",
+							},
+							Matcher: matcher,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "suppress matching when missing version",
+			p: pkg.Package{
+				CPEs: []cpe.CPE{
+					cpe.Must("cpe:2.3:*:activerecord:activerecord:unknown:rando1:*:ra:*:ruby:*:*", ""),
+					cpe.Must("cpe:2.3:*:activerecord:activerecord:unknown:rando4:*:re:*:rails:*:*", ""),
+				},
+				Name:     "activerecord",
+				Version:  "",
+				Language: syftPkg.Ruby,
+				Type:     syftPkg.GemPkg,
+			},
+			expected: []match.Match{},
+		},
 		{
 			name: "multiple matches",
 			p: pkg.Package{
@@ -511,7 +576,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
 							Type:       match.CPEMatch,
 							Confidence: 0.9,
 							SearchedBy: CPEParameters{
-								CPEs:      []string{"cpe:2.3:*:sw:sw:*:*:*:*:*:*:*:*"},
+								CPEs:      []string{"cpe:2.3:*:sw:sw:0.1:*:*:*:*:*:*:*"},
 								Namespace: "nvd:cpe",
 								Package: CPEPackageParameter{
 									Name:    "sw",
@@ -567,7 +632,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
 							Type:       match.CPEMatch,
 							Confidence: 0.9,
 							SearchedBy: CPEParameters{
-								CPEs:      []string{"cpe:2.3:*:funfun:funfun:*:*:*:*:*:python:*:*"},
+								CPEs:      []string{"cpe:2.3:*:funfun:funfun:5.2.1:*:*:*:*:python:*:*"},
 								Namespace: "nvd:cpe",
 								Package: CPEPackageParameter{
 									Name:    "funfun",
@@ -618,7 +683,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
 							Type:       match.CPEMatch,
 							Confidence: 0.9,
 							SearchedBy: CPEParameters{
-								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:*:*:*:*:*:*:*:*"},
+								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:0.1:*:*:*:*:*:*:*"},
 								Namespace: "nvd:cpe",
 								Package: CPEPackageParameter{
 									Name:    "handlebars",
@@ -668,7 +733,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
 							Type:       match.CPEMatch,
 							Confidence: 0.9,
 							SearchedBy: CPEParameters{
-								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:*:*:*:*:*:*:*:*"},
+								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:0.1:*:*:*:*:*:*:*"},
 								Namespace: "nvd:cpe",
 								Package: CPEPackageParameter{
 									Name:    "handlebars",
@@ -718,7 +783,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
 							Type:       match.CPEMatch,
 							Confidence: 0.9,
 							SearchedBy: CPEParameters{
-								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:*:*:*:*:*:*:*:*"},
+								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:0.1:*:*:*:*:*:*:*"},
 								Namespace: "nvd:cpe",
 								Package: CPEPackageParameter{
 									Name:    "handlebars",
@@ -781,7 +846,7 @@ func TestFindMatchesByPackageCPE(t *testing.T) {
 							Type:       match.CPEMatch,
 							Confidence: 0.9,
 							SearchedBy: CPEParameters{
-								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:*:*:*:*:*:*:*:*"},
+								CPEs:      []string{"cpe:2.3:a:handlebarsjs:handlebars:0.1:*:*:*:*:*:*:*"},
 								Namespace: "nvd:cpe",
 								Package: CPEPackageParameter{
 									Name:    "handlebars",
diff --git a/grype/search/distro_test.go b/grype/search/distro_test.go
index fecc7f78..b90f0e61 100644
--- a/grype/search/distro_test.go
+++ b/grype/search/distro_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/anchore/grype/grype/distro"
 	"github.com/anchore/grype/grype/match"
@@ -106,8 +107,15 @@ func TestFindMatchesByPackageDistro(t *testing.T) {
 
 	store := newMockProviderByDistro()
 	actual, err := ByPackageDistro(store, d, p, match.PythonMatcher)
-	assert.NoError(t, err)
+	require.NoError(t, err)
 	assertMatchesUsingIDsForVulnerabilities(t, expected, actual)
+
+	// prove we do not search for unknown versions
+	p.Version = "unknown"
+	actual, err = ByPackageDistro(store, d, p, match.PythonMatcher)
+	require.NoError(t, err)
+	assert.Empty(t, actual)
+
 }
 
 func TestFindMatchesByPackageDistroSles(t *testing.T) {
diff --git a/grype/search/language_test.go b/grype/search/language_test.go
index 9c610a73..5dfae0f8 100644
--- a/grype/search/language_test.go
+++ b/grype/search/language_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/anchore/grype/grype/match"
 	"github.com/anchore/grype/grype/pkg"
@@ -94,8 +95,9 @@ func expectedMatch(p pkg.Package, constraint string) []match.Match {
 
 func TestFindMatchesByPackageLanguage(t *testing.T) {
 	cases := []struct {
-		p          pkg.Package
-		constraint string
+		p           pkg.Package
+		constraint  string
+		assertEmpty bool
 	}{
 		{
 			constraint: "< 3.7.6 (semver)",
@@ -117,13 +119,27 @@ func TestFindMatchesByPackageLanguage(t *testing.T) {
 				Type:     syftPkg.GemPkg,
 			},
 		},
+		{
+			p: pkg.Package{
+				ID:       pkg.ID(uuid.NewString()),
+				Name:     "nokogiri",
+				Version:  "unknown",
+				Language: syftPkg.Ruby,
+				Type:     syftPkg.GemPkg,
+			},
+			assertEmpty: true,
+		},
 	}
 
 	store := newMockProviderByLanguage()
 	for _, c := range cases {
 		t.Run(c.p.Name, func(t *testing.T) {
 			actual, err := ByPackageLanguage(store, nil, c.p, match.RubyGemMatcher)
-			assert.NoError(t, err)
+			require.NoError(t, err)
+			if c.assertEmpty {
+				assert.Empty(t, actual)
+				return
+			}
 			assertMatchesUsingIDsForVulnerabilities(t, expectedMatch(c.p, c.constraint), actual)
 		})
 	}
diff --git a/grype/version/rpm_constraint_test.go b/grype/version/rpm_constraint_test.go
index 890fac21..77139361 100644
--- a/grype/version/rpm_constraint_test.go
+++ b/grype/version/rpm_constraint_test.go
@@ -60,6 +60,13 @@ func TestVersionRpmConstraint(t *testing.T) {
 		{version: "4.19.0-1.el7_5", constraint: "<= 12:4.19.0-1.el7", satisfied: false},
 		{version: "3:4.19.0-1.el7_5", constraint: "< 4.21.0-1.el7", satisfied: true},
 		{version: "4:1.2.3-3-el7_5", constraint: "< 1.2.3-el7_5~snapshot1", satisfied: false},
+		// regression https://github.com/anchore/grype/issues/398
+		{version: "8.3.1-5.el8.4", constraint: "< 0:8.3.1-5.el8.5", satisfied: true},
+		{version: "8.3.1-5.el8.40", constraint: "< 0:8.3.1-5.el8.5", satisfied: false},
+		{version: "8.3.1-5.el8", constraint: "< 0:8.3.1-5.el8.0.0", satisfied: false},
+		{version: "8.3.1-5.el8", constraint: "<= 0:8.3.1-5.el8.0.0", satisfied: true},
+		{version: "8.3.1-5.el8.0.0", constraint: "> 0:8.3.1-5.el8", satisfied: false},
+		{version: "8.3.1-5.el8.0.0", constraint: ">= 0:8.3.1-5.el8", satisfied: true},
 	}
 
 	for _, test := range tests {
diff --git a/grype/vulnerability_matcher_test.go b/grype/vulnerability_matcher_test.go
index 71be9e18..69dd4133 100644
--- a/grype/vulnerability_matcher_test.go
+++ b/grype/vulnerability_matcher_test.go
@@ -592,7 +592,7 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
 							SearchedBy: search.CPEParameters{
 								Namespace: "nvd:cpe",
 								CPEs: []string{
-									"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
+									"cpe:2.3:*:activerecord:activerecord:3.7.5:*:*:*:*:rails:*:*",
 								},
 								Package: search.CPEPackageParameter{
 									Name:    "activerecord",
@@ -689,26 +689,12 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
 					},
 					Package: activerecordPkg,
 					Details: match.Details{
-						{
-							Type: match.ExactDirectMatch,
-							SearchedBy: map[string]any{
-								"language":  "ruby",
-								"namespace": "github:language:ruby",
-								"package":   map[string]string{"name": "activerecord", "version": "3.7.5"},
-							},
-							Found: map[string]any{
-								"versionConstraint": "< 3.7.6 (unknown)",
-								"vulnerabilityID":   "GHSA-2014-fake-3",
-							},
-							Matcher:    "ruby-gem-matcher",
-							Confidence: 1,
-						},
 						{
 							Type: match.CPEMatch,
 							SearchedBy: search.CPEParameters{
 								Namespace: "nvd:cpe",
 								CPEs: []string{
-									"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
+									"cpe:2.3:*:activerecord:activerecord:3.7.5:*:*:*:*:rails:*:*",
 								},
 								Package: search.CPEPackageParameter{
 									Name:    "activerecord",
@@ -725,6 +711,20 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
 							Matcher:    "ruby-gem-matcher",
 							Confidence: 0.9,
 						},
+						{
+							Type: match.ExactDirectMatch,
+							SearchedBy: map[string]any{
+								"language":  "ruby",
+								"namespace": "github:language:ruby",
+								"package":   map[string]string{"name": "activerecord", "version": "3.7.5"},
+							},
+							Found: map[string]any{
+								"versionConstraint": "< 3.7.6 (unknown)",
+								"vulnerabilityID":   "GHSA-2014-fake-3",
+							},
+							Matcher:    "ruby-gem-matcher",
+							Confidence: 1,
+						},
 					},
 				},
 			),
@@ -775,7 +775,7 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
 							SearchedBy: search.CPEParameters{
 								Namespace: "nvd:cpe",
 								CPEs: []string{
-									"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
+									"cpe:2.3:*:activerecord:activerecord:3.7.5:*:*:*:*:rails:*:*",
 								},
 								Package: search.CPEPackageParameter{
 									Name:    "activerecord",
@@ -887,7 +887,7 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
 								SearchedBy: search.CPEParameters{
 									Namespace: "nvd:cpe",
 									CPEs: []string{
-										"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
+										"cpe:2.3:*:activerecord:activerecord:3.7.5:*:*:*:*:rails:*:*",
 									},
 									Package: search.CPEPackageParameter{
 										Name:    "activerecord",
@@ -1038,7 +1038,7 @@ func TestVulnerabilityMatcher_FindMatches(t *testing.T) {
 								SearchedBy: search.CPEParameters{
 									Namespace: "nvd:cpe",
 									CPEs: []string{
-										"cpe:2.3:*:activerecord:activerecord:*:*:*:*:*:rails:*:*",
+										"cpe:2.3:*:activerecord:activerecord:3.7.5:*:*:*:*:rails:*:*",
 									},
 									Package: search.CPEPackageParameter{
 										Name:    "activerecord",
diff --git a/test/quality/.yardstick.yaml b/test/quality/.yardstick.yaml
index 2a827f89..41291ddf 100644
--- a/test/quality/.yardstick.yaml
+++ b/test/quality/.yardstick.yaml
@@ -103,7 +103,7 @@ result-sets:
 
         - name: syft
           # note: we want to use a fixed version of syft for capturing all results (NOT "latest")
-          version: v0.98.0
+          version: v1.14.0
           produces: SBOM
           refresh: false
 
@@ -144,7 +144,7 @@ result-sets:
       tools:
         - name: syft
           # note: we want to use a fixed version of syft for capturing all results (NOT "latest")
-          version: v0.98.0
+          version: v1.14.0
           produces: SBOM
           refresh: false