Repository: anchore/grype
Pull request #2552 (2025-03-20 16:02:59)

chore(deps): bump actions/cache from 4.2.2 to 4.2.3
Bumps [actions/cache](https://github.com/actions/cache) from 4.2.2 to 4.2.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/cache/releases">actions/cache's releases</a>.</em></p>
<blockquote>
<h2>v4.2.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to use <code>@​actions/cache</code> 4.0.3 package &amp; prepare for new release by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a href="https://redirect.github.com/actions/cache/pull/1577">actions/cache#1577</a> (SAS tokens for cache entries are now masked in debug logs)</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> made their first contribution in <a href="https://redirect.github.com/actions/cache/pull/1577">actions/cache#1577</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/actions/cache/compare/v4.2.2...v4.2.3">https://github.com/actions/cache/compare/v4.2.2...v4.2.3</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/actions/cache/blob/main/RELEASES.md">actions/cache's changelog</a>.</em></p>
<blockquote>
<h1>Releases</h1>
<h3>4.2.3</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v4.0.3 (obfuscates SAS token in debug logs for cache entries)</li>
</ul>
<h3>4.2.2</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v4.0.2</li>
</ul>
<h3>4.2.1</h3>
<ul>
<li>Bump <code>@actions/cache</code> to v4.0.1</li>
</ul>
<h3>4.2.0</h3>
<p>TLDR; The cache backend service has been rewritten from the ground up for improved performance and reliability. <a href="https://github.com/actions/cache">actions/cache</a> now integrates with the new cache service (v2) APIs.</p>
<p>The new service will gradually roll out as of <strong>February 1st, 2025</strong>. The legacy service will also be sunset on the same date. Changes in these release are <strong>fully backward compatible</strong>.</p>
<p><strong>We are deprecating some versions of this action</strong>. We recommend upgrading to version <code>v4</code> or <code>v3</code> as soon as possible before <strong>February 1st, 2025.</strong> (Upgrade instructions below).</p>
<p>If you are using pinned SHAs, please use the SHAs of versions <code>v4.2.0</code> or <code>v3.4.0</code></p>
<p>If you do not upgrade, all workflow runs using any of the deprecated <a href="https://github.com/actions/cache">actions/cache</a> will fail.</p>
<p>Upgrading to the recommended versions will not break your workflows.</p>
<h3>4.1.2</h3>
<ul>
<li>Add GitHub Enterprise Cloud instances hostname filters to inform API endpoint choices - <a href="https://redirect.github.com/actions/cache/pull/1474">#1474</a></li>
<li>Security fix: Bump braces from 3.0.2 to 3.0.3 - <a href="https://redirect.github.com/actions/cache/pull/1475">#1475</a></li>
</ul>
<h3>4.1.1</h3>
<ul>
<li>Restore original behavior of <code>cache-hit</code> output - <a href="https://redirect.github.com/actions/cache/pull/1467">#1467</a></li>
</ul>
<h3>4.1.0</h3>
<ul>
<li>Ensure <code>cache-hit</code> output is set when a cache is missed - <a href="https://redirect.github.com/actions/cache/pull/1404">#1404</a></li>
<li>Deprecate <code>save-always</code> input - <a href="https://redirect.github.com/actions/cache/pull/1452">#1452</a></li>
</ul>
<h3>4.0.2</h3>
<ul>
<li>Fixed restore <code>fail-on-cache-miss</code> not working.</li>
</ul>
<h3>4.0.1</h3>
<ul>
<li>Updated <code>isGhes</code> check</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/cache/commit/5a3ec84eff668545956fd18022155c47e93e2684"><code>5a3ec84</code></a> Merge pull request <a href="https://redirect.github.com/actions/cache/issues/1577">#1577</a> from salmanmkc/salmanmkc/4-test</li>
<li><a href="https://github.com/actions/cache/commit/7de21022a7b6824c106a9847befcbd8154b45b6a"><code>7de2102</code></a> Update releases.md</li>
<li><a href="https://github.com/actions/cache/commit/76d40dd347779762a1c829bbeeda5da4d81ca8c1"><code>76d40dd</code></a> Update to use the latest version of the cache package to obfuscate the SAS</li>
<li><a href="https://github.com/actions/cache/commit/76dd5eb692f606c28d4b7a4ea7cfdffc926ba06a"><code>76dd5eb</code></a> update cache with main</li>
<li><a href="https://github.com/actions/cache/commit/8c80c27c5e4498d5675b05fb1eff96a56c593b06"><code>8c80c27</code></a> new package</li>
<li><a href="https://github.com/actions/cache/commit/45cfd0e7fffd1869ea4d5bfb54a464d825c1f742"><code>45cfd0e</code></a> updates</li>
<li><a href="https://github.com/actions/cache/commit/edd449b9cf39c2a20dc7c3d505ff6dc193c48a02"><code>edd449b</code></a> updated cache with latest changes</li>
<li><a href="https://github.com/actions/cache/commit/0576707e373f92196b81695442ed3f80c347f9c7"><code>0576707</code></a> latest test before pr</li>
<li><a href="https://github.com/actions/cache/commit/3105dc9754dd9cd935ffcf45c091ed2cadbf42b9"><code>3105dc9</code></a> update</li>
<li><a href="https://github.com/actions/cache/commit/9450d42d15022999ad2fa60a8b91f01fc92a0563"><code>9450d42</code></a> mask</li>
<li>Additional commits viewable in <a href="https://github.com/actions/cache/compare/d4323d4df104b026a6aa633fdb11d772146be0bf...5a3ec84eff668545956fd18022155c47e93e2684">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/cache&package-manager=github_actions&previous-version=4.2.2&new-version=4.2.3)](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 f4829b3f..3a630c82 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -46,7 +46,7 @@ jobs:
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
 
     - name: Utilize Go Module Cache
-      uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
+      uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
       with:
         path: |
           ~/go/pkg/mod
@@ -63,7 +63,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@5f8171a638ada777af81d42b55959a643bb29017 # v3.28.12
+      uses: github/codeql-action/init@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.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@5f8171a638ada777af81d42b55959a643bb29017 # v3.28.12
+      uses: github/codeql-action/analyze@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 04fe4ec4..e2cd39ec 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -163,7 +163,7 @@ jobs:
         with:
           artifact-name: sbom.spdx.json
 
-      - uses: 8398a7/action-slack@28ba43ae48961b90635b50953d216767a6bea486 # v3.16.2
+      - uses: 8398a7/action-slack@1750b5085f3ec60384090fb7c52965ef822e869e # v3.18.0
         continue-on-error: true
         with:
           status: ${{ job.status }}
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 3f120001..e9b33c32 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@5f8171a638ada777af81d42b55959a643bb29017 # v1.0.26
+        uses: github/codeql-action/upload-sarif@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v1.0.26
         with:
           sarif_file: results.sarif
diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml
index 575a3a93..0ebae590 100644
--- a/.github/workflows/validations.yaml
+++ b/.github/workflows/validations.yaml
@@ -103,7 +103,7 @@ jobs:
         uses: ./.github/actions/bootstrap
 
       - name: Restore integration test cache
-        uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: ${{ github.workspace }}/test/integration/test-fixtures/cache
           key: ${{ runner.os }}-integration-test-cache-${{ hashFiles('test/integration/test-fixtures/cache.fingerprint') }}
@@ -134,7 +134,7 @@ jobs:
       # why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
       # see https://github.com/actions/upload-artifact/issues/199 for more info
       - name: Upload snapshot artifacts
-        uses: actions/cache/save@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: snapshot
           key: snapshot-build-${{ github.run_id }}
@@ -147,7 +147,7 @@ jobs:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
 
       - name: Download snapshot build
-        uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: snapshot
           key: snapshot-build-${{ github.run_id }}
@@ -188,14 +188,14 @@ jobs:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
 
       - name: Download snapshot build
-        uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: snapshot
           key: snapshot-build-${{ github.run_id }}
 
       - name: Restore install.sh test image cache
         id: install-test-image-cache
-        uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: ${{ github.workspace }}/test/install/cache
           key: ${{ runner.os }}-install-test-image-cache-${{ hashFiles('test/install/cache.fingerprint') }}
@@ -223,14 +223,14 @@ jobs:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
 
       - name: Download snapshot build
-        uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: snapshot
           key: snapshot-build-${{ github.run_id }}
 
       - name: Restore docker image cache for compare testing
         id: mac-compare-testing-cache
-        uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: image.tar
           key: ${{ runner.os }}-${{ hashFiles('test/compare/mac.sh') }}
@@ -251,13 +251,13 @@ jobs:
         uses: ./.github/actions/bootstrap
 
       - name: Restore CLI test-fixture cache
-        uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: ${{ github.workspace }}/test/cli/test-fixtures/cache
           key: ${{ runner.os }}-cli-test-cache-${{ hashFiles('test/cli/test-fixtures/cache.fingerprint') }}
 
       - name: Download snapshot build
-        uses: actions/cache/restore@d4323d4df104b026a6aa633fdb11d772146be0bf #v4.2.2
+        uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
         with:
           path: snapshot
           key: snapshot-build-${{ github.run_id }}
diff --git a/cmd/grype/cli/commands/db_list.go b/cmd/grype/cli/commands/db_list.go
index f20ecd84..3be6a4a9 100644
--- a/cmd/grype/cli/commands/db_list.go
+++ b/cmd/grype/cli/commands/db_list.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"net/url"
 	"os"
-	"path"
 
 	"github.com/spf13/cobra"
 
@@ -62,28 +61,39 @@ func runDBList(opts dbListOptions) error {
 		return fmt.Errorf("unable to get database listing: %w", err)
 	}
 
-	return presentDBList(opts.Output, opts.DB.UpdateURL, os.Stdout, latest)
+	u, err := c.ResolveArchiveURL(latest.Archive)
+	if err != nil {
+		return fmt.Errorf("unable to resolve database URL: %w", err)
+	}
+
+	return presentDBList(opts.Output, u, opts.DB.UpdateURL, os.Stdout, latest)
 }
 
