Repository: google/osv-scanner
Pull request #338 (2023-04-12 06:20:15)

chore(deps): bump nokogiri from 1.14.1 to 1.14.3 in /docs
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.14.1 to 1.14.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/sparklemotion/nokogiri/releases">nokogiri's releases</a>.</em></p>
<blockquote>
<h2>1.14.3 / 2023-04-11</h2>
<h3>Security</h3>
<ul>
<li>[CRuby] Vendored libxml2 is updated to address CVE-2023-29469, CVE-2023-28484, and one other security-related issue. See <a href="https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-pxvg-2qj5-37jq">GHSA-pxvg-2qj5-37jqGHSA-pxvg-2qj5-37jq</a> for more information.</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>[CRuby] Vendored libxml2 is updated to <a href="https://gitlab.gnome.org/GNOME/libxml2/-/releases/v2.10.4">v2.10.4</a> from v2.10.3.</li>
</ul>
<hr />
<p>sha256 checksums:</p>
<pre><code>9cc53dd8d92868a0f5bcee44396357a19f95e32d8b9754092622a25bc954c60c  nokogiri-1.14.3-aarch64-linux.gem
320fa1836b8e59e86a804baee534893bcf3b901cc255bbec6d87f3dd3e431610  nokogiri-1.14.3-arm-linux.gem
67dd4ac33a8cf0967c521fa57e5a5422db39da8a9d131aaa2cd53deaa12be4cd  nokogiri-1.14.3-arm64-darwin.gem
13969ec7f41d9cff46fc7707224c55490a519feef7cfea727c6945c5b444caa2  nokogiri-1.14.3-java.gem
9885085249303461ee08f9a9b161d0a570391b8f5be0316b3ac5a6d9a947e1e2  nokogiri-1.14.3-x64-mingw-ucrt.gem
997943d7582a23ad6e7a0abe081d0d40d2c1319a6b2749f9b30fd18037f0c38a  nokogiri-1.14.3-x64-mingw32.gem
58c30b763aebd62dc4222385509d7f83ac398ee520490fadc4b6d7877e29895a  nokogiri-1.14.3-x86-linux.gem
e1d58a5c56c34aab71b00901a969e19bf9f7322ee459b4e9380f433213887c04  nokogiri-1.14.3-x86-mingw32.gem
f0a1ed1460a91fd2daf558357f4c0ceac6d994899da1bf98431aeda301e4dc74  nokogiri-1.14.3-x86_64-darwin.gem
e323a7c654ef846e64582fb6e26f6fed869a96753f8e048ff723e74d8005cb11  nokogiri-1.14.3-x86_64-linux.gem
3b1cee0eb8879e9e25b6dd431be597ca68f20283b0d4f4ca986521fad107dc3a  nokogiri-1.14.3.gem
</code></pre>
<h2>1.14.2 / 2023-02-13</h2>
<h3>Fixed</h3>
<ul>
<li>Calling <code>NodeSet#to_html</code> on an empty node set no longer raises an encoding-related exception. This bug was introduced in v1.14.0 while fixing <a href="https://redirect.github.com/sparklemotion/nokogiri/issues/2649">#2649</a>. [<a href="https://redirect.github.com/sparklemotion/nokogiri/issues/2784">#2784</a>]</li>
</ul>
<hr />
<p>sha256 checksums:</p>
<pre lang="text"><code>966acf4f6c1fba10518f86498141cf44265564ac5a65dcc8496b65f8c354f776  nokogiri-1.14.2-aarch64-linux.gem
8a3a35cadae4a800ddc0b967394257343d62196d9d059b54e38cf067981db428  nokogiri-1.14.2-arm-linux.gem
81404cd014ecb597725c3847523c2ee365191a968d0b5f7d857e03f388c57631  nokogiri-1.14.2-arm64-darwin.gem
0a39222af14e75eb0243e8d969345e03b90c0e02b0f33c61f1ebb6ae53538bb5  nokogiri-1.14.2-java.gem
62a18f9213a0ceeaf563d1bc7ccfd93273323c4356ded58a5617c59bc4635bc5  nokogiri-1.14.2-x64-mingw-ucrt.gem
54f6ac2c15a7a88f431bb5e23f4616aa8fc97a92eb63336bcf65b7050f2d3be0  nokogiri-1.14.2-x64-mingw32.gem
c42fa0856f01f901954898e28c3c2b4dce0e843056b1b126f441d06e887e1b77  nokogiri-1.14.2-x86-linux.gem
f940d9c8e47b0f19875465376f2d1c8911bc9489ac9a48c124579819dc4a7f19  nokogiri-1.14.2-x86-mingw32.gem
2508978f5ca28944919973f6300f0a7355fbe72604ab6a6913f1630be1030265  nokogiri-1.14.2-x86_64-darwin.gem
bc6405e1f3ddac6e401f82d775f1c0c24c6e58c371b3fadaca0596d5d511e476  nokogiri-1.14.2-x86_64-linux.gem
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md">nokogiri's changelog</a>.</em></p>
<blockquote>
<h2>1.14.3 / 2023-04-11</h2>
<h3>Security</h3>
<ul>
<li>[CRuby] Vendored libxml2 is updated to address CVE-2023-29469, CVE-2023-28484, and one other security-related issue. See <a href="https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-pxvg-2qj5-37jq">GHSA-pxvg-2qj5-37jqGHSA-pxvg-2qj5-37jq</a> for more information.</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>[CRuby] Vendored libxml2 is updated to <a href="https://gitlab.gnome.org/GNOME/libxml2/-/releases/v2.10.4">v2.10.4</a> from v2.10.3.</li>
</ul>
<h2>1.14.2 / 2023-02-13</h2>
<h3>Fixed</h3>
<ul>
<li>Calling <code>NodeSet#to_html</code> on an empty node set no longer raises an encoding-related exception. This bug was introduced in v1.14.0 while fixing <a href="https://redirect.github.com/sparklemotion/nokogiri/issues/2649">#2649</a>. [<a href="https://redirect.github.com/sparklemotion/nokogiri/issues/2784">#2784</a>]</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/e8d2f4a829c50e6ad4ccd1625cd094e3a24acf36"><code>e8d2f4a</code></a> version bump to v1.14.3</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/59fbc7b6d5cecad921cc24daa34d69bbda8713fb"><code>59fbc7b</code></a> doc: update CHANGELOG for v1.14.3</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/347eacbeeacd2be0140faede75395bd1ed01073f"><code>347eacb</code></a> Merge pull request <a href="https://redirect.github.com/sparklemotion/nokogiri/issues/2852">#2852</a> from sparklemotion/flavorjones-libxml2-2.10.4-backport</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/36b0b3355d6d0d45bfdf1b55012bccfb348a6b4f"><code>36b0b33</code></a> dep: update libxml2 to 2.10.4 from 2.10.3</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/ac83e6ee7011ce9fec24399c759a73fb924a0de6"><code>ac83e6e</code></a> test: update behavior of namespaces in HTML4</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/2cf4996c5280f93e1ea66b2aceeed848d57fbe01"><code>2cf4996</code></a> test: make default GC behavior &quot;normal&quot;</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/1580121eeae3c1f266c4012d22d61314aa3202b7"><code>1580121</code></a> version bump to v1.14.2</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/530947753e3074d551217ea6bce7b4bbbc1234a9"><code>5309477</code></a> Merge pull request <a href="https://redirect.github.com/sparklemotion/nokogiri/issues/2791">#2791</a> from sparklemotion/2784-encoding-empty-strings-v1.14.x</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/975ae491c4993ff47e36262dc8e305fa2d651bf4"><code>975ae49</code></a> doc: update CHANGELOG</li>
<li><a href="https://github.com/sparklemotion/nokogiri/commit/f13cdb46406c9b119a368cf506d831865dd95163"><code>f13cdb4</code></a> fix: empty node set serialization when document encoding is nil</li>
<li>See full diff in <a href="https://github.com/sparklemotion/nokogiri/compare/v1.14.1...v1.14.3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=nokogiri&package-manager=bundler&previous-version=1.14.1&new-version=1.14.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 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)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/google/osv-scanner/network/alerts).

