Repository: google/osv-scanner
Pull request #437 (2023-07-09 15:12:30)

chore(deps): update docker/setup-buildx-action digest to 4c0219f
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [docker/setup-buildx-action](https://togithub.com/docker/setup-buildx-action) | action | digest | `16c0bc4` -> `4c0219f` |

---

### Configuration

📅 **Schedule**: Branch creation - "before 6am on monday" in timezone Australia/Sydney, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/google/osv-scanner).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNi41LjMiLCJ1cGRhdGVkSW5WZXIiOiIzNi41LjMiLCJ0YXJnZXRCcmFuY2giOiJtYWluIn0=-->



Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 876dee6..4a5fde9 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -44,7 +44,7 @@ jobs:
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3
+        uses: github/codeql-action/init@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # v2.21.0
         with:
           languages: ${{ matrix.language }}
           # If you wish to specify custom queries, you can do so here or in a config file.
@@ -55,7 +55,7 @@ jobs:
       # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
       # If this step fails, then you should remove it and run the build manually (see below)
       - name: Autobuild
-        uses: github/codeql-action/autobuild@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3
+        uses: github/codeql-action/autobuild@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # v2.21.0
 
       # ℹ️ Command-line programs to run using the OS shell.
       # 📚 https://git.io/JvXDl
@@ -69,4 +69,4 @@ jobs:
       #   make release
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3
+        uses: github/codeql-action/analyze@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # v2.21.0
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 7541d63..81da942 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -34,7 +34,7 @@ jobs:
       - name: Run Lints
         uses: ./.github/workflows/lint-action
       - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2
-      - uses: docker/setup-buildx-action@16c0bc4a6e6ada2cfd8afd41d22d95379cf7c32a # v2
+      - uses: docker/setup-buildx-action@4c0219f9ac95b02789c1075625400b2acbff50b1 # v2
       - name: ghcr-login
         uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2
         with:
diff --git a/.github/workflows/link-check-on-push.yml b/.github/workflows/link-check-on-push.yml
index fbd739e..813cf6b 100644
--- a/.github/workflows/link-check-on-push.yml
+++ b/.github/workflows/link-check-on-push.yml
@@ -8,7 +8,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@master
-    - uses: gaurav-nelson/github-action-markdown-link-check@v1
+    - uses: gaurav-nelson/github-action-markdown-link-check@322b2315689b8cc8c65c14a07064ec862e62ee7c
       with:
         use-quiet-mode: "yes"
         base-branch: "main"
diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml
index 0c9e42f..feca363 100644
--- a/.github/workflows/link-check.yml
+++ b/.github/workflows/link-check.yml
@@ -2,7 +2,7 @@ name: Check markdown links on schedule
 
 on: 
   schedule:
-    - cron: '45 22 * * *'
+    - cron: '45 22 * * 1,4'
 permissions:  # added using https://github.com/step-security/secure-repo
   contents: read
 jobs:
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@master
-    - uses: gaurav-nelson/github-action-markdown-link-check@v1
+    - uses: gaurav-nelson/github-action-markdown-link-check@322b2315689b8cc8c65c14a07064ec862e62ee7c
       with:
         use-quiet-mode: "yes"
 # Documentation available here: https://github.com/marketplace/actions/markdown-link-check
\ No newline at end of file
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 57abe6d..1673667 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -67,6 +67,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3
+        uses: github/codeql-action/upload-sarif@1813ca74c3faaa3a2da2070b9b8a0b3e7373a0d8 # v2.21.0
         with:
           sarif_file: results.sarif
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 807bd99..29b5fb1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,18 @@
 