-func presentDBList(format string, u string, writer io.Writer, latest *distribution.LatestDocument) error {
+func presentDBList(format string, archiveURL, listingURL string, writer io.Writer, latest *distribution.LatestDocument) error {
 	if latest == nil {
 		return fmt.Errorf("no database listing found")
 	}
 
-	parsedURL, err := url.Parse(u)
+	// remove query params
+	archiveURLObj, err := url.Parse(archiveURL)
 	if err != nil {
-		return fmt.Errorf("failed to parse base URL: %w", err)
+		return fmt.Errorf("unable to parse db URL %q: %w", archiveURL, err)
 	}
 
-	parsedURL.Path = path.Join(path.Dir(parsedURL.Path), latest.Path)
+	archiveURLObj.RawQuery = ""
+
+	if listingURL == distribution.DefaultConfig().LatestURL {
+		// append on the schema
+		listingURL = fmt.Sprintf("%s/v%v/%s", listingURL, latest.SchemaVersion.Model, distribution.LatestFileName)
+	}
 
 	switch format {
 	case textOutputFormat:
 		fmt.Fprintf(writer, "Status:   %s\n", latest.Status)
 		fmt.Fprintf(writer, "Schema:   %s\n", latest.SchemaVersion.String())
 		fmt.Fprintf(writer, "Built:    %s\n", latest.Built.String())
-		fmt.Fprintf(writer, "Listing:  %s\n", u)
-		fmt.Fprintf(writer, "DB URL:   %s\n", parsedURL.String())
+		fmt.Fprintf(writer, "Listing:  %s\n", listingURL)
+		fmt.Fprintf(writer, "DB URL:   %s\n", archiveURLObj.String())
 		fmt.Fprintf(writer, "Checksum: %s\n", latest.Checksum)
 	case jsonOutputFormat, "raw":
 		enc := json.NewEncoder(writer)
diff --git a/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities.go b/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities.go
index e53f30a7..6b4c7622 100644
--- a/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities.go
+++ b/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	v6 "github.com/anchore/grype/grype/db/v6"
+	"github.com/anchore/grype/internal/cvss"
 	"github.com/anchore/grype/internal/log"
 )
 
@@ -83,6 +84,23 @@ type EPSS struct {
 	Date       string  `json:"date"`
 }
 
+type CVSSSeverity struct {
+	// Vector is the CVSS assessment as a parameterized string
+	Vector string `json:"vector"`
+
+	// Version is the CVSS version (e.g. "3.0")
+	Version string `json:"version,omitempty"`
+
+	// Metrics is the CVSS quantitative assessment based on the vector
+	Metrics CvssMetrics `json:"metrics"`
+}
+
+type CvssMetrics struct {
+	BaseScore           float64  `json:"baseScore"`
+	ExploitabilityScore *float64 `json:"exploitabilityScore,omitempty"`
+	ImpactScore         *float64 `json:"impactScore,omitempty"`
+}
+
 type vulnerabilityAffectedPackageJoin struct {
 	Vulnerability    v6.VulnerabilityHandle
 	OperatingSystems []v6.OperatingSystem
@@ -111,6 +129,7 @@ func newVulnerabilityInfo(vuln v6.VulnerabilityHandle, vc vulnerabilityDecoratio
 	if vuln.BlobValue != nil {
 		blob = *vuln.BlobValue
 	}
+	patchCVSSMetrics(&blob)
 	return VulnerabilityInfo{
 		Model:             vuln,
 		VulnerabilityBlob: blob,
@@ -124,6 +143,29 @@ func newVulnerabilityInfo(vuln v6.VulnerabilityHandle, vc vulnerabilityDecoratio
 	}
 }
 
+func patchCVSSMetrics(blob *v6.VulnerabilityBlob) {
+	for i := range blob.Severities {
+		sev := &blob.Severities[i]
+		if val, ok := sev.Value.(v6.CVSSSeverity); ok {
+			met, err := cvss.ParseMetricsFromVector(val.Vector)
+			if err != nil {
+				log.WithFields("vector", val.Vector, "error", err).Debug("unable to parse CVSS vector")
+				continue
+			}
+			newSev := CVSSSeverity{
+				Vector:  val.Vector,
+				Version: val.Version,
+				Metrics: CvssMetrics{
+					BaseScore:           met.BaseScore,
+					ExploitabilityScore: met.ExploitabilityScore,
+					ImpactScore:         met.ImpactScore,
+				},
+			}
+			sev.Value = newSev
+		}
+	}
+}
+
 func newOperatingSystems(oss []v6.OperatingSystem) (os []OperatingSystem) {
 	for _, o := range oss {
 		os = append(os, OperatingSystem{
diff --git a/go.mod b/go.mod
index 5fa30375..42648620 100644
--- a/go.mod
+++ b/go.mod
@@ -10,27 +10,30 @@ require (
 	github.com/adrg/xdg v0.5.3
 	github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51
 	github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
-	github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837
+	github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716
 	github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
-	github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
+	github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d
+	github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722
 	github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
 	github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4
 	github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115
-	github.com/anchore/stereoscope v0.1.0
-	github.com/anchore/syft v1.21.0
+	github.com/anchore/stereoscope v0.1.1-0.20250320125929-d35c173d28fc
+	github.com/anchore/syft v1.21.1-0.20250320141945-410b85e1c959
 	github.com/aquasecurity/go-pep440-version v0.0.1
 	github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
 	github.com/bmatcuk/doublestar/v2 v2.0.4
 	github.com/charmbracelet/bubbletea v1.3.4
 	github.com/charmbracelet/lipgloss v1.1.0
 	github.com/dave/jennifer v1.7.1
-	github.com/docker/docker v28.0.1+incompatible
+	github.com/docker/docker v28.0.4+incompatible
 	github.com/dustin/go-humanize v1.0.1
 	github.com/facebookincubator/nvdtools v0.1.5
 	github.com/gabriel-vasile/mimetype v1.4.8
 	github.com/gkampitakis/go-snaps v0.5.11
 	github.com/glebarez/sqlite v1.11.0
 	github.com/go-test/deep v1.1.1
+	github.com/go-viper/mapstructure/v2 v2.2.1
+	github.com/gohugoio/hashstructure v0.5.0
 	github.com/google/go-cmp v0.7.0
 	github.com/google/go-containerregistry v0.20.3
 	github.com/google/uuid v1.6.0
@@ -45,9 +48,7 @@ require (
 	github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
 	github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
 	github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08
-	github.com/mitchellh/go-homedir v1.1.0
-	github.com/mitchellh/hashstructure/v2 v2.0.2
-	github.com/mitchellh/mapstructure v1.5.0
+	github.com/muesli/termenv v0.16.0
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/openvex/go-vex v0.2.5
 	github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554
@@ -70,10 +71,6 @@ require (
 	gorm.io/gorm v1.25.12
 )
 
-require github.com/DataDog/zstd v1.5.5 // indirect
-
-require github.com/muesli/termenv v0.16.0
-
 require (
 	cel.dev/expr v0.16.1 // indirect
 	cloud.google.com/go v0.116.0 // indirect
@@ -86,7 +83,8 @@ require (
 	dario.cat/mergo v1.0.1 // indirect
 	github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
 	github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
-	github.com/BurntSushi/toml v1.4.0 // indirect
+	github.com/BurntSushi/toml v1.5.0 // indirect
+	github.com/DataDog/zstd v1.5.5 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
 	github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
@@ -96,9 +94,10 @@ require (
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/Microsoft/hcsshim v0.11.7 // indirect
 	github.com/ProtonMail/go-crypto v1.1.5 // indirect
+	github.com/STARRY-S/zip v0.2.1 // indirect
 	github.com/acobaugh/osrelease v0.1.0 // indirect
 	github.com/agext/levenshtein v1.2.1 // indirect
-	github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77 // indirect
+	github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2 // indirect
 	github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb // indirect
 	github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
 	github.com/andybalholm/brotli v1.1.1 // indirect
@@ -110,7 +109,11 @@ require (
 	github.com/becheran/wildmatch-go v1.0.0 // indirect
 	github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
 	github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef // indirect
+	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
 	github.com/bmatcuk/doublestar/v4 v4.8.1 // indirect
+	github.com/bodgit/plumbing v1.3.0 // indirect
+	github.com/bodgit/sevenzip v1.6.0 // indirect
+	github.com/bodgit/windows v1.0.1 // indirect
 	github.com/buger/jsonparser v1.1.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -143,7 +146,7 @@ require (
 	github.com/docker/go-connections v0.5.0 // indirect
 	github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
 	github.com/docker/go-units v0.5.0 // indirect
-	github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
+	github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
 	github.com/edsrzf/mmap-go v1.1.0 // indirect
 	github.com/elliotchance/phpserialize v1.4.0 // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
@@ -152,11 +155,11 @@ require (
 	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
 	github.com/felixge/fgprof v0.9.5 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
-	github.com/fsnotify/fsnotify v1.7.0 // indirect
+	github.com/fsnotify/fsnotify v1.8.0 // indirect
 	github.com/github/go-spdx/v2 v2.3.2 // indirect
 	github.com/gkampitakis/ciinfo v0.3.1 // indirect
 	github.com/gkampitakis/go-diff v1.3.2 // indirect
-	github.com/glebarez/go-sqlite v1.21.2 // indirect
+	github.com/glebarez/go-sqlite v1.22.0 // indirect
 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 	github.com/go-git/go-billy/v5 v5.6.2 // indirect
 	github.com/go-git/go-git/v5 v5.14.0 // indirect
@@ -174,7 +177,7 @@ require (
 	github.com/googleapis/gax-go/v2 v2.14.1 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-safetemp v1.0.0 // indirect
-	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
 	github.com/hashicorp/hcl/v2 v2.23.0 // indirect
 	github.com/huandu/xstrings v1.5.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -192,7 +195,6 @@ require (
 	github.com/kr/text v0.2.0 // indirect
 	github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
-	github.com/magiconair/properties v1.8.9 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/maruel/natural v1.1.1 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
@@ -200,9 +202,11 @@ require (
 	github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
 	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
+	github.com/mholt/archives v0.1.0 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
-	github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
+	github.com/mitchellh/go-wordwrap v1.0.1 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/moby/docker-image-spec v1.3.1 // indirect
 	github.com/moby/locker v1.0.1 // indirect
@@ -216,6 +220,7 @@ require (
 	github.com/muesli/cancelreader v0.2.2 // indirect
 	github.com/ncruces/go-strftime v0.1.9 // indirect
 	github.com/nwaples/rardecode v1.1.3 // indirect
+	github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.1.1 // indirect
 	github.com/opencontainers/runtime-spec v1.1.0 // indirect
@@ -223,7 +228,7 @@ require (
 	github.com/package-url/packageurl-go v0.1.1 // indirect
 	github.com/pborman/indent v1.2.1 // indirect
 	github.com/pelletier/go-toml v1.9.5 // indirect
-	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
 	github.com/pierrec/lz4/v4 v4.1.22 // indirect
 	github.com/pjbgf/sha1cd v0.3.2 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
@@ -235,20 +240,20 @@ require (
 	github.com/rogpeppe/go-internal v1.14.1 // indirect
 	github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c // indirect
 	github.com/saferwall/pe v1.5.6 // indirect
-	github.com/sagikazarmark/locafero v0.4.0 // indirect
-	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+	github.com/sagikazarmark/locafero v0.7.0 // indirect
 	github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
 	github.com/sassoftware/go-rpmutils v0.4.0 // indirect
 	github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect
 	github.com/shopspring/decimal v1.4.0 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/skeema/knownhosts v1.3.1 // indirect
+	github.com/sorairolake/lzip-go v0.3.5 // indirect
 	github.com/sourcegraph/conc v0.3.0 // indirect
 	github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb // indirect
 	github.com/spdx/tools-golang v0.5.5 // indirect
-	github.com/spf13/cast v1.7.0 // indirect
+	github.com/spf13/cast v1.7.1 // indirect
 	github.com/spf13/pflag v1.0.6 // indirect
-	github.com/spf13/viper v1.19.0 // indirect
+	github.com/spf13/viper v1.20.0 // indirect
 	github.com/stretchr/objx v0.5.2 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
 	github.com/sylabs/sif/v2 v2.20.2 // indirect
@@ -278,6 +283,7 @@ require (
 	go.opentelemetry.io/otel/trace v1.33.0 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.9.0 // indirect
+	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
 	golang.org/x/crypto v0.36.0 // indirect
 	golang.org/x/mod v0.24.0 // indirect
 	golang.org/x/net v0.37.0 // indirect
@@ -293,7 +299,6 @@ require (
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
 	google.golang.org/grpc v1.67.3 // indirect
 	google.golang.org/protobuf v1.36.3 // indirect
-	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	modernc.org/libc v1.61.13 // indirect
 	modernc.org/mathutil v1.7.1 // indirect
diff --git a/go.sum b/go.sum
index e00289b2..8b7fb2a8 100644
--- a/go.sum
+++ b/go.sum
@@ -629,8 +629,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
-github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
+github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/CycloneDX/cyclonedx-go v0.9.2 h1:688QHn2X/5nRezKe2ueIVCt+NRqf7fl3AVQk+vaFcIo=
 github.com/CycloneDX/cyclonedx-go v0.9.2/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg=
@@ -664,6 +664,8 @@ github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8
 github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
 github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
 github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
+github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
 github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
@@ -684,14 +686,16 @@ github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51 h1:yhk+P8lF3
 github.com/anchore/archiver/v3 v3.5.3-0.20241210171143-5b1d8d1c7c51/go.mod h1:nwuGSd7aZp0rtYt79YggCGafz1RYsclE7pi3fhLwvuw=
 github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU=
 github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
-github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837 h1:bIG3WsfosZsJ5LMC7PB9J/ekFM3a0j0ZEDvN3ID6GTI=
-github.com/anchore/clio v0.0.0-20241115144204-29e89f9fa837/go.mod h1:tRQVKkjYeejrh9AdM0s1esbwtMU7rdHAHSQWkv4qskE=
-github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77 h1:h7+GCqazHVS5GDJYYS6wjjglYi8xFnVWMdSUukoImTM=
-github.com/anchore/fangs v0.0.0-20241014225144-4e1713cafd77/go.mod h1:qbev5czQeyDO74fPNThiEKYkgt0mx1axb+5wQcxDPFY=
+github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716 h1:2sIdYJlQESEnyk3Y0WD2vXWW5eD2iMz9Ev8fj1Z8LNA=
+github.com/anchore/clio v0.0.0-20250319180342-2cfe4b0cb716/go.mod h1:Utb9i4kwiCWvqAIxZaJeMIXFO9uOgQXlvH2BfbfO/zI=
+github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2 h1:GC2QaO0YsmjpsZ4rtVKv9DnproIxqqn+qkskpc+i8MA=
+github.com/anchore/fangs v0.0.0-20250319222917-446a1e748ec2/go.mod h1:XUbUECwVKuD3qYRUj+QZIOHjyyXua2gFmVjKA40iHXA=
 github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q=
 github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
-github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
-github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg=
+github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d h1:gT69osH9AsdpOfqxbRwtxcNnSZ1zg4aKy2BevO3ZBdc=
+github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d/go.mod h1:PhSnuFYknwPZkOWKB1jXBNToChBA+l0FjwOxtViIc50=
+github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4VyBzhjLkRF/3gDrcpUBj8LjvvO6OOM=
+github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw=
 github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
 github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk=
 github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
@@ -702,10 +706,10 @@ github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4 h1:rmZG77uXgE
 github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
 github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY=
 github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
-github.com/anchore/stereoscope v0.1.0 h1:waF2C5b/XRAyShKup3VoJz8Pdv7AiDxUbABTizmJdJ0=
-github.com/anchore/stereoscope v0.1.0/go.mod h1:3vasimie0IJOXvMbMpjwvwIHBDA1+192QZDWNhJRqFQ=
-github.com/anchore/syft v1.21.0 h1:JHmYOnEbCJsElROCCfg+3oIODw1LQLfXGkIrmXNZYsI=
-github.com/anchore/syft v1.21.0/go.mod h1:8i8Yp/MiSOdqID0+6eiwE9bOJWM7fEBYitINZyr2G6s=
+github.com/anchore/stereoscope v0.1.1-0.20250320125929-d35c173d28fc h1:wyiNipBKQApiCX56jeDNTrnkHddF4laJwze6ldYr+ZA=
+github.com/anchore/stereoscope v0.1.1-0.20250320125929-d35c173d28fc/go.mod h1:o4Gy3h1KLi27+v53O58whB9yCqcrgGdhthRc3st4whk=
+github.com/anchore/syft v1.21.1-0.20250320141945-410b85e1c959 h1:yywFcavCivMcW5Bjn7wO9XW+9D6GbLt6TRhdErn+KSs=
+github.com/anchore/syft v1.21.1-0.20250320141945-410b85e1c959/go.mod h1:xQpHtL2mfQvMcEf8PqR/sbUlpcvaIlv3f/m2ETEooas=
 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
@@ -750,10 +754,18 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ
 github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef h1:TSFnfbbu2oAOuWbeDDTtwXWE6z+PmpgbSsMBeV7l0ww=
 github.com/bitnami/go-version v0.0.0-20250131085805-b1f57a8634ef/go.mod h1:9iglf1GG4oNRJ39bZ5AZrjgAFD2RwQbXw6Qf7Cs47wo=
+github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
+github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI=
 github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
 github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
 github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
+github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
+github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
+github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
+github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
+github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
+github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
 github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
@@ -863,8 +875,8 @@ github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl
 github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
-github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrbxjVBtS+cdcok=
+github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
 github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
 github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
@@ -874,8 +886,8 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6Uezg
 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
-github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
-github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
+github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
+github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
@@ -929,8 +941,8 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 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/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
 github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
 github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -942,8 +954,8 @@ github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZ
 github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
 github.com/gkampitakis/go-snaps v0.5.11 h1:LFG0ggUKR+KEiiaOvFCmLgJ5NO2zf93AxxddkBn3LdQ=
 github.com/gkampitakis/go-snaps v0.5.11/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY=
-github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
-github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
+github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
+github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
 github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
 github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
 github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
@@ -983,6 +995,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
 github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
+github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
+github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
 github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
 github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
@@ -993,6 +1007,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp47K9swqg=
+github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
@@ -1160,7 +1176,8 @@ github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
 github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
@@ -1289,6 +1306,8 @@ github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
+github.com/mholt/archives v0.1.0 h1:FacgJyrjiuyomTuNA92X5GyRBRZjE43Y/lrzKIlF35Q=
+github.com/mholt/archives v0.1.0/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
 github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -1302,15 +1321,11 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
 github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
 github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
-github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
-github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
-github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
-github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
+github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
+github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
 github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@@ -1347,6 +1362,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
 github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
 github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
 github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
+github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78 h1:MYzLheyVx1tJVDqfu3YnN4jtnyALNzLvwl+f58TcvQY=
+github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
@@ -1375,8 +1392,8 @@ github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrp
 github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
 github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
-github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
 github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
 github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
@@ -1434,14 +1451,13 @@ github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c h1:8
 github.com/rust-secure-code/go-rustaudit v0.0.0-20250226111315-e20ec32e963c/go.mod h1:kwM/7r/rVluTE8qJbHAffduuqmSv4knVQT2IajGvSiA=
 github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
 github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk=
+github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/saferwall/pe v1.5.6 h1:DrRLnoQFxHWJ5lJUmrH7X2L0xeUu6SUS95Dc61eW2Yc=
 github.com/saferwall/pe v1.5.6/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
 github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
-github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
-github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
-github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
-github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
+github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
 github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
 github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
 github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
@@ -1471,6 +1487,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
 github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
+github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
+github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
 github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
 github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -1484,8 +1502,8 @@ github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcD
 github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
 github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
 github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
-github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
+github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
 github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
 github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
@@ -1494,8 +1512,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
-github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
-github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
+github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
+github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -1637,6 +1655,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
 go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
 go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
+go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -2368,8 +2388,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
 gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/grype/db/v5/namespace/index.go b/grype/db/v5/namespace/index.go
deleted file mode 100644
index 5c207cd4..00000000
--- a/grype/db/v5/namespace/index.go
+++ /dev/null
@@ -1,248 +0,0 @@
-package namespace
-
-import (
-	"fmt"
-	"regexp"
-	"sort"
-	"strings"
-
-	hashiVer "github.com/anchore/go-version"
-	"github.com/anchore/grype/grype/db/v5/namespace/cpe"
-	"github.com/anchore/grype/grype/db/v5/namespace/distro"
-	"github.com/anchore/grype/grype/db/v5/namespace/language"
-	grypeDistro "github.com/anchore/grype/grype/distro"
-	"github.com/anchore/grype/internal"
-	"github.com/anchore/grype/internal/log"
-	syftPkg "github.com/anchore/syft/syft/pkg"
-)
-
-var simpleSemVer = regexp.MustCompile(`^(?P<major>\d+)(\.(?P<minor>\d+)(\.(?P<patch>\d+(?P<remaining>[^-_]+)*))?)?$`)
-
-type Index struct {
-	all         []Namespace
-	byLanguage  map[syftPkg.Language][]*language.Namespace
-	byDistroKey map[string][]*distro.Namespace
-	cpe         []*cpe.Namespace
-}
-
-func FromStrings(namespaces []string) (*Index, error) {
-	all := make([]Namespace, 0)
-	byLanguage := make(map[syftPkg.Language][]*language.Namespace)
-	byDistroKey := make(map[string][]*distro.Namespace)
-	cpeNamespaces := make([]*cpe.Namespace, 0)
-
-	for _, n := range namespaces {
-		ns, err := FromString(n)
-
-		if err != nil {
-			log.Warnf("unable to create namespace object from namespace=%s: %+v", n, err)
-			continue
-		}
-
-		all = append(all, ns)
-
-		switch nsObj := ns.(type) {
-		case *language.Namespace:
-			l := nsObj.Language()
-			if _, ok := byLanguage[l]; !ok {
-				byLanguage[l] = make([]*language.Namespace, 0)
-			}
-
-			byLanguage[l] = append(byLanguage[l], nsObj)
-		case *distro.Namespace:
-			distroKey := fmt.Sprintf("%s:%s", nsObj.DistroType(), nsObj.Version())
-			if _, ok := byDistroKey[distroKey]; !ok {
-				byDistroKey[distroKey] = make([]*distro.Namespace, 0)
-			}
-
-			byDistroKey[distroKey] = append(byDistroKey[distroKey], nsObj)
-		case *cpe.Namespace:
-			cpeNamespaces = append(cpeNamespaces, nsObj)
-		default:
-			log.Warnf("unable to index namespace=%s", n)
-			continue
-		}
-	}
-
-	return &Index{
-		all:         all,
-		byLanguage:  byLanguage,
-		byDistroKey: byDistroKey,
-		cpe:         cpeNamespaces,
-	}, nil
-}
-
-func (i *Index) NamespacesForLanguage(l syftPkg.Language) []*language.Namespace {
-	if _, ok := i.byLanguage[l]; ok {
-		return i.byLanguage[l]
-	}
-
-	return nil
-}
-
-//nolint:funlen,gocognit
-func (i *Index) NamespacesForDistro(d *grypeDistro.Distro) []*distro.Namespace {
-	if d == nil {
-		return nil
-	}
-
-	dTy := DistroTypeString(d.Type)
-
-	if d.IsRolling() {
-		distroKey := fmt.Sprintf("%s:%s", dTy, "rolling")
-		if v, ok := i.byDistroKey[distroKey]; ok {
-			return v
-		}
-	}
-
-	var versionSegments []int
-	if d.Version != nil {
-		versionSegments = d.Version.Segments()
-	}
-
-	switch d.Type {
-	case grypeDistro.Alpine:
-		if v := i.getAlpineMajorMinorNamespace(d, versionSegments); v != nil {
-			return v
-		}
-
-		// Fall back to alpine:edge if no version segments found
-		// alpine:edge is labeled as alpine-x.x_alphaYYYYMMDD
-		distroKey := fmt.Sprintf("%s:%s", dTy, "edge")
-		if v, ok := i.byDistroKey[distroKey]; ok {
-			return v
-		}
-	case grypeDistro.Debian:
-		if v, ok := i.findClosestNamespace(d, versionSegments); ok {
-			return v
-		}
-
-		if d.RawVersion == "unstable" {
-			distroKey := fmt.Sprintf("%s:%s", dTy, "unstable")
-			if v, ok := i.byDistroKey[distroKey]; ok {
-				return v
-			}
-		}
-	}
-
-	if v, ok := i.findClosestNamespace(d, versionSegments); ok {
-		return v
-	}
-
-	return nil
-}
-
-func (i *Index) getAlpineMajorMinorNamespace(d *grypeDistro.Distro, versionSegments []int) []*distro.Namespace {
-	var hasPrerelease bool
-	if d.Version != nil {
-		hasPrerelease = d.Version.Prerelease() != ""
-	}
-
-	if !hasPrerelease {
-		namespaces, done := i.findClosestNamespace(d, versionSegments)
-		if done {
-			return namespaces
-		}
-	}
-	// If the version does not match x.y.z then it is edge
-	// In this case it would have - or _ alpha,beta,etc
-	// note: later in processing we handle the alpine:edge case
-	return nil
-}
-
-func (i *Index) findClosestNamespace(d *grypeDistro.Distro, versionSegments []int) ([]*distro.Namespace, bool) {
-	ty := DistroTypeString(d.Type)
-
-	// look for exact match
-	distroKey := fmt.Sprintf("%s:%s", ty, d.FullVersion())
-	if v, ok := i.byDistroKey[distroKey]; ok {
-		return v, true
-	}
-
-	values := internal.MatchNamedCaptureGroups(simpleSemVer, d.RawVersion)
-
-	switch {
-	case values["major"] == "":
-		// use edge
-		break
-	case values["minor"] == "":
-		namespaces, done := i.findHighestMatchingMajorVersionNamespaces(d, versionSegments)
-		if done {
-			return namespaces, true
-		}
-
-	default:
-
-		if len(versionSegments) >= 2 {
-			// try with only first two version components
-			distroKey = fmt.Sprintf("%s:%d.%d", ty, versionSegments[0], versionSegments[1])
-			if v, ok := i.byDistroKey[distroKey]; ok {
-				return v, true
-			}
-		}
-
-		if len(versionSegments) >= 1 {
-			// try using only major version component
-			distroKey = fmt.Sprintf("%s:%d", ty, versionSegments[0])
-			if v, ok := i.byDistroKey[distroKey]; ok {
-				return v, true
-			}
-		}
-	}
-	return nil, false
-}
-
-func (i *Index) findHighestMatchingMajorVersionNamespaces(d *grypeDistro.Distro, versionSegments []int) ([]*distro.Namespace, bool) {
-	// find the highest version that matches the major version
-	majorVersion := versionSegments[0]
-
-	var all []*distro.Namespace
-	for _, vs := range i.byDistroKey {
-		for _, v := range vs {
-			if v.DistroType() == d.Type {
-				all = append(all, v)
-			}
-		}
-	}
-
-	type namespaceVersion struct {
-		version   *hashiVer.Version
-		namespace *distro.Namespace
-	}
-
-	var valid []namespaceVersion
-	for _, v := range all {
-		if strings.HasPrefix(v.Version(), fmt.Sprintf("%d.", majorVersion)) {
-			ver, err := hashiVer.NewVersion(v.Version())
-			if err != nil {
-				continue
-			}
-			valid = append(valid, namespaceVersion{
-				version:   ver,
-				namespace: v,
-			})
-		}
-	}
-
-	// return the highest version from valid
-	sort.Slice(valid, func(i, j int) bool {
-		return valid[i].version.GreaterThan(valid[j].version)
-	})
-
-	if len(valid) > 0 {
-		return []*distro.Namespace{valid[0].namespace}, true
-	}
-	return nil, false
-}
-
-func (i *Index) CPENamespaces() []*cpe.Namespace {
-	return i.cpe
-}
-
-func DistroTypeString(ty grypeDistro.Type) string {
-	switch ty {
-	case grypeDistro.CentOS, grypeDistro.RedHat, grypeDistro.Fedora, grypeDistro.RockyLinux, grypeDistro.AlmaLinux, grypeDistro.Gentoo:
-		return strings.ToLower(string(grypeDistro.RedHat))
-	}
-	return strings.ToLower(string(ty))
-}
diff --git a/grype/db/v5/pkg/qualifier/from_json.go b/grype/db/v5/pkg/qualifier/from_json.go
index a06e76dc..dbb661ee 100644
--- a/grype/db/v5/pkg/qualifier/from_json.go
+++ b/grype/db/v5/pkg/qualifier/from_json.go
@@ -3,7 +3,7 @@ package qualifier
 import (
 	"encoding/json"
 
-	"github.com/mitchellh/mapstructure"
+	"github.com/go-viper/mapstructure/v2"
 
 	"github.com/anchore/grype/grype/db/v5/pkg/qualifier/platformcpe"
 	"github.com/anchore/grype/grype/db/v5/pkg/qualifier/rpmmodularity"
diff --git a/grype/db/v6/affected_package_store.go b/grype/db/v6/affected_package_store.go
index e902ba4c..59ef9da4 100644
--- a/grype/db/v6/affected_package_store.go
+++ b/grype/db/v6/affected_package_store.go
@@ -97,6 +97,9 @@ type OSSpecifier struct {
 	// MinorVersion is the second field in the VERSION_ID field in /etc/os-release (e.g. 0 in "7.0.1406")
 	MinorVersion string
 
+	// RemainingVersion is anything after the minor version in the VERSION_ID field in /etc/os-release (e.g. 1406 in "7.0.1406")
+	RemainingVersion string
+
 	// LabelVersion is a string that represents a floating version (e.g. "edge" or "unstable") or is the CODENAME field in /etc/os-release (e.g. "wheezy" for debian 7)
 	LabelVersion string
 }
@@ -112,10 +115,7 @@ func (d *OSSpecifier) String() string {
 
 	var version string
 	if d.MajorVersion != "" {
-		version = d.MajorVersion
-		if d.MinorVersion != "" {
-			version += "." + d.MinorVersion
-		}
+		version = d.version()
 	} else {
 		version = d.LabelVersion
 	}
@@ -132,19 +132,17 @@ func (d *OSSpecifier) String() string {
 }
 
 func (d OSSpecifier) version() string {
-	if d.MajorVersion != "" && d.MinorVersion != "" {
-		return d.MajorVersion + "." + d.MinorVersion
-	}
-
 	if d.MajorVersion != "" {
+		if d.MinorVersion != "" {
+			if d.RemainingVersion != "" {
+				return d.MajorVersion + "." + d.MinorVersion + "." + d.RemainingVersion
+			}
+			return d.MajorVersion + "." + d.MinorVersion
+		}
 		return d.MajorVersion
 	}
 
-	if d.LabelVersion != "" {
-		return d.LabelVersion
-	}
-
-	return ""
+	return d.LabelVersion
 }
 
 func (d OSSpecifiers) String() string {
diff --git a/grype/db/v6/data.go b/grype/db/v6/data.go
index 42117c0d..bbd1ac09 100644
--- a/grype/db/v6/data.go
+++ b/grype/db/v6/data.go
@@ -42,7 +42,7 @@ func KnownOperatingSystemSpecifierOverrides() []OperatingSystemSpecifierOverride
 		//
 		// depending where the team is during the development cycle you will see different behavior, making automating
 		// this a little challenging.
-		{Alias: "debian", Codename: "trixie", Rolling: true}, // is currently sid, which is considered rolling
+		{Alias: "debian", Codename: "trixie", Rolling: true, ReplacementLabelVersion: strRef("unstable")}, // is currently sid, which is considered rolling
 	}
 }
 
diff --git a/grype/db/v6/severity.go b/grype/db/v6/severity.go
index 0c7c4b36..74b14fef 100644
--- a/grype/db/v6/severity.go
+++ b/grype/db/v6/severity.go
@@ -2,15 +2,9 @@ package v6
 
 import (
 	"fmt"
-	"math"
-	"strings"
-
-	gocvss20 "github.com/pandatix/go-cvss/20"
-	gocvss30 "github.com/pandatix/go-cvss/30"
-	gocvss31 "github.com/pandatix/go-cvss/31"
-	gocvss40 "github.com/pandatix/go-cvss/40"
 
 	"github.com/anchore/grype/grype/vulnerability"
+	"github.com/anchore/grype/internal/cvss"
 	"github.com/anchore/grype/internal/log"
 )
 
@@ -35,7 +29,7 @@ func extractSeverity(severity any) (vulnerability.Severity, error) {
 	case string:
 		return vulnerability.ParseSeverity(sev), nil
 	case CVSSSeverity:
-		metrics, err := parseCVSS(sev.Vector)
+		metrics, err := cvss.ParseMetricsFromVector(sev.Vector)
 		if err != nil {
 			return vulnerability.UnknownSeverity, fmt.Errorf("unable to parse CVSS vector: %w", err)
 		}
@@ -48,67 +42,6 @@ func extractSeverity(severity any) (vulnerability.Severity, error) {
 	}
 }
 
-func parseCVSS(vector string) (*vulnerability.CvssMetrics, error) {
-	switch {
-	case strings.HasPrefix(vector, "CVSS:3.0"):
-		cvss, err := gocvss30.ParseVector(vector)
-		if err != nil {
-			return nil, fmt.Errorf("unable to parse CVSS v3 vector: %w", err)
-		}
-		ex := roundScore(cvss.Exploitability())
-		im := roundScore(cvss.Impact())
-		return &vulnerability.CvssMetrics{
-			BaseScore:           roundScore(cvss.BaseScore()),
-			ExploitabilityScore: &ex,
-			ImpactScore:         &im,
-		}, nil
-	case strings.HasPrefix(vector, "CVSS:3.1"):
-		cvss, err := gocvss31.ParseVector(vector)
-		if err != nil {
-			return nil, fmt.Errorf("unable to parse CVSS v3.1 vector: %w", err)
-		}
-		ex := roundScore(cvss.Exploitability())
-		im := roundScore(cvss.Impact())
-		return &vulnerability.CvssMetrics{
-			BaseScore:           roundScore(cvss.BaseScore()),
-			ExploitabilityScore: &ex,
-			ImpactScore:         &im,
-		}, nil
-	case strings.HasPrefix(vector, "CVSS:4.0"):
-		cvss, err := gocvss40.ParseVector(vector)
-		if err != nil {
-			return nil, fmt.Errorf("unable to parse CVSS v4.0 vector: %w", err)
-		}
-		// there are no exploitability and impact scores in CVSS v4.0
-		return &vulnerability.CvssMetrics{
-			BaseScore: roundScore(cvss.Score()),
-		}, nil
-	default:
-		// should be CVSS v2.0 or is invalid
-		cvss, err := gocvss20.ParseVector(vector)
-		if err != nil {
-			return nil, fmt.Errorf("unable to parse CVSS v2 vector: %w", err)
-		}
-		ex := roundScore(cvss.Exploitability())
-		im := roundScore(cvss.Impact())
-		return &vulnerability.CvssMetrics{
-			BaseScore:           roundScore(cvss.BaseScore()),
-			ExploitabilityScore: &ex,
-			ImpactScore:         &im,
-		}, nil
-	}
-}
-
-// roundScore rounds the score to the nearest tenth based on first.org rounding rules
-// see https://www.first.org/cvss/v3.1/specification-document#Appendix-A---Floating-Point-Rounding
-func roundScore(score float64) float64 {
-	intInput := int(math.Round(score * 100000))
-	if intInput%10000 == 0 {
-		return float64(intInput) / 100000.0
-	}
-	return (math.Floor(float64(intInput)/10000.0) + 1) / 10.0
-}
-
 func interpretCVSS(score float64, version string) vulnerability.Severity {
 	switch version {
 	case "2.0":
@@ -178,7 +111,7 @@ func toCvss(severities ...Severity) []vulnerability.Cvss {
 		}
 		var usedMetrics vulnerability.CvssMetrics
 		// though the DB has the base score, we parse the vector for all metrics
-		metrics, err := parseCVSS(cvssSev.Vector)
+		metrics, err := cvss.ParseMetricsFromVector(cvssSev.Vector)
 		if err != nil {
 			log.WithFields("vector", cvssSev.Vector, "error", err).Warn("unable to parse CVSS vector")
 			continue
diff --git a/grype/db/v6/vulnerability_provider.go b/grype/db/v6/vulnerability_provider.go
index acf66c6d..5b6c1c9a 100644
--- a/grype/db/v6/vulnerability_provider.go
+++ b/grype/db/v6/vulnerability_provider.go
@@ -250,9 +250,11 @@ func (vp vulnerabilityProvider) FindVulnerabilities(criteria ...vulnerability.Cr
 			case *search.DistroCriteria:
 				for _, d := range c.Distros {
 					osSpecs = append(osSpecs, &OSSpecifier{
-						Name:         d.Name(),
-						MajorVersion: d.MajorVersion(),
-						MinorVersion: d.MinorVersion(),
+						Name:             d.Name(),
+						MajorVersion:     d.MajorVersion(),
+						MinorVersion:     d.MinorVersion(),
+						RemainingVersion: d.RemainingVersion(),
+						LabelVersion:     d.Codename,
 					})
 				}
 				applied = true
diff --git a/grype/distro/distro.go b/grype/distro/distro.go
index 17ac8dbb..b7234442 100644
--- a/grype/distro/distro.go
+++ b/grype/distro/distro.go
@@ -11,29 +11,46 @@ import (
 
 // Distro represents a Linux Distribution.
 type Distro struct {
-	Type       Type
-	Version    *hashiVer.Version
-	RawVersion string
-	IDLike     []string
+	Type     Type
+	Version  string
+	Codename string
+	IDLike   []string
+
+	// fields populated in the constructor
+
+	major     string
+	minor     string
+	remaining string
 }
 
 // New creates a new Distro object populated with the given values.
-func New(t Type, version string, idLikes ...string) (*Distro, error) {
-	var verObj *hashiVer.Version
-	var err error
-
+func New(t Type, version, label string, idLikes ...string) (*Distro, error) {
+	var major, minor, remaining string
 	if version != "" {
-		verObj, err = hashiVer.NewVersion(version)
-		if err != nil {
-			return nil, fmt.Errorf("unable to parse version: %w", err)
+		// if starts with a digit, then assume it's a version and extract the major, minor, and remaining versions
+		if version[0] >= '0' && version[0] <= '9' {
+			// extract the major, minor, and remaining versions
+			parts := strings.Split(version, ".")
+			if len(parts) > 0 {
+				major = parts[0]
+				if len(parts) > 1 {
+					minor = parts[1]
+				}
+				if len(parts) > 2 {
+					remaining = strings.Join(parts[2:], ".")
+				}
+			}
 		}
 	}
 
 	return &Distro{
-		Type:       t,
-		Version:    verObj,
-		RawVersion: version,
-		IDLike:     idLikes,
+		Type:      t,
+		major:     major,
+		minor:     minor,
+		remaining: remaining,
+		Version:   version,
+		Codename:  label,
+		IDLike:    idLikes,
 	}, nil
 }
 
@@ -51,21 +68,18 @@ func NewFromRelease(release linux.Release) (*Distro, error) {
 			continue
 		}
 
-		if _, err := hashiVer.NewVersion(version); err == nil {
+		_, err := hashiVer.NewVersion(version)
+		if err == nil {
 			selectedVersion = version
 			break
 		}
 	}
 
-	if t == Debian && release.VersionID == "" && release.Version == "" && strings.Contains(release.PrettyName, "sid") {
-		return &Distro{
-			Type:       t,
-			RawVersion: "unstable",
-			IDLike:     release.IDLike,
-		}, nil
+	if selectedVersion == "" {
+		selectedVersion = release.VersionID
 	}
 
-	return New(t, selectedVersion, release.IDLike...)
+	return New(t, selectedVersion, release.VersionCodename, release.IDLike...)
 }
 
 func (d Distro) Name() string {
@@ -74,46 +88,27 @@ func (d Distro) Name() string {
 
 // MajorVersion returns the major version value from the pseudo-semantically versioned distro version value.
 func (d Distro) MajorVersion() string {
-	if d.Version == nil {
-		return strings.Split(d.RawVersion, ".")[0]
-	}
-	return fmt.Sprintf("%d", d.Version.Segments()[0])
+	return d.major
 }
 
 // MinorVersion returns the minor version value from the pseudo-semantically versioned distro version value.
 func (d Distro) MinorVersion() string {
-	if d.Version == nil {
-		parts := strings.Split(d.RawVersion, ".")
-		if len(parts) > 1 {
-			return parts[1]
-		}
-		return ""
-	}
-	parts := d.Version.Segments()
-	if len(parts) > 1 {
-		return fmt.Sprintf("%d", parts[1])
-	}
-	return ""
+	return d.minor
 }
 
-// FullVersion returns the original user version value.
-func (d Distro) FullVersion() string {
-	return d.RawVersion
+func (d Distro) RemainingVersion() string {
+	return d.remaining
 }
 
 // String returns a human-friendly representation of the Linux distribution.
 func (d Distro) String() string {
 	versionStr := "(version unknown)"
-	if d.RawVersion != "" {
-		versionStr = d.RawVersion
+	if d.Version != "" {
+		versionStr = d.Version
 	}
 	return fmt.Sprintf("%s %s", d.Type, versionStr)
 }
 
-func (d Distro) IsRolling() bool {
-	return d.Type == Wolfi || d.Type == Chainguard || d.Type == ArchLinux || d.Type == Gentoo
-}
-
 // Unsupported Linux distributions
 func (d Distro) Disabled() bool {
 	switch {
diff --git a/grype/match/details.go b/grype/match/details.go
index aeeabad9..5c7ac53f 100644
--- a/grype/match/details.go
+++ b/grype/match/details.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/mitchellh/hashstructure/v2"
+	"github.com/gohugoio/hashstructure"
 )
 
 type Details []Detail
@@ -43,7 +43,7 @@ func (m Details) Types() (tys []Type) {
 }
 
 func (m Detail) ID() string {
-	f, err := hashstructure.Hash(&m, hashstructure.FormatV2, &hashstructure.HashOptions{
+	f, err := hashstructure.Hash(&m, &hashstructure.HashOptions{
 		ZeroNil:      true,
 		SlicesAsSets: true,
 	})
diff --git a/grype/match/fingerprint.go b/grype/match/fingerprint.go
index d4950ee6..cec830fc 100644
--- a/grype/match/fingerprint.go
+++ b/grype/match/fingerprint.go
@@ -3,7 +3,7 @@ package match
 import (
 	"fmt"
 
-	"github.com/mitchellh/hashstructure/v2"
+	"github.com/gohugoio/hashstructure"
 
 	"github.com/anchore/grype/grype/pkg"
 )
@@ -24,7 +24,7 @@ func (m Fingerprint) String() string {
 }
 
 func (m Fingerprint) ID() string {
-	f, err := hashstructure.Hash(&m, hashstructure.FormatV2, &hashstructure.HashOptions{
+	f, err := hashstructure.Hash(&m, &hashstructure.HashOptions{
 		ZeroNil:      true,
 		SlicesAsSets: true,
 	})
diff --git a/grype/matcher/internal/distro.go b/grype/matcher/internal/distro.go
index 1608522f..fd2f7fba 100644
--- a/grype/matcher/internal/distro.go
+++ b/grype/matcher/internal/distro.go
@@ -54,7 +54,7 @@ func MatchPackageByDistro(provider vulnerability.Provider, p pkg.Package, upstre
 					SearchedBy: map[string]interface{}{
 						"distro": map[string]string{
 							"type":    p.Distro.Type.String(),
-							"version": p.Distro.RawVersion,
+							"version": p.Distro.Version,
 						},
 						// why include the package information? The given package searched with may be a source package
 						// for another package that is installed on the system. This makes it apparent exactly what
diff --git a/grype/pkg/package.go b/grype/pkg/package.go
index 5d3a0594..58ea7c8d 100644
--- a/grype/pkg/package.go
+++ b/grype/pkg/package.go
@@ -204,6 +204,8 @@ func dataFromPkg(p syftPkg.Package) (interface{}, []UpstreamPackage) {
 		metadata = golangMetadataFromPkg(p)
 	case syftPkg.DpkgDBEntry:
 		upstreams = dpkgDataFromPkg(p)
+	case syftPkg.DpkgArchiveEntry:
+		upstreams = dpkgDataFromPkg(p)
 	case syftPkg.RpmArchive, syftPkg.RpmDBEntry:
 		m, u := rpmDataFromPkg(p)
 		upstreams = u
@@ -277,16 +279,25 @@ func golangMetadataFromPkg(p syftPkg.Package) interface{} {
 }
 
 func dpkgDataFromPkg(p syftPkg.Package) (upstreams []UpstreamPackage) {
-	if value, ok := p.Metadata.(syftPkg.DpkgDBEntry); ok {
+	switch value := p.Metadata.(type) {
+	case syftPkg.DpkgDBEntry:
 		if value.Source != "" {
 			upstreams = append(upstreams, UpstreamPackage{
 				Name:    value.Source,
 				Version: value.SourceVersion,
 			})
 		}
-	} else {
+	case syftPkg.DpkgArchiveEntry:
+		if value.Source != "" {
+			upstreams = append(upstreams, UpstreamPackage{
+				Name:    value.Source,
+				Version: value.SourceVersion,
+			})
+		}
+	default:
 		log.Warnf("unable to extract DPKG metadata for %s", p)
 	}
+
 	return upstreams
 }
 
diff --git a/grype/pkg/purl_provider.go b/grype/pkg/purl_provider.go
index 692a3b10..2b8f14f7 100644
--- a/grype/pkg/purl_provider.go
+++ b/grype/pkg/purl_provider.go
@@ -7,9 +7,9 @@ import (
 	"os"
 	"strings"
 
-	"github.com/mitchellh/go-homedir"
 	"github.com/scylladb/go-set/strset"
 
+	"github.com/anchore/go-homedir"
 	"github.com/anchore/packageurl-go"
 	"github.com/anchore/syft/syft/cpe"
 	"github.com/anchore/syft/syft/linux"
diff --git a/grype/pkg/syft_sbom_provider.go b/grype/pkg/syft_sbom_provider.go
index 056742a9..4c7b88ba 100644
--- a/grype/pkg/syft_sbom_provider.go
+++ b/grype/pkg/syft_sbom_provider.go
@@ -9,8 +9,8 @@ import (
 	"strings"
 
 	"github.com/gabriel-vasile/mimetype"
-	"github.com/mitchellh/go-homedir"
 
+	"github.com/anchore/go-homedir"
 	"github.com/anchore/grype/internal"
 	"github.com/anchore/grype/internal/log"
 	"github.com/anchore/syft/syft/format"
diff --git a/grype/presenter/models/distribution.go b/grype/presenter/models/distribution.go
index 7e5bf242..e105a8da 100644
--- a/grype/presenter/models/distribution.go
+++ b/grype/presenter/models/distribution.go
@@ -34,7 +34,7 @@ func newDistribution(r *linux.Release) distribution {
 
 	return distribution{
 		Name:    d.Name(),
-		Version: d.FullVersion(),
+		Version: d.Version,
 		IDLike:  cleanIDLike(d.IDLike),
 	}
 }
diff --git a/grype/presenter/template/presenter.go b/grype/presenter/template/presenter.go
index bf61c055..402b9201 100644
--- a/grype/presenter/template/presenter.go
+++ b/grype/presenter/template/presenter.go
@@ -8,9 +8,9 @@ import (
 	"text/template"
 
 	"github.com/Masterminds/sprig/v3"
-	"github.com/mitchellh/go-homedir"
 
 	"github.com/anchore/clio"
+	"github.com/anchore/go-homedir"
 	"github.com/anchore/grype/grype/presenter/models"
 )
 
diff --git a/grype/search/distro.go b/grype/search/distro.go
index da5ffa80..e371b1af 100644
--- a/grype/search/distro.go
+++ b/grype/search/distro.go
@@ -63,13 +63,13 @@ func matchesDistro(d *distro.Distro, ns *distroNs.Namespace) bool {
 		return false
 	}
 
-	ty := namespace.DistroTypeString(d.Type)
-
-	distroType := ns.DistroType()
-	if distroType != d.Type && distroType != distro.Type(ty) {
+	distroType := mimicV6DistroTypeOverrides(ns.DistroType())
+	targetType := mimicV6DistroTypeOverrides(d.Type)
+	if distroType != targetType {
 		return false
 	}
-	return compatibleVersion(d.FullVersion(), ns.Version())
+
+	return compatibleVersion(d.Version, ns.Version())
 }
 
 // compatibleVersion returns true when the versions are the same or the partial version describes the matching parts
@@ -86,3 +86,34 @@ func compatibleVersion(fullVersion string, partialVersion string) bool {
 	}
 	return false
 }
+
+// TODO: this is a temporary workaround... in the long term the mock should more strongly enforce
+// data overrides and not require this kind of logic being baked into mocks directly.
+func mimicV6DistroTypeOverrides(t distro.Type) distro.Type {
+	overrideMap := map[string]string{
+		"centos":      "rhel",
+		"rocky":       "rhel",
+		"rockylinux":  "rhel",
+		"alma":        "rhel",
+		"almalinux":   "rhel",
+		"gentoo":      "rhel",
+		"archlinux":   "arch",
+		"oracle":      "ol",
+		"oraclelinux": "ol",
+		"amazon":      "amzn",
+		"amazonlinux": "amzn",
+	}
+
+	applyMapping := func(i string) distro.Type {
+		if replacement, exists := distro.IDMapping[i]; exists {
+			return replacement
+		}
+		return distro.Type(i)
+	}
+
+	if replacement, exists := overrideMap[string(t)]; exists {
+		return applyMapping(replacement)
+	}
+
+	return applyMapping(string(t))
+}
diff --git a/internal/cvss/metrics.go b/internal/cvss/metrics.go
new file mode 100644
index 00000000..2d1a0fcc
--- /dev/null
+++ b/internal/cvss/metrics.go
@@ -0,0 +1,75 @@
+package cvss
+
+import (
+	"fmt"
+	"math"
+	"strings"
+
+	gocvss20 "github.com/pandatix/go-cvss/20"
+	gocvss30 "github.com/pandatix/go-cvss/30"
+	gocvss31 "github.com/pandatix/go-cvss/31"
+	gocvss40 "github.com/pandatix/go-cvss/40"
+
+	"github.com/anchore/grype/grype/vulnerability"
+)
+
+func ParseMetricsFromVector(vector string) (*vulnerability.CvssMetrics, error) {
+	switch {
+	case strings.HasPrefix(vector, "CVSS:3.0"):
+		cvss, err := gocvss30.ParseVector(vector)
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse CVSS v3 vector: %w", err)
+		}
+		ex := roundScore(cvss.Exploitability())
+		im := roundScore(cvss.Impact())
+		return &vulnerability.CvssMetrics{
+			BaseScore:           roundScore(cvss.BaseScore()),
+			ExploitabilityScore: &ex,
+			ImpactScore:         &im,
+		}, nil
+	case strings.HasPrefix(vector, "CVSS:3.1"):
+		cvss, err := gocvss31.ParseVector(vector)
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse CVSS v3.1 vector: %w", err)
+		}
+		ex := roundScore(cvss.Exploitability())
+		im := roundScore(cvss.Impact())
+		return &vulnerability.CvssMetrics{
+			BaseScore:           roundScore(cvss.BaseScore()),
+			ExploitabilityScore: &ex,
+			ImpactScore:         &im,
+		}, nil
+	case strings.HasPrefix(vector, "CVSS:4.0"):
+		cvss, err := gocvss40.ParseVector(vector)
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse CVSS v4.0 vector: %w", err)
+		}
+		// there are no exploitability and impact scores in CVSS v4.0
+		return &vulnerability.CvssMetrics{
+			BaseScore: roundScore(cvss.Score()),
+		}, nil
+	default:
+		// should be CVSS v2.0 or is invalid
+		cvss, err := gocvss20.ParseVector(vector)
+		if err != nil {
+			return nil, fmt.Errorf("unable to parse CVSS v2 vector: %w", err)
+		}
+		ex := roundScore(cvss.Exploitability())
+		im := roundScore(cvss.Impact())
+		return &vulnerability.CvssMetrics{
+			BaseScore:           roundScore(cvss.BaseScore()),
+			ExploitabilityScore: &ex,
+			ImpactScore:         &im,
+		}, nil
+	}
+}
+
+// roundScore rounds the score to the nearest tenth based on first.org rounding rules
+// see https://www.first.org/cvss/v3.1/specification-document#Appendix-A---Floating-Point-Rounding
+func roundScore(score float64) float64 {
+	intInput := int(math.Round(score * 100000))
+	if intInput%10000 == 0 {
+		return float64(intInput) / 100000.0
+	}
+	return (math.Floor(float64(intInput)/10000.0) + 1) / 10.0
+}
diff --git a/internal/format/writer.go b/internal/format/writer.go
index a55351a7..03e65652 100644
--- a/internal/format/writer.go
+++ b/internal/format/writer.go
@@ -9,8 +9,8 @@ import (
 	"strings"
 
 	"github.com/hashicorp/go-multierror"
-	"github.com/mitchellh/go-homedir"
 
+	"github.com/anchore/go-homedir"
 	"github.com/anchore/grype/grype/presenter/models"
 	"github.com/anchore/grype/internal/bus"
 	"github.com/anchore/grype/internal/log"


Test patch:
diff --git a/cmd/grype/cli/commands/db_list_test.go b/cmd/grype/cli/commands/db_list_test.go
index 30d24194..3492f8e4 100644
--- a/cmd/grype/cli/commands/db_list_test.go
+++ b/cmd/grype/cli/commands/db_list_test.go
@@ -66,7 +66,6 @@ func Test_ListingUserAgent(t *testing.T) {
 }
 
 func TestPresentDBList(t *testing.T) {
-	baseURL := "http://localhost:8000/latest.json"
 	latestDoc := &distribution.LatestDocument{
 		Status: "active",
 		Archive: distribution.Archive{
@@ -82,20 +81,39 @@ func TestPresentDBList(t *testing.T) {
 	tests := []struct {
 		name         string
 		format       string
+		baseURL      string
+		archiveURL   string
 		latest       *distribution.LatestDocument
 		expectedText string
 		expectedErr  require.ErrorAssertionFunc
 	}{
 		{
-			name:   "valid text format",
-			format: textOutputFormat,
-			latest: latestDoc,
+			name:       "valid text format",
+			format:     textOutputFormat,
+			latest:     latestDoc,
+			baseURL:    "http://localhost:8000/latest.json",
+			archiveURL: "http://localhost:8000/vulnerability-db_v6.0.0_2024-11-25T01:31:56Z_1732718597.tar.zst",
 			expectedText: `Status:   active
 Schema:   v6.0.0
 Built:    2024-11-27T14:43:17Z
 Listing:  http://localhost:8000/latest.json
 DB URL:   http://localhost:8000/vulnerability-db_v6.0.0_2024-11-25T01:31:56Z_1732718597.tar.zst
 Checksum: sha256:16bcb6551c748056f752f299fcdb4fa50fe61589d086be3889e670261ff21ca4
+`,
+			expectedErr: require.NoError,
+		},
+		{
+			name:       "complete default values",
+			format:     textOutputFormat,
+			latest:     latestDoc,
+			baseURL:    "https://grype.anchore.io/databases",
+			archiveURL: "https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2024-11-25T01:31:56Z_1732718597.tar.zst",
+			expectedText: `Status:   active
+Schema:   v6.0.0
+Built:    2024-11-27T14:43:17Z
+Listing:  https://grype.anchore.io/databases/v6/latest.json
+DB URL:   https://grype.anchore.io/databases/v6/vulnerability-db_v6.0.0_2024-11-25T01:31:56Z_1732718597.tar.zst
+Checksum: sha256:16bcb6551c748056f752f299fcdb4fa50fe61589d086be3889e670261ff21ca4
 `,
 			expectedErr: require.NoError,
 		},
@@ -133,7 +151,7 @@ Checksum: sha256:16bcb6551c748056f752f299fcdb4fa50fe61589d086be3889e670261ff21ca
 		t.Run(tt.name, func(t *testing.T) {
 			writer := &bytes.Buffer{}
 
-			err := presentDBList(tt.format, baseURL, writer, tt.latest)
+			err := presentDBList(tt.format, tt.archiveURL, tt.baseURL, writer, tt.latest)
 			if tt.expectedErr == nil {
 				tt.expectedErr = require.NoError
 			}
diff --git a/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities_test.go b/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities_test.go
index 285addb5..05366113 100644
--- a/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities_test.go
+++ b/cmd/grype/cli/commands/internal/dbsearch/vulnerabilities_test.go
@@ -113,7 +113,20 @@ func TestVulnerabilities(t *testing.T) {
 			PublishedDate: ptr(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)),
 			ModifiedDate:  ptr(time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)),
 			Provider:      &v6.Provider{ID: "provider1"},
-			BlobValue:     &v6.VulnerabilityBlob{Description: "Test description"},
+			BlobValue: &v6.VulnerabilityBlob{
+				Description: "Test description",
+				Severities: []v6.Severity{
+					{
+						Scheme: v6.SeveritySchemeCVSS,
+						Value: v6.CVSSSeverity{
+							Vector:  "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+							Version: "3.1",
+						},
+						Source: "nvd",
+						Rank:   1,
+					},
+				},
+			},
 		},
 	}, nil)
 
@@ -156,12 +169,30 @@ func TestVulnerabilities(t *testing.T) {
 	expected := []Vulnerability{
 		{
 			VulnerabilityInfo: VulnerabilityInfo{
-				VulnerabilityBlob: v6.VulnerabilityBlob{Description: "Test description"},
-				Provider:          "provider1",
-				Status:            "active",
-				PublishedDate:     ptr(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)),
-				ModifiedDate:      ptr(time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)),
-				WithdrawnDate:     nil,
+				VulnerabilityBlob: v6.VulnerabilityBlob{
+					Description: "Test description",
+					Severities: []v6.Severity{
+						{
+							Scheme: "CVSS",
+							Value: CVSSSeverity{
+								Vector:  "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+								Version: "3.1",
+								Metrics: CvssMetrics{
+									BaseScore:           7.5,
+									ExploitabilityScore: ptr(3.9),
+									ImpactScore:         ptr(3.6),
+								},
+							},
+							Source: "nvd",
+							Rank:   1,
+						},
+					},
+				},
+				Provider:      "provider1",
+				Status:        "active",
+				PublishedDate: ptr(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)),
+				ModifiedDate:  ptr(time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC)),
+				WithdrawnDate: nil,
 				KnownExploited: []KnownExploited{
 					{
 						CVE:                        "CVE-1234-5678",
diff --git a/grype/db/v5/namespace/index_test.go b/grype/db/v5/namespace/index_test.go
deleted file mode 100644
index 248c02db..00000000
--- a/grype/db/v5/namespace/index_test.go
+++ /dev/null
@@ -1,404 +0,0 @@
-package namespace
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-
-	"github.com/anchore/grype/grype/db/v5/namespace/cpe"
-	"github.com/anchore/grype/grype/db/v5/namespace/distro"
-	"github.com/anchore/grype/grype/db/v5/namespace/language"
-	osDistro "github.com/anchore/grype/grype/distro"
-	syftPkg "github.com/anchore/syft/syft/pkg"
-)
-
-func TestFromStringSlice(t *testing.T) {
-	tests := []struct {
-		namespaces  []string
-		byLanguage  map[syftPkg.Language][]*language.Namespace
-		byDistroKey map[string][]*distro.Namespace
-		cpe         []*cpe.Namespace
-	}{
-		{
-			namespaces: []string{
-				"github:language:python",
-				"github:language:python:conda",
-				"debian:distro:debian:8",
-				"alpine:distro:alpine:3.15",
-				"alpine:distro:alpine:3.16",
-				"msrc:distro:windows:12345",
-				"nvd:cpe",
-				"github:language:ruby",
-				"abc.xyz:language:ruby",
-				"github:language:rust",
-				"something:language:rust",
-				"1234.4567:language:unknown",
-				"---:cpe",
-				"another-provider:distro:alpine:3.15",
-				"another-provider:distro:alpine:3.16",
-			},
-			byLanguage: map[syftPkg.Language][]*language.Namespace{
-				syftPkg.Python: {
-					language.NewNamespace("github", syftPkg.Python, ""),
-					language.NewNamespace("github", syftPkg.Python, syftPkg.Type("conda")),
-				},
-				syftPkg.Ruby: {
-					language.NewNamespace("github", syftPkg.Ruby, ""),
-					language.NewNamespace("abc.xyz", syftPkg.Ruby, ""),
-				},
-				syftPkg.Rust: {
-					language.NewNamespace("github", syftPkg.Rust, ""),
-					language.NewNamespace("something", syftPkg.Rust, ""),
-				},
-				syftPkg.Language("unknown"): {
-					language.NewNamespace("1234.4567", syftPkg.Language("unknown"), ""),
-				},
-			},
-			byDistroKey: map[string][]*distro.Namespace{
-				"debian:8": {
-					distro.NewNamespace("debian", osDistro.Debian, "8"),
-				},
-				"alpine:3.15": {
-					distro.NewNamespace("alpine", osDistro.Alpine, "3.15"),
-					distro.NewNamespace("another-provider", osDistro.Alpine, "3.15"),
-				},
-				"alpine:3.16": {
-					distro.NewNamespace("alpine", osDistro.Alpine, "3.16"),
-					distro.NewNamespace("another-provider", osDistro.Alpine, "3.16"),
-				},
-				"windows:12345": {
-					distro.NewNamespace("msrc", osDistro.Windows, "12345"),
-				},
-			},
-			cpe: []*cpe.Namespace{
-				cpe.NewNamespace("---"),
-				cpe.NewNamespace("nvd"),
-			},
-		},
-	}
-
-	for _, test := range tests {
-		result, _ := FromStrings(test.namespaces)
-		assert.Len(t, result.all, len(test.namespaces))
-
-		for l, elems := range result.byLanguage {
-			assert.Contains(t, test.byLanguage, l)
-			assert.ElementsMatch(t, elems, test.byLanguage[l])
-		}
-
-		for d, elems := range result.byDistroKey {
-			assert.Contains(t, test.byDistroKey, d)
-			assert.ElementsMatch(t, elems, test.byDistroKey[d])
-		}
-
-		assert.ElementsMatch(t, result.cpe, test.cpe)
-	}
-}
-
-func TestIndex_CPENamespaces(t *testing.T) {
-	tests := []struct {
-		namespaces []string
-		cpe        []*cpe.Namespace
-	}{
-		{
-			namespaces: []string{"nvd:cpe", "another-source:cpe", "x:distro:y:10"},
-			cpe: []*cpe.Namespace{
-				cpe.NewNamespace("nvd"),
-				cpe.NewNamespace("another-source"),
-			},
-		},
-	}
-
-	for _, test := range tests {
-		result, _ := FromStrings(test.namespaces)
-		assert.Len(t, result.all, len(test.namespaces))
-		assert.ElementsMatch(t, result.CPENamespaces(), test.cpe)
-	}
-}
-
-func newDistro(t *testing.T, dt osDistro.Type, v string, idLikes []string) *osDistro.Distro {
-	d, err := osDistro.New(dt, v, idLikes...)
-	assert.NoError(t, err)
-	return d
-}
-
-func TestIndex_NamespacesForDistro(t *testing.T) {
-	namespaceIndex, err := FromStrings([]string{
-		"alpine:distro:alpine:2.17",
-		"alpine:distro:alpine:3.15",
-		"alpine:distro:alpine:3.16",
-		"alpine:distro:alpine:4.13",
-		"alpine:distro:alpine:edge",
-		"debian:distro:debian:8",
-		"debian:distro:debian:unstable",
-		"amazon:distro:amazonlinux:2",
-		"amazon:distro:amazonlinux:2022",
-		"abc.xyz:distro:unknown:123.456",
-		"redhat:distro:redhat:8",
-		"redhat:distro:redhat:9",
-		"other-provider:distro:debian:8",
-		"other-provider:distro:redhat:9",
-		"suse:distro:sles:12.5",
-		"mariner:distro:mariner:2.0",
-		"mariner:distro:azurelinux:3.0",
-		"msrc:distro:windows:471816",
-		"ubuntu:distro:ubuntu:18.04",
-		"ubuntu:distro:ubuntu:18.10",
-		"ubuntu:distro:ubuntu:20.04",
-		"ubuntu:distro:ubuntu:20.10",
-		"oracle:distro:oraclelinux:8",
-		"wolfi:distro:wolfi:rolling",
-		"chainguard:distro:chainguard:rolling",
-		"archlinux:distro:archlinux:rolling",
-	})
-
-	assert.NoError(t, err)
-
-	tests := []struct {
-		name       string
-		distro     *osDistro.Distro
-		namespaces []*distro.Namespace
-	}{
-		{
-			name:   "alpine patch version matches minor version namespace",
-			distro: newDistro(t, osDistro.Alpine, "3.15.4", []string{"alpine"}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "3.15"),
-			},
-		},
-		{
-			name:   "alpine missing patch version matches with minor version",
-			distro: newDistro(t, osDistro.Alpine, "3.16", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "3.16"),
-			},
-		},
-		{
-			name:   "alpine missing minor version uses latest minor version",
-			distro: newDistro(t, osDistro.Alpine, "3", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "3.16"),
-			},
-		},
-		{
-			name:   "ubuntu missing minor version uses latest minor version",
-			distro: newDistro(t, osDistro.Ubuntu, "18", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("ubuntu", osDistro.Ubuntu, "18.10"),
-			},
-		},
-		{
-			name:   "alpine rc version with no patch should match edge",
-			distro: newDistro(t, osDistro.Alpine, "3.16.4-r4", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "edge"),
-			},
-		},
-
-		{
-			name:   "alpine edge version matches edge namespace",
-			distro: &osDistro.Distro{Type: osDistro.Alpine, Version: nil, RawVersion: "3.17.1_alpha20221002", IDLike: []string{"alpine"}},
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "edge"),
-			},
-		},
-		{
-			name:   "alpine raw version matches edge with - character",
-			distro: &osDistro.Distro{Type: osDistro.Alpine, Version: nil, RawVersion: "3.17.1-alpha20221002", IDLike: []string{"alpine"}},
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "edge"),
-			},
-		},
-		{
-			name:   "alpine raw version matches edge with - character no sha",
-			distro: newDistro(t, osDistro.Alpine, "3.17.1-alpha", []string{"alpine"}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "edge"),
-			},
-		},
-		{
-			name: "alpine raw version matches edge with _ character no sha",
-			// we don't create a newDistro from this since parsing the version fails
-			distro: &osDistro.Distro{Type: osDistro.Alpine, Version: nil, RawVersion: "3.17.1_alpha", IDLike: []string{"alpine"}},
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "edge"),
-			},
-		},
-		{
-			name:   "alpine malformed version matches with closest",
-			distro: newDistro(t, osDistro.Alpine, "3.16.4.5", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("alpine", osDistro.Alpine, "3.16"),
-			},
-		},
-		{
-			name:   "Debian minor version matches debian and other-provider namespaces",
-			distro: newDistro(t, osDistro.Debian, "8.5", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("debian", osDistro.Debian, "8"),
-				distro.NewNamespace("other-provider", osDistro.Debian, "8"),
-			},
-		},
-		{
-			name:   "Redhat minor version matches redhat and other-provider namespaces",
-			distro: newDistro(t, osDistro.RedHat, "9.5", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("redhat", osDistro.RedHat, "9"),
-				distro.NewNamespace("other-provider", osDistro.RedHat, "9"),
-			},
-		},
-		{
-			name:   "Centos minor version matches redhat and other-provider namespaces",
-			distro: newDistro(t, osDistro.CentOS, "9.5", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("redhat", osDistro.RedHat, "9"),
-				distro.NewNamespace("other-provider", osDistro.RedHat, "9"),
-			},
-		},
-		{
-			name:   "Alma Linux minor version matches redhat and other-provider namespaces",
-			distro: newDistro(t, osDistro.AlmaLinux, "9.5", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("redhat", osDistro.RedHat, "9"),
-				distro.NewNamespace("other-provider", osDistro.RedHat, "9"),
-			},
-		},
-		{
-			name:   "Rocky Linux minor version matches redhat and other-provider namespaces",
-			distro: newDistro(t, osDistro.RockyLinux, "9.5", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("redhat", osDistro.RedHat, "9"),
-				distro.NewNamespace("other-provider", osDistro.RedHat, "9"),
-			},
-		},
-		{
-			name:   "SLES minor version matches suse namespace",
-			distro: newDistro(t, osDistro.SLES, "12.5", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("suse", osDistro.SLES, "12.5"),
-			},
-		},
-		{
-			name:   "Windows version object matches msrc namespace with exact version",
-			distro: newDistro(t, osDistro.Windows, "471816", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("msrc", osDistro.Windows, "471816"),
-			},
-		},
-		{
-			name:   "Ubuntu minor semvar matches ubuntu namespace with exact version",
-			distro: newDistro(t, osDistro.Ubuntu, "18.04", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("ubuntu", osDistro.Ubuntu, "18.04"),
-			},
-		},
-		{
-			name:       "Fedora minor semvar will not match a namespace",
-			distro:     newDistro(t, osDistro.Fedora, "31.4", []string{}),
-			namespaces: nil,
-		},
-		{
-			name:   "Amazon Linux Major semvar matches amazon namespace with exact version",
-			distro: newDistro(t, osDistro.AmazonLinux, "2", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("amazon", osDistro.AmazonLinux, "2"),
-			},
-		},
-		{
-			name:   "Amazon Linux year version matches amazon namespace with exact uear",
-			distro: newDistro(t, osDistro.AmazonLinux, "2022", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("amazon", osDistro.AmazonLinux, "2022"),
-			},
-		},
-		{
-			name:       "Mariner minor semvar matches no namespace",
-			distro:     newDistro(t, osDistro.Mariner, "20.1", []string{}),
-			namespaces: nil,
-		},
-		{
-			name:   "Mariner 2.0 matches mariner namespace",
-			distro: newDistro(t, osDistro.Mariner, "2.0", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("mariner", "mariner", "2.0"),
-			},
-		},
-		{
-			name:   "azurelinux 3 is matched by mariner 3 namespace",
-			distro: newDistro(t, osDistro.Azure, "3.0", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("mariner", osDistro.Azure, "3.0"),
-			},
-		},
-		{
-			name:   "Oracle Linux Major semvar matches oracle namespace with exact version",
-			distro: newDistro(t, osDistro.OracleLinux, "8", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("oracle", osDistro.OracleLinux, "8"),
-			},
-		},
-		{
-
-			name:   "Arch Linux matches archlinux rolling namespace",
-			distro: newDistro(t, osDistro.ArchLinux, "", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("archlinux", osDistro.ArchLinux, "rolling"),
-			},
-		},
-		{
-
-			name:   "Wolfi matches wolfi rolling namespace",
-			distro: newDistro(t, osDistro.Wolfi, "20221011", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("wolfi", osDistro.Wolfi, "rolling"),
-			},
-		},
-		{
-
-			name:   "Chainguard matches chainguard rolling namespace",
-			distro: newDistro(t, osDistro.Chainguard, "20230214", []string{}),
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("chainguard", osDistro.Chainguard, "rolling"),
-			},
-		},
-		{
-
-			name:       "Gentoo doesn't match any namespace since the gentoo rolling namespace doesn't exist in index",
-			distro:     newDistro(t, osDistro.Gentoo, "", []string{}),
-			namespaces: nil,
-		},
-		{
-			name:       "Open Suse Leap semvar matches no namespace",
-			distro:     newDistro(t, osDistro.OpenSuseLeap, "100", []string{}),
-			namespaces: nil,
-		},
-		{
-			name:       "Photon minor semvar no namespace",
-			distro:     newDistro(t, osDistro.Photon, "20.1", []string{}),
-			namespaces: nil,
-		},
-		{
-			name:       "Busybox minor semvar matches no namespace",
-			distro:     newDistro(t, osDistro.Busybox, "20.1", []string{}),
-			namespaces: nil,
-		},
-		{
-			name: "debian unstable",
-			distro: &osDistro.Distro{
-				Type:       osDistro.Debian,
-				RawVersion: "unstable",
-				Version:    nil,
-			},
-			namespaces: []*distro.Namespace{
-				distro.NewNamespace("debian", osDistro.Debian, "unstable"),
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			namespaces := namespaceIndex.NamespacesForDistro(test.distro)
-			assert.ElementsMatch(t, test.namespaces, namespaces)
-		})
-	}
-}
diff --git a/grype/db/v6/affected_package_store_test.go b/grype/db/v6/affected_package_store_test.go
index c8f1d8a2..3f7ede29 100644
--- a/grype/db/v6/affected_package_store_test.go
+++ b/grype/db/v6/affected_package_store_test.go
@@ -992,7 +992,7 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) {
 	debian10 := &OperatingSystem{Name: "debian", ReleaseID: "debian", MajorVersion: "10"}
 	alpine318 := &OperatingSystem{Name: "alpine", ReleaseID: "alpine", MajorVersion: "3", MinorVersion: "18"}
 	alpineEdge := &OperatingSystem{Name: "alpine", ReleaseID: "alpine", LabelVersion: "edge"}
-	debianTrixie := &OperatingSystem{Name: "debian", ReleaseID: "debian", LabelVersion: "trixie"}
+	debianUnstable := &OperatingSystem{Name: "debian", ReleaseID: "debian", LabelVersion: "unstable"}
 	debian7 := &OperatingSystem{Name: "debian", ReleaseID: "debian", MajorVersion: "7", LabelVersion: "wheezy"}
 	wolfi := &OperatingSystem{Name: "wolfi", ReleaseID: "wolfi", MajorVersion: "20230201"}
 	arch := &OperatingSystem{Name: "arch", ReleaseID: "arch", MajorVersion: "20241110", MinorVersion: "0"}
@@ -1010,7 +1010,7 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) {
 		debian10,
 		alpine318,
 		alpineEdge,
-		debianTrixie,
+		debianUnstable,
 		debian7,
 		wolfi,
 		arch,
@@ -1130,7 +1130,7 @@ func TestAffectedPackageStore_ResolveDistro(t *testing.T) {
 				MajorVersion: "13",
 				LabelVersion: "trixie",
 			},
-			expected: []OperatingSystem{*debianTrixie},
+			expected: []OperatingSystem{*debianUnstable},
 		},
 		{
 			name: "debian by codename",
diff --git a/grype/db/v6/severity_test.go b/grype/db/v6/severity_test.go
index e8b3b41f..3b943d0f 100644
--- a/grype/db/v6/severity_test.go
+++ b/grype/db/v6/severity_test.go
@@ -126,107 +126,6 @@ func TestExtractSeverity(t *testing.T) {
 	}
 }
 
-func TestParseCVSS(t *testing.T) {
-	tests := []struct {
-		name            string
-		vector          string
-		expectedMetrics *vulnerability.CvssMetrics
-		wantErr         require.ErrorAssertionFunc
-	}{
-		{
-			name:   "valid CVSS 2.0",
-			vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P",
-			expectedMetrics: &vulnerability.CvssMetrics{
-				BaseScore:           7.5,
-				ExploitabilityScore: ptr(10.0),
-				ImpactScore:         ptr(6.5),
-			},
-		},
-		{
-			name:   "valid CVSS 3.0",
-			vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
-			expectedMetrics: &vulnerability.CvssMetrics{
-				BaseScore:           9.8,
-				ExploitabilityScore: ptr(3.9),
-				ImpactScore:         ptr(5.9),
-			},
-		},
-		{
-			name:   "valid CVSS 3.1",
-			vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
-			expectedMetrics: &vulnerability.CvssMetrics{
-				BaseScore:           9.8,
-				ExploitabilityScore: ptr(3.9),
-				ImpactScore:         ptr(5.9),
-			},
-		},
-		{
-			name:   "valid CVSS 4.0",
-			vector: "CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:N/VI:H/VA:L/SC:L/SI:H/SA:L/MAC:L/MAT:P/MPR:N/S:N/R:A/RE:L/U:Clear",
-			expectedMetrics: &vulnerability.CvssMetrics{
-				BaseScore: 9.1,
-			},
-		},
-		{
-			name:    "invalid CVSS 2.0",
-			vector:  "AV:N/AC:INVALID",
-			wantErr: require.Error,
-		},
-		{
-			name:    "invalid CVSS 3.0",
-			vector:  "CVSS:3.0/AV:INVALID",
-			wantErr: require.Error,
-		},
-		{
-			name:    "invalid CVSS 3.1",
-			vector:  "CVSS:3.1/AV:INVALID",
-			wantErr: require.Error,
-		},
-		{
-			name:    "invalid CVSS 4.0",
-			vector:  "CVSS:4.0/AV:INVALID",
-			wantErr: require.Error,
-		},
-		{
-			name:    "empty vector",
-			vector:  "",
-			wantErr: require.Error,
-		},
-		{
-			name:    "malformed vector",
-			vector:  "INVALID:VECTOR",
-			wantErr: require.Error,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if tt.wantErr == nil {
-				tt.wantErr = require.NoError
-			}
-			result, err := parseCVSS(tt.vector)
-			tt.wantErr(t, err)
-			if err != nil {
-				assert.Nil(t, result)
-				return
-			}
-
-			require.NotNil(t, result)
-			assert.Equal(t, tt.expectedMetrics.BaseScore, result.BaseScore, "given vector: %s", tt.vector)
-
-			if tt.expectedMetrics.ExploitabilityScore != nil {
-				require.NotNil(t, result.ExploitabilityScore)
-				assert.Equal(t, *tt.expectedMetrics.ExploitabilityScore, *result.ExploitabilityScore, "given vector: %s", tt.vector)
-			}
-
-			if tt.expectedMetrics.ImpactScore != nil {
-				require.NotNil(t, result.ImpactScore)
-				assert.Equal(t, *tt.expectedMetrics.ImpactScore, *result.ImpactScore, "given vector: %s", tt.vector)
-			}
-		})
-	}
-}
-
 func TestExtractSeverities(t *testing.T) {
 	tests := []struct {
 		name          string
diff --git a/grype/distro/distro_test.go b/grype/distro/distro_test.go
index c18be758..e399b7d2 100644
--- a/grype/distro/distro_test.go
+++ b/grype/distro/distro_test.go
@@ -3,6 +3,8 @@ package distro
 import (
 	"testing"
 
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
@@ -14,12 +16,12 @@ import (
 
 func Test_NewDistroFromRelease(t *testing.T) {
 	tests := []struct {
-		name               string
-		release            linux.Release
-		expectedVersion    string
-		expectedRawVersion string
-		expectedType       Type
-		expectErr          bool
+		name      string
+		release   linux.Release
+		expected  *Distro
+		minor     string
+		major     string
+		expectErr require.ErrorAssertionFunc
 	}{
 		{
 			name: "go case: derive version from version-id",
@@ -28,9 +30,12 @@ func Test_NewDistroFromRelease(t *testing.T) {
 				VersionID: "8",
 				Version:   "7",
 			},
-			expectedType:       CentOS,
-			expectedRawVersion: "8",
-			expectedVersion:    "8.0.0",
+			expected: &Distro{
+				Type:    CentOS,
+				Version: "8",
+			},
+			major: "8",
+			minor: "",
 		},
 		{
 			name: "fallback to release name when release id is missing",
@@ -38,9 +43,12 @@ func Test_NewDistroFromRelease(t *testing.T) {
 				Name:      "windows",
 				VersionID: "8",
 			},
-			expectedType:       Windows,
-			expectedRawVersion: "8",
-			expectedVersion:    "8.0.0",
+			expected: &Distro{
+				Type:    Windows,
+				Version: "8",
+			},
+			major: "8",
+			minor: "",
 		},
 		{
 			name: "fallback to version when version-id missing",
@@ -48,16 +56,22 @@ func Test_NewDistroFromRelease(t *testing.T) {
 				ID:      "centos",
 				Version: "8",
 			},
-			expectedType:       CentOS,
-			expectedRawVersion: "8",
-			expectedVersion:    "8.0.0",
+			expected: &Distro{
+				Type:    CentOS,
+				Version: "8",
+			},
+			major: "8",
+			minor: "",
 		},
 		{
-			name: "missing version results in error",
+			// this enables matching on multiple OS versions at once
+			name: "missing version or label version is allowed",
 			release: linux.Release{
 				ID: "centos",
 			},
-			expectedType: CentOS,
+			expected: &Distro{
+				Type: CentOS,
+			},
 		},
 		{
 			name: "bogus distro type results in error",
@@ -65,7 +79,7 @@ func Test_NewDistroFromRelease(t *testing.T) {
 				ID:        "bogosity",
 				VersionID: "8",
 			},
-			expectErr: true,
+			expectErr: require.Error,
 		},
 		{
 			// syft -o json debian:testing | jq .distro
@@ -78,9 +92,12 @@ func Test_NewDistroFromRelease(t *testing.T) {
 				VersionCodename: "trixie",
 				Name:            "Debian GNU/Linux",
 			},
-			expectedType:       Debian,
-			expectedRawVersion: "unstable",
-			expectedVersion:    "",
+			expected: &Distro{
+				Type:     Debian,
+				Codename: "trixie",
+			},
+			major: "",
+			minor: "",
 		},
 		{
 			name: "azure linux 3",
@@ -89,176 +106,194 @@ func Test_NewDistroFromRelease(t *testing.T) {
 				Version:   "3.0.20240417",
 				VersionID: "3.0",
 			},
-			expectedType:       Azure,
-			expectedRawVersion: "3.0",
+			expected: &Distro{
+				Type:    Azure,
+				Version: "3.0",
+			},
+			major: "3",
+			minor: "0",
 		},
 	}
 
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			d, err := NewFromRelease(test.release)
-			if test.expectErr {
-				require.Error(t, err)
-				return
-			} else {
-				require.NoError(t, err)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.expectErr == nil {
+				tt.expectErr = require.NoError
 			}
 
-			assert.Equal(t, test.expectedType, d.Type)
-			if test.expectedVersion != "" {
-				assert.Equal(t, test.expectedVersion, d.Version.String())
+			distro, err := NewFromRelease(tt.release)
+			tt.expectErr(t, err)
+			if err != nil {
+				return
 			}
-			if test.expectedRawVersion != "" {
-				assert.Equal(t, test.expectedRawVersion, d.FullVersion())
+
+			if d := cmp.Diff(tt.expected, distro, cmpopts.IgnoreUnexported(Distro{})); d != "" {
+				t.Errorf("unexpected result: %s", d)
 			}
+			assert.Equal(t, tt.major, distro.MajorVersion(), "unexpected major version")
+			assert.Equal(t, tt.minor, distro.MinorVersion(), "unexpected minor version")
 		})
 	}
 
 }
 
 func Test_NewDistroFromRelease_Coverage(t *testing.T) {
+	observedDistros := stringutil.NewStringSet()
+	definedDistros := stringutil.NewStringSet()
+
+	for _, distroType := range All {
+		definedDistros.Add(string(distroType))
+	}
+
+	// Somewhat cheating with Windows. There is no support for detecting/parsing a Windows OS, so it is not
+	// possible to comply with this test unless it is added manually to the "observed distros"
+	definedDistros.Remove(string(Windows))
+
 	tests := []struct {
-		fixture string
-		Type    Type
-		Version string
+		Name         string
+		Type         Type
+		Version      string
+		LabelVersion string
 	}{
 		{
-			fixture: "test-fixtures/os/alpine",
+			Name:    "test-fixtures/os/alpine",
 			Type:    Alpine,
 			Version: "3.11.6",
 		},
 		{
-			fixture: "test-fixtures/os/amazon",
+			Name:    "test-fixtures/os/alpine-edge",
+			Type:    Alpine,
+			Version: "3.22.0_alpha20250108",
+		},
+		{
+			Name:    "test-fixtures/os/amazon",
 			Type:    AmazonLinux,
-			Version: "2.0.0",
+			Version: "2",
 		},
 		{
-			fixture: "test-fixtures/os/busybox",
+			Name:    "test-fixtures/os/busybox",
 			Type:    Busybox,
 			Version: "1.31.1",
 		},
 		{
-			fixture: "test-fixtures/os/centos",
+			Name:    "test-fixtures/os/centos",
 			Type:    CentOS,
-			Version: "8.0.0",
+			Version: "8",
 		},
 		{
-			fixture: "test-fixtures/os/debian",
+			Name:    "test-fixtures/os/debian",
 			Type:    Debian,
-			Version: "8.0.0",
+			Version: "8",
 		},
 		{
-			fixture: "test-fixtures/os/fedora",
+			Name:         "test-fixtures/os/debian-sid",
+			Type:         Debian,
+			LabelVersion: "trixie",
+		},
+		{
+			Name:    "test-fixtures/os/fedora",
 			Type:    Fedora,
-			Version: "31.0.0",
+			Version: "31",
 		},
 		{
-			fixture: "test-fixtures/os/redhat",
+			Name:    "test-fixtures/os/redhat",
 			Type:    RedHat,
-			Version: "7.3.0",
+			Version: "7.3",
 		},
 		{
-			fixture: "test-fixtures/os/ubuntu",
-			Type:    Ubuntu,
-			Version: "20.4.0",
+			Name:         "test-fixtures/os/ubuntu",
+			Type:         Ubuntu,
+			Version:      "20.04",
+			LabelVersion: "focal",
 		},
 		{
-			fixture: "test-fixtures/os/oraclelinux",
+			Name:    "test-fixtures/os/oraclelinux",
 			Type:    OracleLinux,
-			Version: "8.3.0",
+			Version: "8.3",
 		},
 		{
-			fixture: "test-fixtures/os/custom",
+			Name:    "test-fixtures/os/custom",
 			Type:    RedHat,
-			Version: "8.0.0",
+			Version: "8",
 		},
 		{
-			fixture: "test-fixtures/os/opensuse-leap",
+			Name:    "test-fixtures/os/opensuse-leap",
 			Type:    OpenSuseLeap,
-			Version: "15.2.0",
+			Version: "15.2",
 		},
 		{
-			fixture: "test-fixtures/os/sles",
+			Name:    "test-fixtures/os/sles",
 			Type:    SLES,
-			Version: "15.2.0",
+			Version: "15.2",
 		},
 		{
-			fixture: "test-fixtures/os/photon",
+			Name:    "test-fixtures/os/photon",
 			Type:    Photon,
-			Version: "2.0.0",
+			Version: "2.0",
 		},
 		{
-			fixture: "test-fixtures/os/arch",
-			Type:    ArchLinux,
+			Name: "test-fixtures/os/arch",
+			Type: ArchLinux,
 		},
 		{
-			fixture: "test-fixtures/partial-fields/missing-id",
+			Name:    "test-fixtures/partial-fields/missing-id",
 			Type:    Debian,
-			Version: "8.0.0",
+			Version: "8",
 		},
 		{
-			fixture: "test-fixtures/partial-fields/unknown-id",
+			Name:    "test-fixtures/partial-fields/unknown-id",
 			Type:    Debian,
-			Version: "8.0.0",
+			Version: "8",
 		},
 		{
-			fixture: "test-fixtures/os/centos6",
+			Name:    "test-fixtures/os/centos6",
 			Type:    CentOS,
-			Version: "6.0.0",
+			Version: "6",
 		},
 		{
-			fixture: "test-fixtures/os/centos5",
+			Name:    "test-fixtures/os/centos5",
 			Type:    CentOS,
-			Version: "5.7.0",
+			Version: "5.7",
 		},
 		{
-			fixture: "test-fixtures/os/mariner",
+			Name:    "test-fixtures/os/mariner",
 			Type:    Mariner,
-			Version: "1.0.0",
+			Version: "1.0",
 		},
 		{
-			fixture: "test-fixtures/os/azurelinux",
+			Name:    "test-fixtures/os/azurelinux",
 			Type:    Azure,
-			Version: "3.0.0",
+			Version: "3.0",
 		},
 		{
-			fixture: "test-fixtures/os/rockylinux",
+			Name:    "test-fixtures/os/rockylinux",
 			Type:    RockyLinux,
-			Version: "8.4.0",
+			Version: "8.4",
 		},
 		{
-			fixture: "test-fixtures/os/almalinux",
+			Name:    "test-fixtures/os/almalinux",
 			Type:    AlmaLinux,
-			Version: "8.4.0",
+			Version: "8.4",
 		},
 		{
-			fixture: "test-fixtures/os/gentoo",
-			Type:    Gentoo,
+			Name: "test-fixtures/os/gentoo",
+			Type: Gentoo,
 		},
 		{
-			fixture: "test-fixtures/os/wolfi",
+			Name:    "test-fixtures/os/wolfi",
 			Type:    Wolfi,
+			Version: "20220914",
 		},
 		{
-			fixture: "test-fixtures/os/chainguard",
+			Name:    "test-fixtures/os/chainguard",
 			Type:    Chainguard,
+			Version: "20230214",
 		},
 	}
 
-	observedDistros := stringutil.NewStringSet()
-	definedDistros := stringutil.NewStringSet()
-
-	for _, distroType := range All {
-		definedDistros.Add(string(distroType))
-	}
-
-	// Somewhat cheating with Windows. There is no support for detecting/parsing a Windows OS, so it is not
-	// possible to comply with this test unless it is added manually to the "observed distros"
-	definedDistros.Remove(string(Windows))
-
-	for _, test := range tests {
-		t.Run(test.fixture, func(t *testing.T) {
-			s, err := directorysource.NewFromPath(test.fixture)
+	for _, tt := range tests {
+		t.Run(tt.Name, func(t *testing.T) {
+			s, err := directorysource.NewFromPath(tt.Name)
 			require.NoError(t, err)
 
 			resolver, err := s.FileResolver(source.SquashedScope)
@@ -274,10 +309,9 @@ func Test_NewDistroFromRelease_Coverage(t *testing.T) {
 
 			observedDistros.Add(d.Type.String())
 
-			assert.Equal(t, test.Type, d.Type)
-			if test.Version != "" {
-				assert.Equal(t, d.Version.String(), test.Version)
-			}
+			assert.Equal(t, tt.Type, d.Type, "unexpected distro type")
+			assert.Equal(t, tt.LabelVersion, d.Codename, "unexpected label version")
+			assert.Equal(t, tt.Version, d.Version, "unexpected version")
 		})
 	}
 
@@ -324,7 +358,7 @@ func TestDistro_FullVersion(t *testing.T) {
 				Version: test.version,
 			})
 			require.NoError(t, err)
-			assert.Equal(t, test.expected, d.FullVersion())
+			assert.Equal(t, test.expected, d.Version)
 		})
 	}
 
diff --git a/grype/distro/test-fixtures/os/alpine-edge/etc/os-release b/grype/distro/test-fixtures/os/alpine-edge/etc/os-release
new file mode 100644
index 00000000..c7133dc2
--- /dev/null
+++ b/grype/distro/test-fixtures/os/alpine-edge/etc/os-release
@@ -0,0 +1,6 @@
+NAME="Alpine Linux"
+ID=alpine
+VERSION_ID=3.22.0_alpha20250108
+PRETTY_NAME="Alpine Linux edge"
+HOME_URL="https://alpinelinux.org/"
+BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"
\ No newline at end of file
diff --git a/grype/distro/test-fixtures/os/debian-sid/usr/lib/os-release b/grype/distro/test-fixtures/os/debian-sid/usr/lib/os-release
new file mode 100644
index 00000000..c32b48d1
--- /dev/null
+++ b/grype/distro/test-fixtures/os/debian-sid/usr/lib/os-release
@@ -0,0 +1,7 @@
+PRETTY_NAME="Debian GNU/Linux trixie/sid"
+NAME="Debian GNU/Linux"
+VERSION_CODENAME=trixie
+ID=debian
+HOME_URL="https://www.debian.org/"
+SUPPORT_URL="https://www.debian.org/support"
+BUG_REPORT_URL="https://bugs.debian.org/"
diff --git a/grype/matcher/apk/matcher_test.go b/grype/matcher/apk/matcher_test.go
index 353c973d..4f068caf 100644
--- a/grype/matcher/apk/matcher_test.go
+++ b/grype/matcher/apk/matcher_test.go
@@ -61,7 +61,7 @@ func TestSecDBOnlyMatch(t *testing.T) {
 					SearchedBy: map[string]interface{}{
 						"distro": map[string]string{
 							"type":    d.Type.String(),
-							"version": d.RawVersion,
+							"version": d.Version,
 						},
 						"package": map[string]string{
 							"name":    "libvncserver",
@@ -140,7 +140,7 @@ func TestBothSecdbAndNvdMatches(t *testing.T) {
 					SearchedBy: map[string]interface{}{
 						"distro": map[string]string{
 							"type":    d.Type.String(),
-							"version": d.RawVersion,
+							"version": d.Version,
 						},
 						"package": map[string]string{
 							"name":    "libvncserver",
@@ -226,7 +226,7 @@ func TestBothSecdbAndNvdMatches_DifferentFixInfo(t *testing.T) {
 					SearchedBy: map[string]interface{}{
 						"distro": map[string]string{
 							"type":    d.Type.String(),
-							"version": d.RawVersion,
+							"version": d.Version,
 						},
 						"package": map[string]string{
 							"name":    "libvncserver",
@@ -306,7 +306,7 @@ func TestBothSecdbAndNvdMatches_DifferentPackageName(t *testing.T) {
 					SearchedBy: map[string]interface{}{
 						"distro": map[string]string{
 							"type":    d.Type.String(),
-							"version": d.RawVersion,
+							"version": d.Version,
 						},
 						"package": map[string]string{
 							"name":    "libvncserver",
@@ -659,7 +659,7 @@ func TestNVDMatchCanceledByOriginPackageInSecDB(t *testing.T) {
 	vp := mock.VulnerabilityProvider(nvdVuln, secDBVuln)
 
 	m := Matcher{}
-	d, err := distro.New(distro.Wolfi, "")
+	d, err := distro.New(distro.Wolfi, "", "")
 	if err != nil {
 		t.Fatalf("failed to create a new distro: %+v", err)
 	}
@@ -734,7 +734,7 @@ func TestDistroMatchBySourceIndirection(t *testing.T) {
 					SearchedBy: map[string]interface{}{
 						"distro": map[string]string{
 							"type":    d.Type.String(),
-							"version": d.RawVersion,
+							"version": d.Version,
 						},
 						"package": map[string]string{
 							"name":    "musl",
@@ -805,7 +805,7 @@ func TestSecDBMatchesStillCountedWithCpeErrors(t *testing.T) {
 					SearchedBy: map[string]interface{}{
 						"distro": map[string]string{
 							"type":    d.Type.String(),
-							"version": d.RawVersion,
+							"version": d.Version,
 						},
 						"package": map[string]string{
 							"name":    "musl",
@@ -903,6 +903,7 @@ func assertMatches(t *testing.T, expected, actual []match.Match) {
 	var opts = []cmp.Option{
 		cmpopts.IgnoreFields(vulnerability.Vulnerability{}, "Constraint"),
 		cmpopts.IgnoreFields(pkg.Package{}, "Locations"),
+		cmpopts.IgnoreUnexported(distro.Distro{}),
 	}
 
 	if diff := cmp.Diff(expected, actual, opts...); diff != "" {
diff --git a/grype/matcher/msrc/matcher_test.go b/grype/matcher/msrc/matcher_test.go
index 0696dc80..f42e5a0a 100644
--- a/grype/matcher/msrc/matcher_test.go
+++ b/grype/matcher/msrc/matcher_test.go
@@ -21,7 +21,7 @@ func TestMatches(t *testing.T) {
 
 	// TODO: it would be ideal to test against something that constructs the namespace based on grype-db
 	// and not break the adaption of grype-db
-	msrcNamespace := fmt.Sprintf("msrc:distro:windows:%s", d.RawVersion)
+	msrcNamespace := fmt.Sprintf("msrc:distro:windows:%s", d.Version)
 
 	vp := mock.VulnerabilityProvider([]vulnerability.Vulnerability{
 		{
@@ -29,7 +29,7 @@ func TestMatches(t *testing.T) {
 				ID:        "CVE-2016-3333",
 				Namespace: msrcNamespace,
 			},
-			PackageName: d.RawVersion,
+			PackageName: d.Version,
 			Constraint:  version.MustGetConstraint("3200970 || 878787 || base", version.KBFormat),
 		},
 		{
@@ -38,7 +38,7 @@ func TestMatches(t *testing.T) {
 				ID:        "CVE-2020-made-up",
 				Namespace: msrcNamespace,
 			},
-			PackageName: d.RawVersion,
+			PackageName: d.Version,
 			Constraint:  version.MustGetConstraint("778786 || 878787 || base", version.KBFormat),
 		},
 		// Does not match the product ID
@@ -61,7 +61,7 @@ func TestMatches(t *testing.T) {
 			name: "direct KB match",
 			pkg: pkg.Package{
 				ID:      pkg.ID(uuid.NewString()),
-				Name:    d.RawVersion,
+				Name:    d.Version,
 				Version: "3200970",
 				Type:    syftPkg.KbPkg,
 				Distro:  d,
@@ -74,7 +74,7 @@ func TestMatches(t *testing.T) {
 			name: "multiple direct KB match",
 			pkg: pkg.Package{
 				ID:      pkg.ID(uuid.NewString()),
-				Name:    d.RawVersion,
+				Name:    d.Version,
 				Version: "878787",
 				Type:    syftPkg.KbPkg,
 				Distro:  d,
@@ -88,7 +88,7 @@ func TestMatches(t *testing.T) {
 			name: "no KBs found",
 			pkg: pkg.Package{
 				ID:   pkg.ID(uuid.NewString()),
-				Name: d.RawVersion,
+				Name: d.Version,
 				// this is the assumed version if no KBs are found
 				Version: "base",
 				Type:    syftPkg.KbPkg,
diff --git a/grype/pkg/package_test.go b/grype/pkg/package_test.go
index 0c02886b..cd6192be 100644
--- a/grype/pkg/package_test.go
+++ b/grype/pkg/package_test.go
@@ -68,6 +68,36 @@ func TestNew(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "dpkg archive with source info",
+			syftPkg: syftPkg.Package{
+				Metadata: syftPkg.DpkgArchiveEntry{
+					Package:       "pkg-info",
+					Source:        "src-info",
+					Version:       "version-info",
+					SourceVersion: "src-version-info",
+					Architecture:  "arch-info",
+					Maintainer:    "maintainer-info",
+					InstalledSize: 10,
+					Files: []syftPkg.DpkgFileRecord{
+						{
+							Path: "path-info",
+							Digest: &file.Digest{
+								Algorithm: "algo-info",
+								Value:     "digest-info",
+							},
+							IsConfigFile: true,
+						},
+					},
+				},
+			},
+			upstreams: []UpstreamPackage{
+				{
+					Name:    "src-info",
+					Version: "src-version-info",
+				},
+			},
+		},
 		{
 			name: "rpm archive with source info",
 			syftPkg: syftPkg.Package{
diff --git a/grype/pkg/qualifier/rpmmodularity/qualifier_test.go b/grype/pkg/qualifier/rpmmodularity/qualifier_test.go
index f8b14f1a..994876bf 100644
--- a/grype/pkg/qualifier/rpmmodularity/qualifier_test.go
+++ b/grype/pkg/qualifier/rpmmodularity/qualifier_test.go
@@ -11,7 +11,7 @@ import (
 )
 
 func TestRpmModularity_Satisfied(t *testing.T) {
-	oracle, _ := distro.New(distro.OracleLinux, "8")
+	oracle, _ := distro.New(distro.OracleLinux, "8", "")
 
 	tests := []struct {
 		name          string
diff --git a/internal/cvss/metrics_test.go b/internal/cvss/metrics_test.go
new file mode 100644
index 00000000..dedbc05d
--- /dev/null
+++ b/internal/cvss/metrics_test.go
@@ -0,0 +1,115 @@
+package cvss
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/anchore/grype/grype/vulnerability"
+)
+
+func TestParseMetricsFromVector(t *testing.T) {
+	tests := []struct {
+		name            string
+		vector          string
+		expectedMetrics *vulnerability.CvssMetrics
+		wantErr         require.ErrorAssertionFunc
+	}{
+		{
+			name:   "valid CVSS 2.0",
+			vector: "AV:N/AC:L/Au:N/C:P/I:P/A:P",
+			expectedMetrics: &vulnerability.CvssMetrics{
+				BaseScore:           7.5,
+				ExploitabilityScore: ptr(10.0),
+				ImpactScore:         ptr(6.5),
+			},
+		},
+		{
+			name:   "valid CVSS 3.0",
+			vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
+			expectedMetrics: &vulnerability.CvssMetrics{
+				BaseScore:           9.8,
+				ExploitabilityScore: ptr(3.9),
+				ImpactScore:         ptr(5.9),
+			},
+		},
+		{
+			name:   "valid CVSS 3.1",
+			vector: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
+			expectedMetrics: &vulnerability.CvssMetrics{
+				BaseScore:           9.8,
+				ExploitabilityScore: ptr(3.9),
+				ImpactScore:         ptr(5.9),
+			},
+		},
+		{
+			name:   "valid CVSS 4.0",
+			vector: "CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:N/VI:H/VA:L/SC:L/SI:H/SA:L/MAC:L/MAT:P/MPR:N/S:N/R:A/RE:L/U:Clear",
+			expectedMetrics: &vulnerability.CvssMetrics{
+				BaseScore: 9.1,
+			},
+		},
+		{
+			name:    "invalid CVSS 2.0",
+			vector:  "AV:N/AC:INVALID",
+			wantErr: require.Error,
+		},
+		{
+			name:    "invalid CVSS 3.0",
+			vector:  "CVSS:3.0/AV:INVALID",
+			wantErr: require.Error,
+		},
+		{
+			name:    "invalid CVSS 3.1",
+			vector:  "CVSS:3.1/AV:INVALID",
+			wantErr: require.Error,
+		},
+		{
+			name:    "invalid CVSS 4.0",
+			vector:  "CVSS:4.0/AV:INVALID",
+			wantErr: require.Error,
+		},
+		{
+			name:    "empty vector",
+			vector:  "",
+			wantErr: require.Error,
+		},
+		{
+			name:    "malformed vector",
+			vector:  "INVALID:VECTOR",
+			wantErr: require.Error,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.wantErr == nil {
+				tt.wantErr = require.NoError
+			}
+			result, err := ParseMetricsFromVector(tt.vector)
+			tt.wantErr(t, err)
+			if err != nil {
+				assert.Nil(t, result)
+				return
+			}
+
+			require.NotNil(t, result)
+			assert.Equal(t, tt.expectedMetrics.BaseScore, result.BaseScore, "given vector: %s", tt.vector)
+
+			if tt.expectedMetrics.ExploitabilityScore != nil {
+				require.NotNil(t, result.ExploitabilityScore)
+				assert.Equal(t, *tt.expectedMetrics.ExploitabilityScore, *result.ExploitabilityScore, "given vector: %s", tt.vector)
+			}
+
+			if tt.expectedMetrics.ImpactScore != nil {
+				require.NotNil(t, result.ImpactScore)
+				assert.Equal(t, *tt.expectedMetrics.ImpactScore, *result.ImpactScore, "given vector: %s", tt.vector)
+			}
+		})
+	}
+}
+
+func ptr(f float64) *float64 {
+	return &f
+}