</details>


Patch:
diff --git a/.github/workflows/lint-action/action.yml b/.github/workflows/lint-action/action.yml
index 8fcee21..4257475 100644
--- a/.github/workflows/lint-action/action.yml
+++ b/.github/workflows/lint-action/action.yml
@@ -22,4 +22,6 @@ runs:
       uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # v3.4.0
       with:
         # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
-        version: v1.51.1
\ No newline at end of file
+        version: v1.51.1
+        # https://github.com/golangci/golangci-lint-action/issues/135
+        skip-pkg-cache: true
\ No newline at end of file
diff --git a/cmd/osv-scanner/main.go b/cmd/osv-scanner/main.go
index 7d80646..c0c0df1 100644
--- a/cmd/osv-scanner/main.go
+++ b/cmd/osv-scanner/main.go
@@ -6,8 +6,8 @@ import (
 	"io"
 	"os"
 
-	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/pkg/osvscanner"
+	"github.com/google/osv-scanner/pkg/reporter"
 
 	"github.com/urfave/cli/v2"
 )
@@ -20,10 +20,10 @@ var (
 )
 
 func run(args []string, stdout, stderr io.Writer) int {
-	var r *output.Reporter
+	var r *reporter.Reporter
 
 	cli.VersionPrinter = func(ctx *cli.Context) {
-		r = output.NewReporter(ctx.App.Writer, ctx.App.ErrWriter, "")
+		r = reporter.NewReporter(ctx.App.Writer, ctx.App.ErrWriter, "")
 		r.PrintText(fmt.Sprintf("osv-scanner version: %s\ncommit: %s\nbuilt at: %s\n", ctx.App.Version, commit, date))
 	}
 
@@ -109,7 +109,7 @@ func run(args []string, stdout, stderr io.Writer) int {
 				format = "json"
 			}
 
-			r = output.NewReporter(stdout, stderr, format)
+			r = reporter.NewReporter(stdout, stderr, format)
 
 			vulnResult, err := osvscanner.DoScan(osvscanner.ScannerActions{
 				LockfilePaths:            context.StringSlice("lockfile"),
@@ -133,7 +133,7 @@ func run(args []string, stdout, stderr io.Writer) int {
 
 	if err := app.Run(args); err != nil {
 		if r == nil {
-			r = output.NewReporter(stdout, stderr, "")
+			r = reporter.NewReporter(stdout, stderr, "")
 		}
 		if errors.Is(err, osvscanner.VulnerabilitiesFoundErr) {
 			return 1
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index c8507e0..07b8195 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -13,7 +13,7 @@ GEM
       execjs
     coffee-script-source (1.11.1)
     colorator (1.1.0)
-    commonmarker (0.23.8)
+    commonmarker (0.23.9)
     concurrent-ruby (1.2.2)
     dnsruby (1.61.9)
       simpleidn (~> 0.1)
@@ -210,7 +210,7 @@ GEM
       jekyll-feed (~> 0.9)
       jekyll-seo-tag (~> 2.1)
     minitest (5.18.0)
-    nokogiri (1.14.1-x86_64-linux)
+    nokogiri (1.14.3-x86_64-linux)
       racc (~> 1.4)
     octokit (4.25.1)
       faraday (>= 1, < 3)
diff --git a/internal/sbom/cyclonedx.go b/internal/sbom/cyclonedx.go
index 9091c38..069b629 100644
--- a/internal/sbom/cyclonedx.go
+++ b/internal/sbom/cyclonedx.go
@@ -1,6 +1,7 @@
 package sbom
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"path/filepath"
@@ -11,10 +12,21 @@ import (
 
 type CycloneDX struct{}
 
+type cyclonedxType struct {
+	name    string
+	bomType cyclonedx.BOMFileFormat
+}
+
 var (
-	cycloneDXTypes = []cyclonedx.BOMFileFormat{
-		cyclonedx.BOMFileFormatJSON,
-		cyclonedx.BOMFileFormatXML,
+	cycloneDXTypes = []cyclonedxType{
+		{
+			name:    "json",
+			bomType: cyclonedx.BOMFileFormatJSON,
+		},
+		{
+			name:    "xml",
+			bomType: cyclonedx.BOMFileFormatXML,
+		},
 	}
 )
 
@@ -77,6 +89,8 @@ func (c *CycloneDX) enumeratePackages(bom *cyclonedx.BOM, callback func(Identifi
 }
 
 func (c *CycloneDX) GetPackages(r io.ReadSeeker, callback func(Identifier) error) error {
+	//nolint:prealloc // Not sure how many there will be in advance.
+	var errs []error
 	var bom cyclonedx.BOM
 
 	for _, formatType := range cycloneDXTypes {
@@ -84,12 +98,21 @@ func (c *CycloneDX) GetPackages(r io.ReadSeeker, callback func(Identifier) error
 		if err != nil {
 			return fmt.Errorf("failed to seek to start of file: %w", err)
 		}
-		decoder := cyclonedx.NewBOMDecoder(r, formatType)
+		decoder := cyclonedx.NewBOMDecoder(r, formatType.bomType)
 		err = decoder.Decode(&bom)
-		if err == nil && (bom.BOMFormat == "CycloneDX" || strings.HasPrefix(bom.XMLNS, "http://cyclonedx.org/schema/bom")) {
-			return c.enumeratePackages(&bom, callback)
+		if err == nil {
+			if bom.BOMFormat == "CycloneDX" || strings.HasPrefix(bom.XMLNS, "http://cyclonedx.org/schema/bom") {
+				return c.enumeratePackages(&bom, callback)
+			} else {
+				err = errors.New("invalid BOMFormat")
+			}
 		}
+
+		errs = append(errs, fmt.Errorf("failed trying %s: %w", formatType.name, err))
 	}
 
-	return ErrInvalidFormat
+	return InvalidFormatError{
+		msg:  "failed to parse CycloneDX",
+		errs: errs,
+	}
 }
diff --git a/internal/sbom/sbom.go b/internal/sbom/sbom.go
index a53e3cc..d948195 100644
--- a/internal/sbom/sbom.go
+++ b/internal/sbom/sbom.go
@@ -1,8 +1,9 @@
 package sbom
 
 import (
-	"errors"
+	"fmt"
 	"io"
+	"strings"
 )
 
 // Identifier is the identifier extracted from the SBOM.
@@ -18,13 +19,23 @@ type SBOMReader interface {
 	GetPackages(io.ReadSeeker, func(Identifier) error) error
 }
 
-var (
-	ErrInvalidFormat = errors.New("invalid format")
-)
-
 var (
 	Providers = []SBOMReader{
 		&SPDX{},
 		&CycloneDX{},
 	}
 )
+
+type InvalidFormatError struct {
+	msg  string
+	errs []error
+}
+
+func (e InvalidFormatError) Error() string {
+	errStrings := make([]string, 0, len(e.errs))
+	for _, e := range e.errs {
+		errStrings = append(errStrings, "\t"+e.Error())
+	}
+
+	return fmt.Sprintf("%s:\n%s", e.msg, strings.Join(errStrings, "\n"))
+}
diff --git a/internal/sbom/spdx.go b/internal/sbom/spdx.go
index 70e5860..191e728 100644
--- a/internal/sbom/spdx.go
+++ b/internal/sbom/spdx.go
@@ -16,11 +16,25 @@ import (
 type SPDX struct{}
 type spdxLoader func(io.Reader) (*v2_3.Document, error)
 
+type loader struct {
+	name   string
+	loader spdxLoader
+}
+
 var (
-	spdxLoaders = []spdxLoader{
-		spdx_json.Load2_3,
-		rdfloader.Load2_3,
-		tvloader.Load2_3,
+	spdxLoaders = []loader{
+		{
+			name:   "json",
+			loader: spdx_json.Load2_3,
+		},
+		{
+			name:   "rdf",
+			loader: rdfloader.Load2_3,
+		},
+		{
+			name:   "tv",
+			loader: tvloader.Load2_3,
+		},
 	}
 )
 
@@ -52,16 +66,22 @@ func (s *SPDX) enumeratePackages(doc *v2_3.Document, callback func(Identifier) e
 }
 
 func (s *SPDX) GetPackages(r io.ReadSeeker, callback func(Identifier) error) error {
+	//nolint:prealloc // Not sure how many there will be in advance.
+	var errs []error
 	for _, loader := range spdxLoaders {
 		_, err := r.Seek(0, io.SeekStart)
 		if err != nil {
 			return fmt.Errorf("failed to seek to start of file: %w", err)
 		}
-		doc, err := loader(r)
+		doc, err := loader.loader(r)
 		if err == nil {
 			return s.enumeratePackages(doc, callback)
 		}
+		errs = append(errs, fmt.Errorf("failed trying %s: %w", loader.name, err))
 	}
 
-	return ErrInvalidFormat
+	return InvalidFormatError{
+		msg:  "failed to parse SPDX",
+		errs: errs,
+	}
 }
diff --git a/internal/sourceanalysis/go.go b/internal/sourceanalysis/go.go
index a2a599a..88ae0d5 100644
--- a/internal/sourceanalysis/go.go
+++ b/internal/sourceanalysis/go.go
@@ -5,13 +5,13 @@ import (
 	"path/filepath"
 
 	"github.com/google/osv-scanner/internal/govulncheckshim"
-	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/pkg/models"
+	"github.com/google/osv-scanner/pkg/reporter"
 	"golang.org/x/exp/slices"
 	"golang.org/x/vuln/exp/govulncheck"
 )
 
-func goAnalysis(r *output.Reporter, pkgs []models.PackageVulns, source models.SourceInfo) {
+func goAnalysis(r *reporter.Reporter, pkgs []models.PackageVulns, source models.SourceInfo) {
 	vulns, vulnsByID := vulnsFromAllPkgs(pkgs)
 	res, err := govulncheckshim.RunGoVulnCheck(filepath.Dir(source.Path), vulns)
 	if err != nil {
diff --git a/internal/sourceanalysis/sourceanalysis.go b/internal/sourceanalysis/sourceanalysis.go
index 121233b..885a2d0 100644
--- a/internal/sourceanalysis/sourceanalysis.go
+++ b/internal/sourceanalysis/sourceanalysis.go
@@ -3,8 +3,8 @@ package sourceanalysis
 import (
 	"path/filepath"
 
-	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/pkg/models"
+	"github.com/google/osv-scanner/pkg/reporter"
 )
 
 // vulnsFromAllPkgs returns the flattened list of unique vulnerabilities
@@ -25,7 +25,7 @@ func vulnsFromAllPkgs(pkgs []models.PackageVulns) ([]models.Vulnerability, map[s
 }
 
 // Run runs the language specific analyzers on the code given packages and source info
-func Run(r *output.Reporter, source models.SourceInfo, pkgs []models.PackageVulns) {
+func Run(r *reporter.Reporter, source models.SourceInfo, pkgs []models.PackageVulns) {
 	// GoVulnCheck
 	if source.Type == "lockfile" && filepath.Base(source.Path) == "go.mod" {
 		goAnalysis(r, pkgs, source)
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 91c3226..cf076d1 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -6,9 +6,8 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/google/osv-scanner/internal/output"
-
 	"github.com/BurntSushi/toml"
+	"github.com/google/osv-scanner/pkg/reporter"
 	"golang.org/x/exp/slices"
 )
 
@@ -64,7 +63,7 @@ func (c *ConfigManager) UseOverride(configPath string) error {
 }
 
 // Attempts to get the config
-func (c *ConfigManager) Get(r *output.Reporter, targetPath string) Config {
+func (c *ConfigManager) Get(r *reporter.Reporter, targetPath string) Config {
 	if c.OverrideConfig != nil {
 		return *c.OverrideConfig
 	}
diff --git a/pkg/lockfile/fixtures/pnpm/one-package-v6-lockfile.yaml b/pkg/lockfile/fixtures/pnpm/one-package-v6-lockfile.yaml
new file mode 100644
index 0000000..44e228b
--- /dev/null
+++ b/pkg/lockfile/fixtures/pnpm/one-package-v6-lockfile.yaml
@@ -0,0 +1,14 @@
+lockfileVersion: '6.0'
+
+dependencies:
+  acorn:
+    specifier: 8.7.0
+    version: 8.7.0
+
+packages:
+
+  /acorn@8.7.0:
+    resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+    dev: false
diff --git a/pkg/lockfile/fixtures/pnpm/scoped-packages-v6-lockfile.yaml b/pkg/lockfile/fixtures/pnpm/scoped-packages-v6-lockfile.yaml
new file mode 100644
index 0000000..2beb037
--- /dev/null
+++ b/pkg/lockfile/fixtures/pnpm/scoped-packages-v6-lockfile.yaml
@@ -0,0 +1,13 @@
+lockfileVersion: '6.0'
+
+dependencies:
+  '@typescript-eslint/types':
+    specifier: ^5.0.0
+    version: 5.57.1
+
+packages:
+
+  /@typescript-eslint/types@5.57.1:
+    resolution: {integrity: sha512-bSs4LOgyV3bJ08F5RDqO2KXqg3WAdwHCu06zOqcQ6vqbTJizyBhuh1o1ImC69X4bV2g1OJxbH71PJqiO7Y1RuA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: false
diff --git a/pkg/lockfile/parse-pnpm-lock.go b/pkg/lockfile/parse-pnpm-lock.go
index 29f0628..37a6331 100644
--- a/pkg/lockfile/parse-pnpm-lock.go
+++ b/pkg/lockfile/parse-pnpm-lock.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 	"regexp"
+	"strconv"
 	"strings"
 
 	"gopkg.in/yaml.v3"
@@ -27,6 +28,30 @@ type PnpmLockfile struct {
 	Packages map[string]PnpmLockPackage `yaml:"packages,omitempty"`
 }
 
+type pnpmLockfileV6 struct {
+	Version  string                     `yaml:"lockfileVersion"`
+	Packages map[string]PnpmLockPackage `yaml:"packages,omitempty"`
+}
+
+func (l *PnpmLockfile) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	var lockfileV6 pnpmLockfileV6
+
+	if err := unmarshal(&lockfileV6); err != nil {
+		return err
+	}
+
+	parsedVersion, err := strconv.ParseFloat(lockfileV6.Version, 64)
+
+	if err != nil {
+		return err
+	}
+
+	l.Version = parsedVersion
+	l.Packages = lockfileV6.Packages
+
+	return nil
+}
+
 const PnpmEcosystem = NpmEcosystem
 
 func startsWithNumber(str string) bool {
@@ -64,6 +89,10 @@ func extractPnpmPackageNameAndVersion(dependencyPath string) (string, string) {
 		version = parts[0]
 	}
 
+	if version == "" {
+		name, version = parseNameAtVersion(name)
+	}
+
 	if version == "" || !startsWithNumber(version) {
 		return "", ""
 	}
@@ -77,6 +106,17 @@ func extractPnpmPackageNameAndVersion(dependencyPath string) (string, string) {
 	return name, version
 }
 
+func parseNameAtVersion(value string) (name string, version string) {
+	// look for pattern "name@version", where name is allowed to contain zero or more "@"
+	matches := regexp.MustCompile(`^(.+)@([\d.]+)$`).FindStringSubmatch(value)
+
+	if len(matches) != 3 {
+		return name, ""
+	}
+
+	return matches[1], matches[2]
+}
+
 func parsePnpmLock(lockfile PnpmLockfile) []PackageDetails {
 	packages := make([]PackageDetails, 0, len(lockfile.Packages))
 
diff --git a/pkg/osvscanner/osvscanner.go b/pkg/osvscanner/osvscanner.go
index c502302..25fa041 100644
--- a/pkg/osvscanner/osvscanner.go
+++ b/pkg/osvscanner/osvscanner.go
@@ -9,12 +9,12 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/internal/sbom"
 	"github.com/google/osv-scanner/pkg/config"
 	"github.com/google/osv-scanner/pkg/lockfile"
 	"github.com/google/osv-scanner/pkg/models"
 	"github.com/google/osv-scanner/pkg/osv"
+	"github.com/google/osv-scanner/pkg/reporter"
 
 	"github.com/go-git/go-billy/v5"
 	"github.com/go-git/go-billy/v5/osfs"
@@ -52,7 +52,7 @@ var OnlyUncalledVulnerabilitiesFoundErr = errors.New("only uncalled vulnerabilit
 //   - Any lockfiles with scanLockfile
 //   - Any SBOM files with scanSBOMFile
 //   - Any git repositories with scanGit
-func scanDir(r *output.Reporter, query *osv.BatchedQuery, dir string, skipGit bool, recursive bool, useGitIgnore bool) error {
+func scanDir(r *reporter.Reporter, query *osv.BatchedQuery, dir string, skipGit bool, recursive bool, useGitIgnore bool) error {
 	var ignoreMatcher *gitIgnoreMatcher
 	if useGitIgnore {
 		var err error
@@ -114,7 +114,7 @@ func scanDir(r *output.Reporter, query *osv.BatchedQuery, dir string, skipGit bo
 			// No need to check for error
 			// If scan fails, it means it isn't a valid SBOM file,
 			// so just move onto the next file
-			_ = scanSBOMFile(r, query, path)
+			_ = scanSBOMFile(r, query, path, true)
 		}
 
 		if !root && !recursive && info.IsDir() {
@@ -180,7 +180,7 @@ func (m *gitIgnoreMatcher) match(absPath string, isDir bool) (bool, error) {
 
 // scanLockfile will load, identify, and parse the lockfile path passed in, and add the dependencies specified
 // within to `query`
-func scanLockfile(r *output.Reporter, query *osv.BatchedQuery, path string, parseAs string) error {
+func scanLockfile(r *reporter.Reporter, query *osv.BatchedQuery, path string, parseAs string) error {
 	var err error
 	var parsedLockfile lockfile.Lockfile
 
@@ -221,10 +221,15 @@ func scanLockfile(r *output.Reporter, query *osv.BatchedQuery, path string, pars
 
 // scanSBOMFile will load, identify, and parse the SBOM path passed in, and add the dependencies specified
 // within to `query`
-func scanSBOMFile(r *output.Reporter, query *osv.BatchedQuery, path string) error {
+func scanSBOMFile(r *reporter.Reporter, query *osv.BatchedQuery, path string, fromFSScan bool) error {
+	var errs []error
 	for _, provider := range sbom.Providers {
-		if !provider.MatchesRecognizedFileNames(path) {
-			// Skip if filename is not usually a sbom file of this format
+		if fromFSScan && !provider.MatchesRecognizedFileNames(path) {
+			// Skip if filename is not usually a sbom file of this format.
+			// Only do this if this is being done in a filesystem scanning context, where we need to be
+			// careful about spending too much time attempting to parse unrelated files.
+			// If this is coming from an explicit scan argument, be more relaxed here since it's common for
+			// filenames to not conform to expected filename standards.
 			continue
 		}
 
@@ -265,13 +270,23 @@ func scanSBOMFile(r *output.Reporter, query *osv.BatchedQuery, path string) erro
 			return nil
 		}
 
-		if errors.Is(err, sbom.ErrInvalidFormat) {
+		var formatErr sbom.InvalidFormatError
+		if errors.As(err, &formatErr) {
+			errs = append(errs, err)
 			continue
 		}
 
 		return err
 	}
 
+	// Don't log these errors if we're coming from a FS scan, since it can get very noisy.
+	if !fromFSScan {
+		r.PrintText("Failed to parse SBOM using all supported formats:\n")
+		for _, err := range errs {
+			r.PrintText(err.Error() + "\n")
+		}
+	}
+
 	return nil
 }
 
@@ -289,7 +304,7 @@ func getCommitSHA(repoDir string) (string, error) {
 }
 
 // Scan git repository. Expects repoDir to end with /
-func scanGit(r *output.Reporter, query *osv.BatchedQuery, repoDir string) error {
+func scanGit(r *reporter.Reporter, query *osv.BatchedQuery, repoDir string) error {
 	commit, err := getCommitSHA(repoDir)
 	if err != nil {
 		return err
@@ -310,7 +325,7 @@ func scanGitCommit(query *osv.BatchedQuery, commit string, source string) error
 	return nil
 }
 
-func scanDebianDocker(r *output.Reporter, query *osv.BatchedQuery, dockerImageName string) error {
+func scanDebianDocker(r *reporter.Reporter, query *osv.BatchedQuery, dockerImageName string) error {
 	cmd := exec.Command("docker", "run", "--rm", "--entrypoint", "/usr/bin/dpkg-query", dockerImageName, "-f", "${Package}###${Version}\\n", "-W")
 	stdout, err := cmd.StdoutPipe()
 
@@ -358,7 +373,7 @@ func scanDebianDocker(r *output.Reporter, query *osv.BatchedQuery, dockerImageNa
 }
 
 // Filters results according to config, preserving order. Returns total number of vulnerabilities removed.
-func filterResults(r *output.Reporter, results *models.VulnerabilityResults, configManager *config.ConfigManager) int {
+func filterResults(r *reporter.Reporter, results *models.VulnerabilityResults, configManager *config.ConfigManager) int {
 	removedCount := 0
 	newResults := []models.PackageSource{} // Want 0 vulnerabilities to show in JSON as an empty list, not null.
 	for _, pkgSrc := range results.Results {
@@ -384,7 +399,7 @@ func filterResults(r *output.Reporter, results *models.VulnerabilityResults, con
 }
 
 // Filters package-grouped vulnerabilities according to config, preserving ordering. Returns filtered package vulnerabilities.
-func filterPackageVulns(r *output.Reporter, pkgVulns models.PackageVulns, configToUse config.Config) models.PackageVulns {
+func filterPackageVulns(r *reporter.Reporter, pkgVulns models.PackageVulns, configToUse config.Config) models.PackageVulns {
 	ignoredVulns := map[string]struct{}{}
 	// Iterate over groups first to remove all aliases of ignored vulnerabilities.
 	var newGroups []models.GroupInfo
@@ -441,9 +456,9 @@ func parseLockfilePath(lockfileElem string) (string, string) {
 }
 
 // Perform osv scanner action, with optional reporter to output information
-func DoScan(actions ScannerActions, r *output.Reporter) (models.VulnerabilityResults, error) {
+func DoScan(actions ScannerActions, r *reporter.Reporter) (models.VulnerabilityResults, error) {
 	if r == nil {
-		r = output.NewVoidReporter()
+		r = reporter.NewVoidReporter()
 	}
 
 	configManager := config.ConfigManager{
@@ -485,7 +500,7 @@ func DoScan(actions ScannerActions, r *output.Reporter) (models.VulnerabilityRes
 		if err != nil {
 			return models.VulnerabilityResults{}, fmt.Errorf("failed to resolved path with error %w", err)
 		}
-		err = scanSBOMFile(r, &query, sbomElem)
+		err = scanSBOMFile(r, &query, sbomElem, false)
 		if err != nil {
 			return models.VulnerabilityResults{}, err
 		}
diff --git a/pkg/osvscanner/vulnerability_result.go b/pkg/osvscanner/vulnerability_result.go
index fab5381..a477a5f 100644
--- a/pkg/osvscanner/vulnerability_result.go
+++ b/pkg/osvscanner/vulnerability_result.go
@@ -4,16 +4,16 @@ import (
 	"fmt"
 	"sort"
 
-	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/internal/sourceanalysis"
 	"github.com/google/osv-scanner/pkg/grouper"
 	"github.com/google/osv-scanner/pkg/models"
 	"github.com/google/osv-scanner/pkg/osv"
+	"github.com/google/osv-scanner/pkg/reporter"
 )
 
 // groupResponseBySource converts raw OSV API response into structured vulnerability information
 // grouped by source location.
-func groupResponseBySource(r *output.Reporter, query osv.BatchedQuery, resp *osv.HydratedBatchedResponse, callAnalysis bool) models.VulnerabilityResults {
+func groupResponseBySource(r *reporter.Reporter, query osv.BatchedQuery, resp *osv.HydratedBatchedResponse, callAnalysis bool) models.VulnerabilityResults {
 	output := models.VulnerabilityResults{
 		Results: []models.PackageSource{},
 	}
diff --git a/internal/output/reporter.go b/pkg/reporter/reporter.go
similarity index 86%
rename from internal/output/reporter.go
rename to pkg/reporter/reporter.go
index f37234a..0cf7171 100644
--- a/internal/output/reporter.go
+++ b/pkg/reporter/reporter.go
@@ -1,10 +1,11 @@
-package output
+package reporter
 
 import (
 	"fmt"
 	"io"
 	"strings"
 
+	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/pkg/models"
 )
 
@@ -60,11 +61,11 @@ func (r *Reporter) PrintText(msg string) {
 func (r *Reporter) PrintResult(vulnResult *models.VulnerabilityResults) error {
 	switch r.format {
 	case "json":
-		return PrintJSONResults(vulnResult, r.stdout)
+		return output.PrintJSONResults(vulnResult, r.stdout)
 	case "markdown":
-		PrintMarkdownTableResults(vulnResult, r.stdout)
+		output.PrintMarkdownTableResults(vulnResult, r.stdout)
 	case "table":
-		PrintTableResults(vulnResult, r.stdout)
+		output.PrintTableResults(vulnResult, r.stdout)
 	}
 
 	return nil


Test patch:
diff --git a/pkg/lockfile/parse-pnpm-lock_test.go b/pkg/lockfile/parse-pnpm-lock_test.go
index a572328..72638f4 100644
--- a/pkg/lockfile/parse-pnpm-lock_test.go
+++ b/pkg/lockfile/parse-pnpm-lock_test.go
@@ -54,6 +54,25 @@ func TestParsePnpmLock_OnePackage(t *testing.T) {
 	})
 }
 
+func TestParsePnpmLock_OnePackageV6Lockfile(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParsePnpmLock("fixtures/pnpm/one-package-v6-lockfile.yaml")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "acorn",
+			Version:   "8.7.0",
+			Ecosystem: lockfile.PnpmEcosystem,
+			CompareAs: lockfile.PnpmEcosystem,
+		},
+	})
+}
+
 func TestParsePnpmLock_OnePackageDev(t *testing.T) {
 	t.Parallel()
 
@@ -92,6 +111,25 @@ func TestParsePnpmLock_ScopedPackages(t *testing.T) {
 	})
 }
 
+func TestParsePnpmLock_ScopedPackagesV6Lockfile(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParsePnpmLock("fixtures/pnpm/scoped-packages-v6-lockfile.yaml")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "@typescript-eslint/types",
+			Version:   "5.57.1",
+			Ecosystem: lockfile.PnpmEcosystem,
+			CompareAs: lockfile.PnpmEcosystem,
+		},
+	})
+}
+
 func TestParsePnpmLock_PeerDependencies(t *testing.T) {
 	t.Parallel()
 
diff --git a/pkg/osvscanner/osvscanner_internal_test.go b/pkg/osvscanner/osvscanner_internal_test.go
index fa7f310..f45e38d 100644
--- a/pkg/osvscanner/osvscanner_internal_test.go
+++ b/pkg/osvscanner/osvscanner_internal_test.go
@@ -5,10 +5,10 @@ import (
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/internal/testutility"
 	"github.com/google/osv-scanner/pkg/config"
 	"github.com/google/osv-scanner/pkg/models"
+	"github.com/google/osv-scanner/pkg/reporter"
 )
 
 func Test_filterResults(t *testing.T) {
@@ -51,7 +51,7 @@ func Test_filterResults(t *testing.T) {
 		tt := tt // Reinitialize for t.Parallel()
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
-			r := output.NewVoidReporter()
+			r := reporter.NewVoidReporter()
 			// ConfigManager looks for osv-scanner.toml in the source path.
 			// Sources in the test input should point to files/folders in the text fixture folder for this to work correctly.
 			configManager := config.ConfigManager{
diff --git a/pkg/osvscanner/vulnerability_result_internal_test.go b/pkg/osvscanner/vulnerability_result_internal_test.go
index 79e860b..212b5ff 100644
--- a/pkg/osvscanner/vulnerability_result_internal_test.go
+++ b/pkg/osvscanner/vulnerability_result_internal_test.go
@@ -4,15 +4,15 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/google/osv-scanner/internal/output"
 	"github.com/google/osv-scanner/pkg/models"
 	"github.com/google/osv-scanner/pkg/osv"
+	"github.com/google/osv-scanner/pkg/reporter"
 )
 
 func Test_groupResponseBySource(t *testing.T) {
 	t.Parallel()
 	type args struct {
-		r            *output.Reporter
+		r            *reporter.Reporter
 		query        osv.BatchedQuery
 		resp         *osv.HydratedBatchedResponse
 		callAnalysis bool