Repository: google/osv-scanner
Pull request #337 (2023-04-12 05:34:36)

chore(deps): bump commonmarker from 0.23.8 to 0.23.9 in /docs
Bumps [commonmarker](https://github.com/gjtorikian/commonmarker) from 0.23.8 to 0.23.9.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/gjtorikian/commonmarker/releases">commonmarker's releases</a>.</em></p>
<blockquote>
<h2>v0.23.9</h2>
<h2>What's Changed</h2>
<ul>
<li>Update to 0.29.0.gfm.11 by <a href="https://github.com/anticomputer"><code>@​anticomputer</code></a> in <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/236">gjtorikian/commonmarker#236</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/gjtorikian/commonmarker/compare/v0.23.8...v0.23.9">https://github.com/gjtorikian/commonmarker/compare/v0.23.8...v0.23.9</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/gjtorikian/commonmarker/blob/main/CHANGELOG.md">commonmarker's changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2><a href="https://github.com/gjtorikian/commonmarker/tree/v1.0.0.pre9">v1.0.0.pre9</a> (2023-03-28)</h2>
<p><a href="https://github.com/gjtorikian/commonmarker/compare/v1.0.0.pre8...v1.0.0.pre9">Full Changelog</a></p>
<p><strong>Merged pull requests:</strong></p>
<ul>
<li>Updates from upstream <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/235">#235</a> (<a href="https://github.com/gjtorikian">gjtorikian</a>)</li>
<li>Bump comrak from 0.16.0 to 0.17.1 <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/234">#234</a> (<a href="https://github.com/apps/dependabot">dependabot[bot]</a>)</li>
<li>Bump magnus from 0.5.1 to 0.5.2 <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/233">#233</a> (<a href="https://github.com/apps/dependabot">dependabot[bot]</a>)</li>
<li>Add ability to load <code>tmtheme</code>s from a folder <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/232">#232</a> (<a href="https://github.com/gjtorikian">gjtorikian</a>)</li>
<li>Bump magnus from 0.5.0 to 0.5.1 <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/231">#231</a> (<a href="https://github.com/apps/dependabot">dependabot[bot]</a>)</li>
<li>Bump magnus from 0.4.4 to 0.5.0 <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/230">#230</a> (<a href="https://github.com/apps/dependabot">dependabot[bot]</a>)</li>
<li>Test the new integrated rb-sys <a href="https://redirect.github.com/gjtorikian/commonmarker/pull/228">#228</a> (<a href="https://github.com/gjtorikian">gjtorikian</a>)</li>
</ul>
<h2><a href="https://github.com/gjtorikian/commonmarker/tree/v1.0.0.pre8">v1.0.0.pre8</a> (2023-03-09)</h2>
<p><a href="https://github.com/gjtorikian/commonmarker/compare/v0.23.8...v1.0.0.pre8">Full Changelog</a></p>
<p><strong>Closed issues:</strong></p>
<ul>
<li>Something changed in how header anchors are named in the output HTML <a href="https://redirect.github.com/gjtorikian/commonmarker/issues/229">#229</a></li>
<li>Problem with CommonMarker on an Azure VM <a href="https://redirect.github.com/gjtorikian/commonmarker/issues/226">#226</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/gjtorikian/commonmarker/commit/42cfc90251353f9fceda91b884d0ded8d3da0bcf"><code>42cfc90</code></a> Merge pull request <a href="https://redirect.github.com/gjtorikian/commonmarker/issues/236">#236</a> from anticomputer/update-to-0.29.0.gfm.10</li>
<li><a href="https://github.com/gjtorikian/commonmarker/commit/d793fbf45106f17c1eb721293bcc8d098bd999dc"><code>d793fbf</code></a> Update cmark-upstream to <a href="https://github.com/github/cmark-gfm/commit/1e230827a">https://github.com/github/cmark-gfm/commit/1e230827a</a>...</li>
<li><a href="https://github.com/gjtorikian/commonmarker/commit/4e4588f2e0d34a21eeb79ec53459d248fdf56b39"><code>4e4588f</code></a> Update Makefile for export header consolidation</li>
<li><a href="https://github.com/gjtorikian/commonmarker/commit/2eb8ca8f2f4463448806663d72218cdcc0a9f38c"><code>2eb8ca8</code></a> Update cmark-upstream to <a href="https://github.com/github/cmark-gfm/commit/c8dcdc71c">https://github.com/github/cmark-gfm/commit/c8dcdc71c</a>...</li>
<li><a href="https://github.com/gjtorikian/commonmarker/commit/bbb49db7223d71ad9366309c358c7c09a4e1c5a8"><code>bbb49db</code></a> HtmlRenderer: don't nest &lt;strong&gt;</li>
<li><a href="https://github.com/gjtorikian/commonmarker/commit/f303e6bae7d83c76f3a929fa9e59ae2e3ac27081"><code>f303e6b</code></a> :gem: release 0.23.9</li>
<li><a href="https://github.com/gjtorikian/commonmarker/commit/d6fe4c8be4febe86f68c68884b08d21a66a2aff4"><code>d6fe4c8</code></a> Update cmark-upstream to <a href="https://github.com/github/cmark-gfm/commit/dcf6b3862">https://github.com/github/cmark-gfm/commit/dcf6b3862</a>...</li>
<li>See full diff in <a href="https://github.com/gjtorikian/commonmarker/compare/v0.23.8...v0.23.9">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=commonmarker&package-manager=bundler&previous-version=0.23.8&new-version=0.23.9)](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..7f99901 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)
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