+# v1.3.6:
+
+### Minor Updates
+- [Feature #431](https://github.com/google/osv-scanner/pull/431)
+  Update GoVulnCheck integration. 
+- [Feature #439](https://github.com/google/osv-scanner/pull/439)
+  Create `models.PURLToPackage()`, and deprecate `osvscanner.PURLToPackage()`.
+
+### Fixes
+- [Feature #439](https://github.com/google/osv-scanner/pull/439)
+  Fix `PURLToPackage` not returning the full namespace of packages in ecosystems
+  that use them (e.g. golang).
+
+
 # v1.3.5:
 
 ### Features
diff --git a/Dockerfile b/Dockerfile
index 838a65a..fc2968c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,27 +12,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:alpine@sha256:fd9d9d7194ec40a9a6ae89fcaef3e47c47de7746dd5848ab5343695dbbd09f8c
+FROM golang:alpine@sha256:7839c9f01b5502d7cb5198b2c032857023424470b3e31ae46a8261ffca72912a AS builder
 
-RUN mkdir /src
 WORKDIR /src
-
-COPY ./go.mod /src/go.mod
-COPY ./go.sum /src/go.sum
+COPY ./go.mod ./go.sum ./
 RUN go mod download
 
-COPY ./ /src/
+COPY ./ ./
 RUN go build -o osv-scanner ./cmd/osv-scanner/
 
 FROM alpine:3.18@sha256:82d1e9d7ed48a7523bdebc18cf6290bdb97b82302a8a9c27d4fe885949ea94d1
-RUN apk --no-cache add \
-    ca-certificates \
-    git
 
-# Allow git to run on mounted directories
-RUN git config --global --add safe.directory '*'
+RUN apk --no-cache add ca-certificates git && \
+    git config --global --add safe.directory '*'
 
 WORKDIR /root/
-COPY --from=0 /src/osv-scanner ./
+COPY --from=builder /src/osv-scanner .
 
 ENTRYPOINT ["/root/osv-scanner"]
diff --git a/README.md b/README.md
index 68566c3..70d8c1d 100644
--- a/README.md
+++ b/README.md
@@ -23,7 +23,7 @@ Read our [detailed documentation](https://google.github.io/osv-scanner) to learn
 ## Contribute
 
 ### Report Problems
-If you have what looks like a bug, please use the [Github issue tracking system](https://github.com/google/osv-scanner/issues). Before you file an issue, please search existing issues to see if your issue is already covered.
+If you have what looks like a bug, please use the [GitHub issue tracking system](https://github.com/google/osv-scanner/issues). Before you file an issue, please search existing issues to see if your issue is already covered.
 
 ### Contributing code to `osv-scanner`
 
diff --git a/cmd/osv-scanner/main.go b/cmd/osv-scanner/main.go
index 7468250..414f79e 100644
--- a/cmd/osv-scanner/main.go
+++ b/cmd/osv-scanner/main.go
@@ -15,7 +15,7 @@ import (
 
 var (
 	// Update this variable when doing a release
-	version = "1.3.5"
+	version = "1.3.6"
 	commit  = "n/a"
 	date    = "n/a"
 )
diff --git a/go.mod b/go.mod
index 54005cb..7d829a9 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/BurntSushi/toml v1.3.2
 	github.com/CycloneDX/cyclonedx-go v0.7.1
 	github.com/go-git/go-billy/v5 v5.4.1
-	github.com/go-git/go-git/v5 v5.7.0
+	github.com/go-git/go-git/v5 v5.8.0
 	github.com/goark/go-cvss v1.6.6
 	github.com/google/go-cmp v0.5.9
 	github.com/jedib0t/go-pretty/v6 v6.4.6
@@ -14,11 +14,11 @@ require (
 	github.com/package-url/packageurl-go v0.1.1
 	github.com/spdx/tools-golang v0.5.2
 	github.com/urfave/cli/v2 v2.25.7
-	golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
+	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
 	golang.org/x/mod v0.12.0
 	golang.org/x/sync v0.3.0
 	golang.org/x/term v0.10.0
-	golang.org/x/vuln v0.2.1-0.20230630202714-a443f34b1a4a
+	golang.org/x/vuln v1.0.0
 	gopkg.in/yaml.v3 v3.0.1
 )
 
diff --git a/go.sum b/go.sum
index 55a825e..e2fcd53 100644
--- a/go.sum
+++ b/go.sum
@@ -32,8 +32,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmS
 github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
 github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
-github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE=
-github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
+github.com/go-git/go-git/v5 v5.8.0 h1:Rc543s6Tyq+YcyPwZRvU4jzZGM8rB/wWu94TnTIYALQ=
+github.com/go-git/go-git/v5 v5.8.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
 github.com/goark/errs v1.1.0 h1:FKnyw4LVyRADIjM8Nj0Up6r0/y5cfADvZAd1E+tthXE=
 github.com/goark/errs v1.1.0/go.mod h1:TtaPEoadm2mzqzfXdkkfpN2xuniCFm2q4JH+c1qzaqw=
 github.com/goark/go-cvss v1.6.6 h1:WJFuIWqmAw1Ilb9USv0vuX+nYzOWJp8lIujseJ/y3sU=
@@ -112,8 +112,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
 golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
-golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
-golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
+golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
@@ -166,8 +166,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8=
 golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
-golang.org/x/vuln v0.2.1-0.20230630202714-a443f34b1a4a h1:lWjZIBGEop6+ED8P8qyyKkbZ8sdOxw6n+jGtlgN6V3g=
-golang.org/x/vuln v0.2.1-0.20230630202714-a443f34b1a4a/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4=
+golang.org/x/vuln v1.0.0 h1:tYLAU3jD9LQr98Y+3el06lWyGMCnvzw06PIWP3LIy7g=
+golang.org/x/vuln v1.0.0/go.mod h1:V0eyhHwaAaHrt42J9bgrN6rd12f6GU4T0Lu0ex2wDg4=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/models/purl_to_package.go b/pkg/models/purl_to_package.go
new file mode 100644
index 0000000..4641dc8
--- /dev/null
+++ b/pkg/models/purl_to_package.go
@@ -0,0 +1,52 @@
+package models
+
+import (
+	"github.com/package-url/packageurl-go"
+)
+
+var purlEcosystems = map[string]Ecosystem{
+	"cargo":    EcosystemCratesIO,
+	"deb":      EcosystemDebian,
+	"hex":      EcosystemHex,
+	"golang":   EcosystemGo,
+	"maven":    EcosystemMaven,
+	"nuget":    EcosystemNuGet,
+	"npm":      EcosystemNPM,
+	"composer": EcosystemPackagist,
+	"generic":  EcosystemOSSFuzz,
+	"pypi":     EcosystemPyPI,
+	"gem":      EcosystemRubyGems,
+}
+
+// PURLToPackage converts a Package URL string to models.PackageInfo
+func PURLToPackage(purl string) (PackageInfo, error) {
+	parsedPURL, err := packageurl.FromString(purl)
+	if err != nil {
+		return PackageInfo{}, err
+	}
+	ecosystem, ok := purlEcosystems[parsedPURL.Type]
+	if !ok {
+		ecosystem = Ecosystem(parsedPURL.Type)
+	}
+
+	// PackageInfo expects the full namespace in the name for ecosystems that specify it.
+	name := parsedPURL.Name
+	if parsedPURL.Namespace != "" {
+		switch ecosystem { //nolint:exhaustive
+		case EcosystemMaven:
+			// Maven uses : to separate namespace and package
+			name = parsedPURL.Namespace + ":" + parsedPURL.Name
+		case EcosystemDebian, EcosystemAlpine:
+			// Debian and Alpine repeats their namespace in PURL, so don't add it to the name
+			name = parsedPURL.Name
+		default:
+			name = parsedPURL.Namespace + "/" + parsedPURL.Name
+		}
+	}
+
+	return PackageInfo{
+		Name:      name,
+		Ecosystem: string(ecosystem),
+		Version:   parsedPURL.Version,
+	}, nil
+}
diff --git a/pkg/osvscanner/osvscanner.go b/pkg/osvscanner/osvscanner.go
index 586b258..50f2d93 100644
--- a/pkg/osvscanner/osvscanner.go
+++ b/pkg/osvscanner/osvscanner.go
@@ -244,7 +244,7 @@ func scanSBOMFile(r reporter.Reporter, query *osv.BatchedQuery, path string, fro
 		count := 0
 		ignoredCount := 0
 		err = provider.GetPackages(file, func(id sbom.Identifier) error {
-			_, err := PURLToPackage(id.PURL)
+			_, err := models.PURLToPackage(id.PURL)
 			if err != nil {
 				ignoredCount++
 				//nolint:nilerr
diff --git a/pkg/osvscanner/purl_to_package.go b/pkg/osvscanner/purl_to_package.go
index a9fe98f..0a33dba 100644
--- a/pkg/osvscanner/purl_to_package.go
+++ b/pkg/osvscanner/purl_to_package.go
@@ -2,36 +2,11 @@ package osvscanner
 
 import (
 	"github.com/google/osv-scanner/pkg/models"
-	"github.com/package-url/packageurl-go"
 )
 
-var purlEcosystems = map[string]string{
-	"cargo":    "crates.io",
-	"deb":      "Debian",
-	"hex":      "Hex",
-	"golang":   "Go",
-	"maven":    "Maven",
-	"nuget":    "NuGet",
-	"npm":      "npm",
-	"composer": "Packagist",
-	"generic":  "OSS-Fuzz",
-	"pypi":     "PyPI",
-	"gem":      "RubyGems",
-}
-
+// PURLToPackage converts a Package URL string to models.PackageInfo
+//
+// Deprecated: Use the PURLToPackage in the models package instead.
 func PURLToPackage(purl string) (models.PackageInfo, error) {
-	parsedPURL, err := packageurl.FromString(purl)
-	if err != nil {
-		return models.PackageInfo{}, err
-	}
-	ecosystem := purlEcosystems[parsedPURL.Type]
-	if ecosystem == "" {
-		ecosystem = parsedPURL.Type
-	}
-
-	return models.PackageInfo{
-		Name:      parsedPURL.Name,
-		Ecosystem: ecosystem,
-		Version:   parsedPURL.Version,
-	}, nil
+	return models.PURLToPackage(purl)
 }
diff --git a/pkg/osvscanner/vulnerability_result.go b/pkg/osvscanner/vulnerability_result.go
index 4a4260a..075b98b 100644
--- a/pkg/osvscanner/vulnerability_result.go
+++ b/pkg/osvscanner/vulnerability_result.go
@@ -30,7 +30,7 @@ func groupResponseBySource(r reporter.Reporter, query osv.BatchedQuery, resp *os
 			pkg.Package.Ecosystem = "GIT"
 		} else if query.Package.PURL != "" {
 			var err error
-			pkg.Package, err = PURLToPackage(query.Package.PURL)
+			pkg.Package, err = models.PURLToPackage(query.Package.PURL)
 			if err != nil {
 				r.PrintError(fmt.Sprintf("Failed to parse purl: %s, with error: %s",
 					query.Package.PURL, err))
diff --git a/renovate.json b/renovate.json
index 5b92396..f00a004 100644
--- a/renovate.json
+++ b/renovate.json
@@ -8,6 +8,7 @@
   "labels": ["dependencies"],
   "postUpdateOptions": ["gomodTidy"],
   "osvVulnerabilityAlerts": true,
+  "lockFileMaintenance": { "enabled": true },
   "packageRules": [
     {
       "matchUpdateTypes": ["major"],


Test patch:
diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go
index 5c24566..6b40d84 100644
--- a/cmd/osv-scanner/main_test.go
+++ b/cmd/osv-scanner/main_test.go
@@ -144,22 +144,22 @@ func TestRun(t *testing.T) {
 			wantStdout: `
 				Scanning dir ./fixtures/sbom-insecure/postgres-stretch.cdx.xml
 				Scanned %%/fixtures/sbom-insecure/postgres-stretch.cdx.xml as CycloneDX SBOM and found 136 packages
-				+-------------------------------------+------+-----------+---------+------------------------------------+-------------------------------------------------+
-				| OSV URL                             | CVSS | ECOSYSTEM | PACKAGE | VERSION                            | SOURCE                                          |
-				+-------------------------------------+------+-----------+---------+------------------------------------+-------------------------------------------------+
-				| https://osv.dev/DLA-3022-1          |      | Debian    | dpkg    | 1.18.25                            | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/GHSA-v95c-p5hm-xq8f | 6    | Go        | runc    | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/GO-2022-0274        |      |           |         |                                    |                                                 |
-				| https://osv.dev/GHSA-f3fp-gc8g-vw66 | 5.9  | Go        | runc    | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/GHSA-g2j6-57v7-gm8c | 6.1  | Go        | runc    | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/GHSA-m8cg-xc2p-r3fc | 2.5  | Go        | runc    | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/GHSA-vpvm-3wq2-2wvm | 7    | Go        | runc    | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/GHSA-p782-xgp4-8hr8 | 5.3  | Go        | sys     | v0.0.0-20210817142637-7d9622a276b7 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/GO-2022-0493        |      |           |         |                                    |                                                 |
-				| https://osv.dev/DLA-3012-1          |      | Debian    | libxml2 | 2.9.4+dfsg1-2.2+deb9u6             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/DLA-3008-1          |      | Debian    | openssl | 1.1.0l-1~deb9u5                    | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				| https://osv.dev/DLA-3051-1          |      | Debian    | tzdata  | 2021a-0+deb9u3                     | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
-				+-------------------------------------+------+-----------+---------+------------------------------------+-------------------------------------------------+
+				+-------------------------------------+------+-----------+--------------------------------+------------------------------------+-------------------------------------------------+
+				| OSV URL                             | CVSS | ECOSYSTEM | PACKAGE                        | VERSION                            | SOURCE                                          |
+				+-------------------------------------+------+-----------+--------------------------------+------------------------------------+-------------------------------------------------+
+				| https://osv.dev/DLA-3022-1          |      | Debian    | dpkg                           | 1.18.25                            | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/GHSA-v95c-p5hm-xq8f | 6    | Go        | github.com/opencontainers/runc | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/GO-2022-0274        |      |           |                                |                                    |                                                 |
+				| https://osv.dev/GHSA-f3fp-gc8g-vw66 | 5.9  | Go        | github.com/opencontainers/runc | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/GHSA-g2j6-57v7-gm8c | 6.1  | Go        | github.com/opencontainers/runc | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/GHSA-m8cg-xc2p-r3fc | 2.5  | Go        | github.com/opencontainers/runc | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/GHSA-vpvm-3wq2-2wvm | 7    | Go        | github.com/opencontainers/runc | v1.0.1                             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/GHSA-p782-xgp4-8hr8 | 5.3  | Go        | golang.org/x/sys               | v0.0.0-20210817142637-7d9622a276b7 | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/GO-2022-0493        |      |           |                                |                                    |                                                 |
+				| https://osv.dev/DLA-3012-1          |      | Debian    | libxml2                        | 2.9.4+dfsg1-2.2+deb9u6             | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/DLA-3008-1          |      | Debian    | openssl                        | 1.1.0l-1~deb9u5                    | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				| https://osv.dev/DLA-3051-1          |      | Debian    | tzdata                         | 2021a-0+deb9u3                     | fixtures/sbom-insecure/postgres-stretch.cdx.xml |
+				+-------------------------------------+------+-----------+--------------------------------+------------------------------------+-------------------------------------------------+
 			`,
 			wantStderr: "",
 		},
diff --git a/pkg/models/purl_to_package_test.go b/pkg/models/purl_to_package_test.go
new file mode 100644
index 0000000..c6623e7
--- /dev/null
+++ b/pkg/models/purl_to_package_test.go
@@ -0,0 +1,88 @@
+package models_test
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/google/osv-scanner/pkg/models"
+)
+
+func TestPURLToPackage(t *testing.T) {
+	t.Parallel()
+	type args struct {
+		purl string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    models.PackageInfo
+		wantErr bool
+	}{
+		{
+			name: "valid PURL",
+			args: args{
+				purl: "pkg:cargo/memoffset@0.6.1",
+			},
+			want: models.PackageInfo{
+				Name:      "memoffset",
+				Version:   "0.6.1",
+				Ecosystem: string(models.EcosystemCratesIO),
+			},
+		},
+		{
+			name: "valid PURL golang",
+			args: args{
+				purl: "pkg:golang/github.com/gogo/protobuf@5.6.0",
+			},
+			want: models.PackageInfo{
+				Name:      "github.com/gogo/protobuf",
+				Version:   "5.6.0",
+				Ecosystem: string(models.EcosystemGo),
+			},
+		},
+		{
+			name: "valid PURL maven",
+			args: args{
+				purl: "pkg:maven/org.hdrhistogram/HdrHistogram@2.1.12",
+			},
+			want: models.PackageInfo{
+				Name:      "org.hdrhistogram:HdrHistogram",
+				Version:   "2.1.12",
+				Ecosystem: string(models.EcosystemMaven),
+			},
+		},
+		{
+			name: "valid Debian maven",
+			args: args{
+				purl: "pkg:deb/debian/nginx@2.36.1-8+deb11u1",
+			},
+			want: models.PackageInfo{
+				Name:      "nginx",
+				Version:   "2.36.1-8+deb11u1",
+				Ecosystem: string(models.EcosystemDebian),
+			},
+		},
+		{
+			name: "invalid PURL",
+			args: args{
+				purl: "pkg-golang/github.com/gogo/protobuf.0",
+			},
+			want:    models.PackageInfo{},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			got, err := models.PURLToPackage(tt.args.purl)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("PURLToPackage() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("PURLToPackage() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}