Repository: google/osv-scanner
Pull request #665 (2023-11-17 05:22:37)

Set Go call analysis to default behaviour
Fix issue https://github.com/google/osv-scanner/issues/513
- Replace `experimental-call-analysis` with `call-analysis`. (`--call-analysis=all`, `--call-analysis=rust`)
- Adding a `--no-call-analysis` to disable call analysis. (`--no-call-analysis=all`, `--no-call-analysis=go`). This overrides `call-analysis`.
- Delete `call-analysis` from `experimental-config` in result report.
- Call analysis for non-experimental languages (e.g. go) is auto enabled.



codecov-commenter: ## [Codecov](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
Attention: `6 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`f37072f`)](https://app.codecov.io/gh/google/osv-scanner/commit/f37072f8fc388c8e9bd7bdf4a9bc2269e225a5b0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) 78.14% compared to head [(`ed8e71a`)](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) 78.20%.

| [Files](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) | Patch % | Lines |
|---|---|---|
| [pkg/osvscanner/osvscanner.go](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-cGtnL29zdnNjYW5uZXIvb3N2c2Nhbm5lci5nbw==) | 50.00% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) |
| [internal/sourceanalysis/sourceanalysis.go](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-aW50ZXJuYWwvc291cmNlYW5hbHlzaXMvc291cmNlYW5hbHlzaXMuZ28=) | 33.33% | [0 Missing and 2 partials :warning: ](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) |
| [pkg/osvscanner/vulnerability\_result.go](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google#diff-cGtnL29zdnNjYW5uZXIvdnVsbmVyYWJpbGl0eV9yZXN1bHQuZ28=) | 66.66% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #665      +/-   ##
==========================================
+ Coverage   78.14%   78.20%   +0.05%     
==========================================
  Files          81       81              
  Lines        5866     5872       +6     
==========================================
+ Hits         4584     4592       +8     
+ Misses       1082     1078       -4     
- Partials      200      202       +2     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/osv-scanner/pull/665?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google).

G-Rath: This looks like a good start but I don't think it's quite the right approach - I've written up [a comment on the issue](https://github.com/google/osv-scanner/issues/513#issuecomment-1817609642) on how I think this should be handled, as it's more of a design thing at this point

Patch:
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 9b0a8d1..488587b 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -47,7 +47,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 78a2a82..720c70b 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -48,7 +48,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
diff --git a/.github/workflows/lint-action/action.yml b/.github/workflows/lint-action/action.yml
index a0322ae..0eb6f39 100644
--- a/.github/workflows/lint-action/action.yml
+++ b/.github/workflows/lint-action/action.yml
@@ -22,6 +22,6 @@ runs:
       uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.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
+        version: v1.55.2
         # https://github.com/golangci/golangci-lint-action/issues/135
-        skip-pkg-cache: true
\ No newline at end of file
+        skip-pkg-cache: true
diff --git a/.golangci.yaml b/.golangci.yaml
index 752d96d..ad85722 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -30,6 +30,7 @@ linters:
     - testpackage      # will re-add later (another-rex)
     - goerr113         # will re-add later (another-rex)
     - nonamedreturns   # disagree with, for now (another-rex)
+    - goconst          # not everything should be a constant
   presets:
     - bugs
     - comment
diff --git a/cmd/osv-reporter/main.go b/cmd/osv-reporter/main.go
index 3db93cc..5c7e8b4 100644
--- a/cmd/osv-reporter/main.go
+++ b/cmd/osv-reporter/main.go
@@ -162,14 +162,8 @@ func run(args []string, stdout, stderr io.Writer) int {
 
 			// if vulnerability exists it should return error
 			if len(diffVulns.Results) > 0 {
-				// If any vulnerabilities are called, then we return VulnerabilitiesFoundErr
-				for _, vf := range diffVulns.Flatten() {
-					if vf.GroupInfo.IsCalled() {
-						return osvscanner.VulnerabilitiesFoundErr
-					}
-				}
 				// Otherwise return OnlyUncalledVulnerabilitiesFoundErr
-				return osvscanner.OnlyUncalledVulnerabilitiesFoundErr
+				return osvscanner.VulnerabilitiesFoundErr
 			}
 
 			return nil
@@ -184,11 +178,6 @@ func run(args []string, stdout, stderr io.Writer) int {
 			return 1
 		}
 
-		if errors.Is(err, osvscanner.OnlyUncalledVulnerabilitiesFoundErr) {
-			// TODO: Discuss whether to have a different exit code now that running call analysis is not default
-			return 2
-		}
-
 		if errors.Is(err, osvscanner.NoPackagesFoundErr) {
 			tableReporter.PrintError("No package sources found, --help for usage information.\n")
 			return 128
diff --git a/cmd/osv-scanner/callanalysis_parser.go b/cmd/osv-scanner/callanalysis_parser.go
new file mode 100644
index 0000000..2837fde
--- /dev/null
+++ b/cmd/osv-scanner/callanalysis_parser.go
@@ -0,0 +1,32 @@
+package main
+
+var stableCallAnalysisStates = map[string]bool{
+	"go":   true,
+	"rust": false,
+}
+
+// Creates a map to record if languages are enabled or disabled for call analysis.
+func createCallAnalysisStates(enabledCallAnalysis []string, disabledCallAnalysis []string) map[string]bool {
+	callAnalysisStates := make(map[string]bool)
+
+	for _, language := range enabledCallAnalysis {
+		callAnalysisStates[language] = true
+	}
+
+	for _, language := range disabledCallAnalysis {
+		callAnalysisStates[language] = false
+	}
+
+	enableAll, containsAll := callAnalysisStates["all"]
+	for language, isStable := range stableCallAnalysisStates {
+		if _, exists := callAnalysisStates[language]; !exists {
+			callAnalysisStates[language] = isStable || enableAll
+		}
+		if containsAll && !enableAll {
+			callAnalysisStates[language] = false
+		}
+	}
+	delete(callAnalysisStates, "all")
+
+	return callAnalysisStates
+}
diff --git a/cmd/osv-scanner/main.go b/cmd/osv-scanner/main.go
index 03f62c4..2c87486 100644
--- a/cmd/osv-scanner/main.go
+++ b/cmd/osv-scanner/main.go
@@ -99,7 +99,7 @@ func run(args []string, stdout, stderr io.Writer) int {
 			},
 			&cli.BoolFlag{
 				Name:  "experimental-call-analysis",
-				Usage: "attempt call analysis on code to detect only active vulnerabilities",
+				Usage: "[Deprecated] attempt call analysis on code to detect only active vulnerabilities",
 				Value: false,
 			},
 			&cli.BoolFlag{
@@ -107,6 +107,14 @@ func run(args []string, stdout, stderr io.Writer) int {
 				Usage: "also scan files that would be ignored by .gitignore",
 				Value: false,
 			},
+			&cli.StringSliceFlag{
+				Name:  "call-analysis",
+				Usage: "attempt call analysis on code to detect only active vulnerabilities",
+			},
+			&cli.StringSliceFlag{
+				Name:  "no-call-analysis",
+				Usage: "disables call graph analysis",
+			},
 			&cli.BoolFlag{
 				Name:  "experimental-local-db",
 				Usage: "checks for vulnerabilities using local databases",
@@ -159,6 +167,14 @@ func run(args []string, stdout, stderr io.Writer) int {
 				return err
 			}
 
+			var callAnalysisStates map[string]bool
+			if context.IsSet("experimental-call-analysis") {
+				callAnalysisStates = createCallAnalysisStates([]string{"all"}, context.StringSlice("no-call-analysis"))
+				r.PrintText("Warning: the experimental-call-analysis flag has been replaced. Please use the call-analysis and no-call-analysis flags instead.\n")
+			} else {
+				callAnalysisStates = createCallAnalysisStates(context.StringSlice("call-analysis"), context.StringSlice("no-call-analysis"))
+			}
+
 			vulnResult, err := osvscanner.DoScan(osvscanner.ScannerActions{
 				LockfilePaths:        context.StringSlice("lockfile"),
 				SBOMPaths:            context.StringSlice("sbom"),
@@ -168,9 +184,9 @@ func run(args []string, stdout, stderr io.Writer) int {
 				NoIgnore:             context.Bool("no-ignore"),
 				ConfigOverridePath:   context.String("config"),
 				DirectoryPaths:       context.Args().Slice(),
+				CallAnalysisStates:   callAnalysisStates,
 				ExperimentalScannerActions: osvscanner.ExperimentalScannerActions{
 					LocalDBPath:           context.String("experimental-local-db-path"),
-					CallAnalysis:          context.Bool("experimental-call-analysis"),
 					CompareLocally:        context.Bool("experimental-local-db"),
 					CompareOffline:        context.Bool("experimental-offline"),
 					ShowAllPackages:       context.Bool("experimental-all-packages"),
@@ -179,16 +195,10 @@ func run(args []string, stdout, stderr io.Writer) int {
 				},
 			}, r)
 
-			issueResultErr := errors.Join(
-				osvscanner.VulnerabilitiesFoundErr,
-				osvscanner.OnlyUncalledVulnerabilitiesFoundErr,
-				osvscanner.LicenseViolationsErr,
-				osvscanner.VulnerabilitiesFoundAndLicenseViolationsErr,
-				osvscanner.OnlyUncalledVulnerabilitiesFoundAndLicenseViolationsErr,
-			)
-			if err != nil && !errors.Is(issueResultErr, err) {
+			if err != nil && !errors.Is(err, osvscanner.VulnerabilitiesFoundErr) {
 				return err
 			}
+
 			if errPrint := r.PrintResult(&vulnResult); errPrint != nil {
 				return fmt.Errorf("failed to write output: %w", errPrint)
 			}
@@ -204,17 +214,7 @@ func run(args []string, stdout, stderr io.Writer) int {
 		}
 		switch {
 		case errors.Is(err, osvscanner.VulnerabilitiesFoundErr):
-			return 0b0001 // 1
-		case errors.Is(err, osvscanner.OnlyUncalledVulnerabilitiesFoundErr):
-			// TODO: Discuss whether to have a different exit code
-			// now that running call analysis is not default.
-			return 0b0010 // 2
-		case errors.Is(err, osvscanner.LicenseViolationsErr):
-			return 0b0100 // 4
-		case errors.Is(err, osvscanner.VulnerabilitiesFoundAndLicenseViolationsErr):
-			return 0b0101 // 5
-		case errors.Is(err, osvscanner.OnlyUncalledVulnerabilitiesFoundAndLicenseViolationsErr):
-			return 0b0110 // 6
+			return 1
 		case errors.Is(err, osvscanner.NoPackagesFoundErr):
 			r.PrintError("No package sources found, --help for usage information.\n")
 			return 128
diff --git a/docs/supported_languages_and_lockfiles.md b/docs/supported_languages_and_lockfiles.md
index cdc6734..f1d8479 100644
--- a/docs/supported_languages_and_lockfiles.md
+++ b/docs/supported_languages_and_lockfiles.md
@@ -32,6 +32,7 @@ A wide range of lockfiles are supported by utilizing this [lockfile package](htt
 | Javascript | `package-lock.json`<br>`pnpm-lock.yaml`<br>`yarn.lock`|
 | PHP        | `composer.lock`|
 | Python     | `Pipfile.lock`<br>`poetry.lock`<br>`requirements.txt`[\*](https://github.com/google/osv-scanner/issues/34) |
+| R          | `renv.lock` |
 | Ruby       | `Gemfile.lock`|
 | Rust       | `Cargo.lock`|
 
@@ -102,4 +103,4 @@ Once you extracted your own dependency information, place it in a `osv-scanner.j
 Then pass this to `osv-scanner` with this:
 ```
 osv-scanner --lockfile osv-scanner:/path/to/osv-scanner.json
-```
\ No newline at end of file
+```
diff --git a/go.mod b/go.mod
index a4b435d..b47c9dd 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/google/osv-scanner
 go 1.20
 
 require (
-	deps.dev/api/v3alpha v0.0.0-20231029115524-a2ccd033c6d4
+	deps.dev/api/v3alpha v0.0.0-20231114023923-e40c4d5c34e5
 	github.com/BurntSushi/toml v1.3.2
 	github.com/CycloneDX/cyclonedx-go v0.7.2
 	github.com/go-git/go-billy/v5 v5.5.0
@@ -17,10 +17,10 @@ require (
 	github.com/package-url/packageurl-go v0.1.2
 	github.com/spdx/tools-golang v0.5.3
 	github.com/urfave/cli/v2 v2.25.7
-	golang.org/x/exp v0.0.0-20231006140011-7918f672742d
+	golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
 	golang.org/x/mod v0.14.0
 	golang.org/x/sync v0.5.0
-	golang.org/x/term v0.13.0
+	golang.org/x/term v0.14.0
 	golang.org/x/vuln v1.0.1
 	google.golang.org/grpc v1.59.0
 	gopkg.in/yaml.v3 v3.0.1
@@ -52,11 +52,11 @@ require (
 	github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
-	golang.org/x/crypto v0.14.0 // indirect
-	golang.org/x/net v0.17.0 // indirect
-	golang.org/x/sys v0.13.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
-	golang.org/x/tools v0.14.0 // indirect
+	golang.org/x/crypto v0.15.0 // indirect
+	golang.org/x/net v0.18.0 // indirect
+	golang.org/x/sys v0.14.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/tools v0.15.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/go.sum b/go.sum
index d8c764e..fe75793 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
 dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-deps.dev/api/v3alpha v0.0.0-20231029115524-a2ccd033c6d4 h1:y/JsYdh5JCgcI1WtxfTYrhj48YK8DsZUHFVVtZT4Qwc=
-deps.dev/api/v3alpha v0.0.0-20231029115524-a2ccd033c6d4/go.mod h1:uRN72FJn1F0FD/2ZYUOqdyFMu8VUsyHxvmZAMW30/DA=
+deps.dev/api/v3alpha v0.0.0-20231114023923-e40c4d5c34e5 h1:Vvh14FIzt0+LaLWn2l09FKQbdXBVxIjtPbjFiykXYlI=
+deps.dev/api/v3alpha v0.0.0-20231114023923-e40c4d5c34e5/go.mod h1:uRN72FJn1F0FD/2ZYUOqdyFMu8VUsyHxvmZAMW30/DA=
 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ=
@@ -136,10 +136,10 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
 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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
@@ -153,8 +153,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
+golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -174,15 +174,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
+golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -192,14 +192,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 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.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
-golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
+golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
 golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
 golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/output/fixtures/sarif-commit-output_windows.md b/internal/output/fixtures/sarif-commit-output_windows.md
new file mode 100644
index 0000000..42f5da8
--- /dev/null
+++ b/internal/output/fixtures/sarif-commit-output_windows.md
@@ -0,0 +1,52 @@
+**Your dependency is vulnerable to [OSV-2023-72](https://osv.dev/list?q=OSV-2023-72)**.
+
+## [OSV-2023-72](https://osv.dev/vulnerability/OSV-2023-72)
+
+<details>
+<summary>Details</summary>
+
+> OSS-Fuzz report: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=56057
+> 
+> ```
+> Crash type: Heap-buffer-overflow WRITE 4
+> Crash state:
+> perfetto::trace_processor::TrackEventParser::ParseTrackDescriptor
+> perfetto::trace_processor::TrackEventModule::ParseTracePacketData
+> perfetto::trace_processor::ProtoTraceParser::ParseTracePacket
+> ```
+> 
+
+</details>
+
+---
+
+### Affected Packages
+
+| Source | Package Name | Package Version |
+| --- | --- | --- |
+| lockfile:/usr/local/google/home/rexpan/Documents/Project/engine/deps_flatten.txt | https://fuchsia.googlesource.com/third_party/android.googlesource.com/platform/external/perfetto | b8da07095979310818f0efde2ef3c69ea70d62c5 |
+
+## Remediation
+
+To fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.
+
+### Fixed Versions
+
+| Vulnerability ID | Package Name | Fixed Version |
+| --- | --- | --- |
+| OSV-2023-72 | perfetto | 9a7f09383dd39f19e662d428321ca708a2a600a3 |
+
+If you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an
+`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.
+
+See the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/
+
+Add or append these values to the following config files to ignore this vulnerability:
+
+`\usr\local\google\home\rexpan\Documents\Project\engine/osv-scanner.toml`
+
+```
+[[IgnoredVulns]]
+id = "OSV-2023-72"
+reason = "Your reason for ignoring this vulnerability"
+```
diff --git a/internal/output/fixtures/sarif-output_windows.md b/internal/output/fixtures/sarif-output_windows.md
new file mode 100644
index 0000000..858a777
--- /dev/null
+++ b/internal/output/fixtures/sarif-output_windows.md
@@ -0,0 +1,130 @@
+**Your dependency is vulnerable to [CVE-2022-24713](https://osv.dev/list?q=CVE-2022-24713)**
+(Also published as: [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013), [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8), ).
+
+## [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013)
+
+<details>
+<summary>Details</summary>
+
+> The Rust Security Response WG was notified that the `regex` crate did not
+> properly limit the complexity of the regular expressions (regex) it parses. An
+> attacker could use this security issue to perform a denial of service, by
+> sending a specially crafted regex to a service accepting untrusted regexes. No
+> known vulnerability is present when parsing untrusted input with trusted
+> regexes.
+> 
+> This issue has been assigned CVE-2022-24713. The severity of this vulnerability
+> is "high" when the `regex` crate is used to parse untrusted regexes. Other uses
+> of the `regex` crate are not affected by this vulnerability.
+> 
+> ## Overview
+> 
+> The `regex` crate features built-in mitigations to prevent denial of service
+> attacks caused by untrusted regexes, or untrusted input matched by trusted
+> regexes. Those (tunable) mitigations already provide sane defaults to prevent
+> attacks. This guarantee is documented and it's considered part of the crate's
+> API.
+> 
+> Unfortunately a bug was discovered in the mitigations designed to prevent
+> untrusted regexes to take an arbitrary amount of time during parsing, and it's
+> possible to craft regexes that bypass such mitigations. This makes it possible
+> to perform denial of service attacks by sending specially crafted regexes to
+> services accepting user-controlled, untrusted regexes.
+> 
+> ## Affected versions
+> 
+> All versions of the `regex` crate before or equal to 1.5.4 are affected by this
+> issue. The fix is include starting from  `regex` 1.5.5.
+> 
+> ## Mitigations
+> 
+> We recommend everyone accepting user-controlled regexes to upgrade immediately
+> to the latest version of the `regex` crate.
+> 
+> Unfortunately there is no fixed set of problematic regexes, as there are
+> practically infinite regexes that could be crafted to exploit this
+> vulnerability. Because of this, we do not recommend denying known problematic
+> regexes.
+> 
+> ## Acknowledgements
+> 
+> We want to thank Addison Crump for responsibly disclosing this to us according
+> to the [Rust security policy][1], and for helping review the fix.
+> 
+> We also want to thank Andrew Gallant for developing the fix, and Pietro Albini
+> for coordinating the disclosure and writing this advisory.
+> 
+> [1]: https://www.rust-lang.org/policies/security
+
+</details>
+
+## [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8)
+
+<details>
+<summary>Details</summary>
+
+> > This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.
+> 
+> [advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw
+> 
+> The Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.
+> 
+> This issue has been assigned CVE-2022-24713. The severity of this vulnerability is "high" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.
+> 
+> ## Overview
+> 
+> The `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.
+> 
+> Unfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.
+> 
+> ## Affected versions
+> 
+> All versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.
+> 
+> ## Mitigations
+> 
+> We recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.
+> 
+> Unfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.
+> 
+> ## Acknowledgements
+> 
+> We want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.
+> 
+> We also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.
+
+</details>
+
+---
+
+### Affected Packages
+
+| Source | Package Name | Package Version |
+| --- | --- | --- |
+| lockfile:/path/to/sub-rust-project/Cargo.lock | regex | 1.5.1 |
+
+## Remediation
+
+To fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.
+
+### Fixed Versions
+
+| Vulnerability ID | Package Name | Fixed Version |
+| --- | --- | --- |
+| GHSA-m5pq-gvj9-9vr8 | regex | 1.5.5 |
+| RUSTSEC-2022-0013 | regex | 1.5.5 |
+
+If you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an
+`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.
+
+See the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/
+
+Add or append these values to the following config files to ignore this vulnerability:
+
+`\path\to\sub-rust-project/osv-scanner.toml`
+
+```
+[[IgnoredVulns]]
+id = "CVE-2022-24713"
+reason = "Your reason for ignoring this vulnerability"
+```
diff --git a/internal/sbom/sbom.go b/internal/sbom/sbom.go
index a35a3d6..f6b5b81 100644
--- a/internal/sbom/sbom.go
+++ b/internal/sbom/sbom.go
@@ -11,16 +11,16 @@ type Identifier struct {
 	PURL string
 }
 
-// SBOMReader is an interface for all SBOM providers.
-type SBOMReader interface {
+// Reader is an interface for all SBOM providers.
+type Reader interface {
 	Name() string
-	// Checks if the file path is a standard recognized file name
-	MatchesRecognizedFileNames(string) bool
-	GetPackages(io.ReadSeeker, func(Identifier) error) error
+	// MatchesRecognizedFileNames checks if the file path is a standard recognized file name
+	MatchesRecognizedFileNames(path string) bool
+	GetPackages(r io.ReadSeeker, callback func(Identifier) error) error
 }
 
 var (
-	Providers = []SBOMReader{
+	Providers = []Reader{
 		&SPDX{},
 		&CycloneDX{},
 	}
diff --git a/internal/semantic/fixtures/cran-versions-generated.txt b/internal/semantic/fixtures/cran-versions-generated.txt
new file mode 100644
index 0000000..5ec1f92
--- /dev/null
+++ b/internal/semantic/fixtures/cran-versions-generated.txt
@@ -0,0 +1,57 @@
+0.1.0 < 0.1.1
+0.1.1 < 1.0.0
+1.0.0 < 1.1.0
+1.1.0 < 1.2.0
+1.2.0 < 1.4.1
+0.9.12 < 0.9.13
+0.9.13 < 0.9.14
+0.9.14 < 0.9.15
+0.9.15 < 0.9.16
+0.9.16 < 0.9.17
+0.9.17 < 0.9.18
+0.9.18 < 0.9.19
+0.9.19 < 0.9.20
+0.9.20 < 0.9.21
+0.9.21 < 0.9.22
+0.9.22 < 1.0
+1.0 < 1.1
+1.1 < 1.2
+1.2 < 1.3
+1.3 < 1.4
+1.4 < 1.5
+1.5 < 1.6
+1.6 < 1.6.1
+1.6.1 < 1.7.0
+1.7.0 < 1.7.1
+1.7.1 < 1.7.2
+1.7.2 < 1.7.3
+1.7.3 < 1.8.0
+1.8.0 < 1.8.1
+1.8.1 < 1.8.2
+1.8.2 < 1.8.3
+1.8.3 < 1.8.4
+1.8.4 < 1.8.5
+1.8.5 < 1.8.6
+1.8.6 < 1.8.7
+0.7.1 < 1.0.0
+1.0.0 < 1.0.1
+1.0.1 < 1.1.1
+1.1.1 < 1.1.2
+1.1.2 < 1.2.1
+1.2.1 < 1.2.2
+0.1.1 < 0.2.0
+0.2.0 < 0.2.1
+0.2.1 < 1.0.0
+0.2 < 0.4
+0.4 < 0.5
+0.5 < 0.6
+0.6 < 0.7
+0.7 < 0.8
+0.8 < 0.9
+0.9 < 1.0
+1.2 < 1.4
+1.6 < 1.7
+1.7 < 1.8
+1.8 < 1.8.0
+1.8.1 < 1.9
+1.9 < 1.9.0
diff --git a/internal/semantic/fixtures/cran-versions.txt b/internal/semantic/fixtures/cran-versions.txt
new file mode 100644
index 0000000..1a8d08b
--- /dev/null
+++ b/internal/semantic/fixtures/cran-versions.txt
@@ -0,0 +1,34 @@
+0.01 < 0.1-0
+0.01.0 = 0.1-0
+0.9 < 0.75
+
+1.0-0 < 1.1-0
+
+# ---
+0.1.0.0 < 0.2.0
+0.1.0 < 0.2.0.0
+0.1.0 < 0.1.1.0
+0.1.0 < 0.1.0.0
+0.1.0 > 0.0.1.0
+
+# https://cran.r-project.org/src/contrib/Archive/abctools/
+0.1-2 < 0.2-2
+0.2 < 0.3-2
+1.0 < 1.0.1
+1.0.2 < 1.0.3
+
+# https://cran.r-project.org/src/contrib/Archive/AntibodyTiters/
+0.1.4 < 0.1.18
+
+# https://cran.r-project.org/src/contrib/Archive/AdaptGauss/
+1.0 < 1.1.0
+1.5 < 1.5.4
+
+# https://cran.r-project.org/src/contrib/Archive/AcceptanceSampling/
+0.1-1 < 0.1-4
+1.0-0 < 1.0-1
+
+# https://cran.r-project.org/src/contrib/Archive/DHARMa/
+0.1.0 < 0.1.0.0
+0.3.2.0 > 0.3.2
+0.3.2.0 < 0.3.3.0
diff --git a/internal/semantic/parse.go b/internal/semantic/parse.go
index 78f2f83..e19ec60 100644
--- a/internal/semantic/parse.go
+++ b/internal/semantic/parse.go
@@ -44,6 +44,8 @@ func Parse(str string, ecosystem Ecosystem) (Version, error) {
 		return parseSemverVersion(str), nil
 	case "ConanCenter":
 		return parseSemverVersion(str), nil
+	case "CRAN":
+		return parseCRANVersion(str), nil
 	}
 
 	return nil, fmt.Errorf("%w %s", ErrUnsupportedEcosystem, ecosystem)
diff --git a/internal/semantic/version-cran.go b/internal/semantic/version-cran.go
new file mode 100644
index 0000000..19fa37c
--- /dev/null
+++ b/internal/semantic/version-cran.go
@@ -0,0 +1,54 @@
+package semantic
+
+import (
+	"math/big"
+	"strings"
+)
+
+// CRANVersion is the representation of a version of a package that is held
+// in the CRAN ecosystem (https://cran.r-project.org/).
+//
+// A version is a sequence of at least two non-negative integers separated by
+// either a period or a dash.
+//
+// See https://astrostatistics.psu.edu/su07/R/html/base/html/package_version.html
+type CRANVersion struct {
+	components Components
+}
+
+func (v CRANVersion) Compare(w CRANVersion) int {
+	if diff := v.components.Cmp(w.components); diff != 0 {
+		return diff
+	}
+
+	// versions are only equal if they also have the same number of components,
+	// otherwise the longer one is considered greater
+	if len(v.components) == len(w.components) {
+		return 0
+	}
+
+	if len(v.components) > len(w.components) {
+		return 1
+	}
+
+	return -1
+}
+
+func (v CRANVersion) CompareStr(str string) int {
+	return v.Compare(parseCRANVersion(str))
+}
+
+func parseCRANVersion(str string) CRANVersion {
+	// dashes and periods have the same weight, so we can just normalize to periods
+	parts := strings.Split(strings.ReplaceAll(str, "-", "."), ".")
+
+	components := make(Components, 0, len(parts))
+
+	for _, s := range parts {
+		v, _ := new(big.Int).SetString(s, 10)
+
+		components = append(components, v)
+	}
+
+	return CRANVersion{components}
+}
diff --git a/internal/sourceanalysis/go.go b/internal/sourceanalysis/go.go
index 4c8d119..d4b8623 100644
--- a/internal/sourceanalysis/go.go
+++ b/internal/sourceanalysis/go.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"os/exec"
 	"path/filepath"
 
 	"github.com/google/osv-scanner/internal/sourceanalysis/govulncheck"
@@ -17,6 +18,13 @@ import (
 )
 
 func goAnalysis(r reporter.Reporter, pkgs []models.PackageVulns, source models.SourceInfo) {
+	cmd := exec.Command("go", "version")
+	_, err := cmd.Output()
+	if err != nil {
+		r.PrintText("Skipping call analysis on Go code since Go is not installed.\n")
+		return
+	}
+
 	vulns, vulnsByID := vulnsFromAllPkgs(pkgs)
 	res, err := runGovulncheck(filepath.Dir(source.Path), vulns)
 	if err != nil {
diff --git a/internal/sourceanalysis/sourceanalysis.go b/internal/sourceanalysis/sourceanalysis.go
index 745b321..1c9f5d8 100644
--- a/internal/sourceanalysis/sourceanalysis.go
+++ b/internal/sourceanalysis/sourceanalysis.go
@@ -25,13 +25,13 @@ 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 reporter.Reporter, source models.SourceInfo, pkgs []models.PackageVulns) {
+func Run(r reporter.Reporter, source models.SourceInfo, pkgs []models.PackageVulns, callAnalysis map[string]bool) {
 	// GoVulnCheck
-	if source.Type == "lockfile" && filepath.Base(source.Path) == "go.mod" {
+	if source.Type == "lockfile" && filepath.Base(source.Path) == "go.mod" && callAnalysis["go"] {
 		goAnalysis(r, pkgs, source)
 	}
 
-	if source.Type == "lockfile" && filepath.Base(source.Path) == "Cargo.lock" {
+	if source.Type == "lockfile" && filepath.Base(source.Path) == "Cargo.lock" && callAnalysis["rust"] {
 		rustAnalysis(r, pkgs, source)
 	}
 }
diff --git a/pkg/depsdev/license.go b/pkg/depsdev/license.go
index 3a99ac4..8ec5e84 100644
--- a/pkg/depsdev/license.go
+++ b/pkg/depsdev/license.go
@@ -81,8 +81,8 @@ func MakeVersionRequests(queries []*depsdevpb.GetVersionRequest) ([][]models.Lic
 
 				return err
 			}
-			ls := make([]models.License, len(resp.Licenses))
-			for j, license := range resp.Licenses {
+			ls := make([]models.License, len(resp.GetLicenses()))
+			for j, license := range resp.GetLicenses() {
 				ls[j] = models.License(license)
 			}
 			if len(ls) == 0 {
diff --git a/pkg/grouper/grouper.go b/pkg/grouper/grouper.go
index 2974042..7e42b27 100644
--- a/pkg/grouper/grouper.go
+++ b/pkg/grouper/grouper.go
@@ -51,8 +51,10 @@ func Group(vulns []IDAliases) []models.GroupInfo {
 
 	// Extract groups into the final result structure.
 	extractedGroups := map[int][]string{}
+	extractedAliases := map[int][]string{}
 	for i, gid := range groups {
 		extractedGroups[gid] = append(extractedGroups[gid], vulns[i].ID)
+		extractedAliases[gid] = append(extractedAliases[gid], vulns[i].Aliases...)
 	}
 
 	// Sort by group ID to maintain stable order for tests.
@@ -63,7 +65,15 @@ func Group(vulns []IDAliases) []models.GroupInfo {
 	for _, key := range sortedKeys {
 		// Sort the strings so they are always in the same order
 		sort.Strings(extractedGroups[key])
-		result = append(result, models.GroupInfo{IDs: extractedGroups[key]})
+
+		// Add IDs to aliases
+		extractedAliases[key] = append(extractedAliases[key], extractedGroups[key]...)
+
+		// Dedup entries
+		sort.Strings(extractedAliases[key])
+		extractedAliases[key] = slices.Compact(extractedAliases[key])
+
+		result = append(result, models.GroupInfo{IDs: extractedGroups[key], Aliases: extractedAliases[key]})
 	}
 
 	return result
diff --git a/pkg/lockfile/apk-installed.go b/pkg/lockfile/apk-installed.go
index 92a7ae6..413eba7 100644
--- a/pkg/lockfile/apk-installed.go
+++ b/pkg/lockfile/apk-installed.go
@@ -3,7 +3,6 @@ package lockfile
 import (
 	"bufio"
 	"fmt"
-	"os"
 	"sort"
 	"strings"
 )
@@ -34,7 +33,7 @@ func groupApkPackageLines(scanner *bufio.Scanner) [][]string {
 	return groups
 }
 
-func parseApkPackageGroup(group []string, pathToLockfile string) PackageDetails {
+func parseApkPackageGroup(group []string) PackageDetails {
 	var pkg = PackageDetails{
 		Ecosystem: AlpineEcosystem,
 		CompareAs: AlpineEcosystem,
@@ -52,20 +51,6 @@ func parseApkPackageGroup(group []string, pathToLockfile string) PackageDetails
 		}
 	}
 
-	if pkg.Version == "" {
-		pkgPrintName := pkg.Name
-		if pkgPrintName == "" {
-			pkgPrintName = unknownPkgName
-		}
-
-		_, _ = fmt.Fprintf(
-			os.Stderr,
-			"warning: malformed APK installed file. Found no version number in record. Package %s. File: %s\n",
-			pkgPrintName,
-			pathToLockfile,
-		)
-	}
-
 	return pkg
 }
 
@@ -87,15 +72,9 @@ func (e ApkInstalledExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 	packages := make([]PackageDetails, 0, len(packageGroups))
 
 	for _, group := range packageGroups {
-		pkg := parseApkPackageGroup(group, f.Path())
+		pkg := parseApkPackageGroup(group)
 
 		if pkg.Name == "" {
-			_, _ = fmt.Fprintf(
-				os.Stderr,
-				"warning: malformed APK installed file. Found no package name in record. File: %s\n",
-				f.Path(),
-			)
-
 			continue
 		}
 
diff --git a/pkg/lockfile/dpkg-status.go b/pkg/lockfile/dpkg-status.go
index 6afadd2..404ee1b 100644
--- a/pkg/lockfile/dpkg-status.go
+++ b/pkg/lockfile/dpkg-status.go
@@ -3,7 +3,6 @@ package lockfile
 import (
 	"bufio"
 	"fmt"
-	"os"
 	"sort"
 	"strings"
 
@@ -48,7 +47,7 @@ func parseSourceField(source string) (string, string) {
 	return strings.TrimSpace(source), ""
 }
 
-func parseDpkgPackageGroup(group []string, pathToLockfile string) PackageDetails {
+func parseDpkgPackageGroup(group []string) PackageDetails {
 	var pkg = PackageDetails{
 		Ecosystem: DebianEcosystem,
 		CompareAs: DebianEcosystem,
@@ -62,14 +61,8 @@ func parseDpkgPackageGroup(group []string, pathToLockfile string) PackageDetails
 		case strings.HasPrefix(line, "Status:"):
 			status := strings.TrimPrefix(line, "Status:")
 			tokens := strings.Fields(status)
-			// Staus field is malformed. Expected: "Status: Want Flag Status"
+			// Status field is malformed. Expected: "Status: Want Flag Status"
 			if len(tokens) != 3 {
-				_, _ = fmt.Fprintf(
-					os.Stderr,
-					"warning: malformed DPKG status file. Found no valid \"Source\" field. File: %s\n",
-					pathToLockfile,
-				)
-
 				return PackageDetails{}
 			}
 			// Status field has correct number of fields but package is not installed or has only config files left
@@ -105,20 +98,6 @@ func parseDpkgPackageGroup(group []string, pathToLockfile string) PackageDetails
 		}
 	}
 
-	if pkg.Version == "" {
-		pkgPrintName := pkg.Name
-		if pkgPrintName == "" {
-			pkgPrintName = unknownPkgName
-		}
-
-		_, _ = fmt.Fprintf(
-			os.Stderr,
-			"warning: malformed DPKG status file. Found no version number in record. Package %s. File: %s\n",
-			pkgPrintName,
-			pathToLockfile,
-		)
-	}
-
 	return pkg
 }
 
@@ -139,7 +118,7 @@ func (e DpkgStatusExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 	packages := make([]PackageDetails, 0, len(packageGroups))
 
 	for _, group := range packageGroups {
-		pkg := parseDpkgPackageGroup(group, f.Path())
+		pkg := parseDpkgPackageGroup(group)
 
 		// PackageDetails does not contain any field that represent a "not installed" state
 		// To manage this state and avoid false positives, empty struct means "not installed" so skip it
@@ -148,12 +127,6 @@ func (e DpkgStatusExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 		}
 
 		if pkg.Name == "" {
-			_, _ = fmt.Fprintf(
-				os.Stderr,
-				"warning: malformed DPKG status file. Found no package name in record. File: %s\n",
-				f.Path(),
-			)
-
 			continue
 		}
 
diff --git a/pkg/lockfile/ecosystems.go b/pkg/lockfile/ecosystems.go
index 861373a..b6dbd96 100644
--- a/pkg/lockfile/ecosystems.go
+++ b/pkg/lockfile/ecosystems.go
@@ -13,6 +13,7 @@ func KnownEcosystems() []Ecosystem {
 		PipEcosystem,
 		PubEcosystem,
 		ConanEcosystem,
+		CRANEcosystem,
 		// Disabled temporarily,
 		// see https://github.com/google/osv-scanner/pull/128 discussion for additional context
 		// AlpineEcosystem,
diff --git a/pkg/lockfile/extractor.go b/pkg/lockfile/extractor.go
index f3349e4..7e05765 100644
--- a/pkg/lockfile/extractor.go
+++ b/pkg/lockfile/extractor.go
@@ -18,7 +18,7 @@ type DepFile interface {
 	// current DepFile if the provided path is relative.
 	//
 	// If the path is an absolute path, then it is opened absolutely.
-	Open(string) (NestedDepFile, error)
+	Open(path string) (NestedDepFile, error)
 
 	Path() string
 }
diff --git a/pkg/lockfile/fixtures/renv/empty.lock b/pkg/lockfile/fixtures/renv/empty.lock
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/pkg/lockfile/fixtures/renv/empty.lock
@@ -0,0 +1,1 @@
+{}
diff --git a/pkg/lockfile/fixtures/renv/not-json.txt b/pkg/lockfile/fixtures/renv/not-json.txt
new file mode 100644
index 0000000..3ae3a21
--- /dev/null
+++ b/pkg/lockfile/fixtures/renv/not-json.txt
@@ -0,0 +1,1 @@
+this is not json!
diff --git a/pkg/lockfile/fixtures/renv/one-package.lock b/pkg/lockfile/fixtures/renv/one-package.lock
new file mode 100644
index 0000000..d215653
--- /dev/null
+++ b/pkg/lockfile/fixtures/renv/one-package.lock
@@ -0,0 +1,17 @@
+{
+  "R": {
+    "Version": "2.15.2",
+    "Repositories": []
+  },
+  "Packages": {
+    "morning": {
+      "Package": "morning",
+      "Version": "0.1.0",
+      "Repository": "CRAN",
+      "Requirements": [
+        "coffee",
+        "toast"
+      ]
+    }
+  }
+}
diff --git a/pkg/lockfile/fixtures/renv/two-packages.lock b/pkg/lockfile/fixtures/renv/two-packages.lock
new file mode 100644
index 0000000..a0a4254
--- /dev/null
+++ b/pkg/lockfile/fixtures/renv/two-packages.lock
@@ -0,0 +1,27 @@
+{
+  "R": {
+    "Version": "4.2.3",
+    "Repositories": [
+      {
+        "Name": "CRAN",
+        "URL": "https://cloud.r-project.org"
+      }
+    ]
+  },
+  "Packages": {
+    "markdown": {
+      "Package": "markdown",
+      "Version": "1.0",
+      "Source": "Repository",
+      "Repository": "CRAN",
+      "Hash": "4584a57f565dd7987d59dda3a02cfb41"
+    },
+    "mime": {
+      "Package": "mime",
+      "Version": "0.7",
+      "Source": "Repository",
+      "Repository": "CRAN",
+      "Hash": "908d95ccbfd1dd274073ef07a7c93934"
+    }
+  }
+}
diff --git a/pkg/lockfile/fixtures/renv/with-bioconductor.lock b/pkg/lockfile/fixtures/renv/with-bioconductor.lock
new file mode 100644
index 0000000..40bd310
--- /dev/null
+++ b/pkg/lockfile/fixtures/renv/with-bioconductor.lock
@@ -0,0 +1,29 @@
+{
+  "R": {
+    "Version": "4.1.0",
+    "Repositories": [
+      {
+        "Name": "CRAN",
+        "URL": "https://cran.rstudio.com"
+      }
+    ]
+  },
+  "Bioconductor": {
+    "Version": "3.13"
+  },
+  "Packages": {
+    "BH": {
+      "Package": "BH",
+      "Version": "1.75.0-0",
+      "Source": "Repository",
+      "Repository": "CRAN",
+      "Hash": "e4c04affc2cac20c8fec18385cd14691"
+    },
+    "BSgenome": {
+      "Package": "BSgenome",
+      "Version": "1.60.0",
+      "Source": "Bioconductor",
+      "Hash": "bc39f66b170caed3ea67c03eb6b4b55c"
+    }
+  }
+}
diff --git a/pkg/lockfile/fixtures/renv/with-mixed-sources.lock b/pkg/lockfile/fixtures/renv/with-mixed-sources.lock
new file mode 100644
index 0000000..6ba0b1b
--- /dev/null
+++ b/pkg/lockfile/fixtures/renv/with-mixed-sources.lock
@@ -0,0 +1,35 @@
+{
+  "R": {
+    "Version": "4.3.1",
+    "Repositories": [
+      {
+        "Name": "CRAN",
+        "URL": "https://cloud.r-project.org"
+      }
+    ]
+  },
+  "Packages": {
+    "markdown": {
+      "Package": "markdown",
+      "Version": "1.0",
+      "Source": "Repository",
+      "Repository": "CRAN",
+      "Hash": "4584a57f565dd7987d59dda3a02cfb41"
+    },
+    "mime": {
+      "Package": "mime",
+      "Version": "0.12.1",
+      "Source": "GitHub",
+      "RemoteType": "github",
+      "RemoteHost": "api.github.com",
+      "RemoteUsername": "yihui",
+      "RemoteRepo": "mime",
+      "RemoteRef": "main",
+      "RemoteSha": "1763e0dcb72fb58d97bab97bb834fc71f1e012bc",
+      "Requirements": [
+        "tools"
+      ],
+      "Hash": "c2772b6269924dad6784aaa1d99dbb86"
+    }
+  }
+}
diff --git a/pkg/lockfile/fixtures/renv/without-repository.lock b/pkg/lockfile/fixtures/renv/without-repository.lock
new file mode 100644
index 0000000..1812d22
--- /dev/null
+++ b/pkg/lockfile/fixtures/renv/without-repository.lock
@@ -0,0 +1,16 @@
+{
+  "R": {
+    "Version": "2.15.2",
+    "Repositories": []
+  },
+  "Packages": {
+    "morning": {
+      "Package": "morning",
+      "Version": "0.1.0",
+      "Requirements": [
+        "coffee",
+        "toast"
+      ]
+    }
+  }
+}
diff --git a/pkg/lockfile/parse-gradle-lock.go b/pkg/lockfile/parse-gradle-lock.go
index 2bfa13f..5413768 100644
--- a/pkg/lockfile/parse-gradle-lock.go
+++ b/pkg/lockfile/parse-gradle-lock.go
@@ -3,7 +3,6 @@ package lockfile
 import (
 	"bufio"
 	"fmt"
-	"os"
 	"path/filepath"
 	"strings"
 )
@@ -63,7 +62,6 @@ func (e GradleLockExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 
 		pkg, err := parseToGradlePackageDetail(lockLine)
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "failed to extract from lockline: %s\n", err.Error())
 			continue
 		}
 
diff --git a/pkg/lockfile/parse-renv-lock.go b/pkg/lockfile/parse-renv-lock.go
new file mode 100644
index 0000000..c398953
--- /dev/null
+++ b/pkg/lockfile/parse-renv-lock.go
@@ -0,0 +1,64 @@
+package lockfile
+
+import (
+	"encoding/json"
+	"fmt"
+	"path/filepath"
+)
+
+type RenvPackage struct {
+	Package    string `json:"Package"`
+	Version    string `json:"Version"`
+	Repository string `json:"Repository"`
+}
+
+type RenvLockfile struct {
+	Packages map[string]RenvPackage `json:"Packages"`
+}
+
+const CRANEcosystem Ecosystem = "CRAN"
+
+type RenvLockExtractor struct{}
+
+func (e RenvLockExtractor) ShouldExtract(path string) bool {
+	return filepath.Base(path) == "renv.lock"
+}
+
+func (e RenvLockExtractor) Extract(f DepFile) ([]PackageDetails, error) {
+	var parsedLockfile *RenvLockfile
+
+	err := json.NewDecoder(f).Decode(&parsedLockfile)
+
+	if err != nil {
+		return []PackageDetails{}, fmt.Errorf("could not extract from %s: %w", f.Path(), err)
+	}
+
+	packages := make([]PackageDetails, 0, len(parsedLockfile.Packages))
+
+	for _, pkg := range parsedLockfile.Packages {
+		// currently we only support CRAN
+		if pkg.Repository != string(CRANEcosystem) {
+			continue
+		}
+
+		packages = append(packages, PackageDetails{
+			Name:      pkg.Package,
+			Version:   pkg.Version,
+			Ecosystem: CRANEcosystem,
+			CompareAs: CRANEcosystem,
+		})
+	}
+
+	return packages, nil
+}
+
+var _ Extractor = RenvLockExtractor{}
+
+//nolint:gochecknoinits
+func init() {
+	registerExtractor("renv.lock", RenvLockExtractor{})
+}
+
+func ParseRenvLock(pathToLockfile string) ([]PackageDetails, error) {
+	return extractFromFile(pathToLockfile, RenvLockExtractor{})
+}
diff --git a/pkg/lockfile/parse.go b/pkg/lockfile/parse.go
index b10a72f..5e09984 100644
--- a/pkg/lockfile/parse.go
+++ b/pkg/lockfile/parse.go
@@ -8,11 +8,6 @@ import (
 	"strings"
 )
 
-// Represents when a package name could not be determined while parsing.
-// Currently, parsers are expected to omit such packages from their results.
-// Using a const is required to avoid linter error (goconst) due to multiple usage in parsers.
-const unknownPkgName = "<unknown>"
-
 func FindParser(pathToLockfile string, parseAs string) (PackageDetailsParser, string) {
 	if parseAs == "" {
 		parseAs = filepath.Base(pathToLockfile)
@@ -38,6 +33,7 @@ var parsers = map[string]PackageDetailsParser{
 	"poetry.lock":                 ParsePoetryLock,
 	"pom.xml":                     ParseMavenLock,
 	"pubspec.lock":                ParsePubspecLock,
+	"renv.lock":                   ParseRenvLock,
 	"requirements.txt":            ParseRequirementsTxt,
 	"yarn.lock":                   ParseYarnLock,
 }
diff --git a/pkg/models/results.go b/pkg/models/results.go
index 81425aa..f832ebc 100644
--- a/pkg/models/results.go
+++ b/pkg/models/results.go
@@ -15,12 +15,7 @@ type VulnerabilityResults struct {
 // ExperimentalAnalysisConfig is an experimental type intended to contain the
 // types of analysis performed on packages found by the scanner.
 type ExperimentalAnalysisConfig struct {
-	CallAnalysis ExperimentalCallAnalysisConfig `json:"call_analysis"`
-	Licenses     ExperimentalLicenseConfig      `json:"licenses"`
-}
-
-type ExperimentalCallAnalysisConfig struct {
-	Enabled bool `json:"enabled"`
+	Licenses ExperimentalLicenseConfig `json:"licenses"`
 }
 
 type ExperimentalLicenseConfig struct {
@@ -108,6 +103,8 @@ type PackageVulns struct {
 type GroupInfo struct {
 	// IDs expected to be sorted in alphanumeric order
 	IDs []string `json:"ids"`
+	// Aliases include all aliases and IDs
+	Aliases []string `json:"aliases"`
 	// Map of Vulnerability IDs to AnalysisInfo
 	ExperimentalAnalysis map[string]AnalysisInfo `json:"experimentalAnalysis,omitempty"`
 }
@@ -170,5 +167,5 @@ type PackageInfo struct {
 	Name      string `json:"name"`
 	Version   string `json:"version"`
 	Ecosystem string `json:"ecosystem"`
-	Commit    string `json:"commit"`
+	Commit    string `json:"commit,omitempty"`
 }
diff --git a/pkg/models/vulnerability.go b/pkg/models/vulnerability.go
index db7a0e1..1f87b5f 100644
--- a/pkg/models/vulnerability.go
+++ b/pkg/models/vulnerability.go
@@ -10,8 +10,8 @@ import (
 //
 // See: https://ossf.github.io/osv-schema/#affectedpackage-field
 type Package struct {
-	Ecosystem Ecosystem `json:"ecosystem" yaml:"ecosystem"`
-	Name      string    `json:"name" yaml:"name"`
+	Ecosystem Ecosystem `json:"ecosystem"      yaml:"ecosystem"`
+	Name      string    `json:"name"           yaml:"name"`
 	Purl      string    `json:"purl,omitempty" yaml:"purl,omitempty"`
 }
 
@@ -27,19 +27,19 @@ type Package struct {
 //
 // See: https://ossf.github.io/osv-schema/#affectedrangesevents-fields
 type Event struct {
-	Introduced   string `json:"introduced,omitempty" yaml:"introduced,omitempty"`
-	Fixed        string `json:"fixed,omitempty" yaml:"fixed,omitempty"`
+	Introduced   string `json:"introduced,omitempty"    yaml:"introduced,omitempty"`
+	Fixed        string `json:"fixed,omitempty"         yaml:"fixed,omitempty"`
 	LastAffected string `json:"last_affected,omitempty" yaml:"last_affected,omitempty"`
-	Limit        string `json:"limit,omitempty" yaml:"limit,omitempty"`
+	Limit        string `json:"limit,omitempty"         yaml:"limit,omitempty"`
 }
 
 // Range describes the affected range of given version for a specific package.
 //
 // See: https://ossf.github.io/osv-schema/#affectedranges-field
 type Range struct {
-	Type             RangeType              `json:"type" yaml:"type"`
-	Events           []Event                `json:"events" yaml:"events"`
-	Repo             string                 `json:"repo,omitempty" yaml:"repo,omitempty"`
+	Type             RangeType              `json:"type"                        yaml:"type"`
+	Events           []Event                `json:"events"                      yaml:"events"`
+	Repo             string                 `json:"repo,omitempty"              yaml:"repo,omitempty"`
 	DatabaseSpecific map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"`
 }
 
@@ -48,7 +48,7 @@ type Range struct {
 //
 // See: https://ossf.github.io/osv-schema/#severity-field
 type Severity struct {
-	Type  SeverityType `json:"type" yaml:"type"`
+	Type  SeverityType `json:"type"  yaml:"type"`
 	Score string       `json:"score" yaml:"score"`
 }
 
@@ -57,11 +57,11 @@ type Severity struct {
 //
 // See: https://ossf.github.io/osv-schema/#affected-fields
 type Affected struct {
-	Package           Package                `json:"package,omitempty" yaml:"package,omitempty"`
-	Severity          []Severity             `json:"severity,omitempty" yaml:"severity,omitempty"`
-	Ranges            []Range                `json:"ranges,omitempty" yaml:"ranges,omitempty"`
-	Versions          []string               `json:"versions,omitempty" yaml:"versions,omitempty"`
-	DatabaseSpecific  map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"`
+	Package           Package                `json:"package,omitempty"            yaml:"package,omitempty"`
+	Severity          []Severity             `json:"severity,omitempty"           yaml:"severity,omitempty"`
+	Ranges            []Range                `json:"ranges,omitempty"             yaml:"ranges,omitempty"`
+	Versions          []string               `json:"versions,omitempty"           yaml:"versions,omitempty"`
+	DatabaseSpecific  map[string]interface{} `json:"database_specific,omitempty"  yaml:"database_specific,omitempty"`
 	EcosystemSpecific map[string]interface{} `json:"ecosystem_specific,omitempty" yaml:"ecosystem_specific,omitempty"`
 }
 
@@ -92,7 +92,7 @@ func (a Affected) MarshalJSON() ([]byte, error) {
 // See: https://ossf.github.io/osv-schema/#references-field
 type Reference struct {
 	Type ReferenceType `json:"type" yaml:"type"`
-	URL  string        `json:"url" yaml:"url"`
+	URL  string        `json:"url"  yaml:"url"`
 }
 
 // Credit gives credit for the discovery, confirmation, patch, or other events
@@ -100,8 +100,8 @@ type Reference struct {
 //
 // See: https://ossf.github.io/osv-schema/#credits-fields
 type Credit struct {
-	Name    string     `json:"name" yaml:"name"`
-	Type    CreditType `json:"type,omitempty" yaml:"type,omitempty"`
+	Name    string     `json:"name"              yaml:"name"`
+	Type    CreditType `json:"type,omitempty"    yaml:"type,omitempty"`
 	Contact []string   `json:"contact,omitempty" yaml:"contact,omitempty"`
 }
 
@@ -110,19 +110,19 @@ type Credit struct {
 // The full documentation for the schema is available at
 // https://ossf.github.io/osv-schema.
 type Vulnerability struct {
-	SchemaVersion    string                 `json:"schema_version,omitempty" yaml:"schema_version,omitempty"`
-	ID               string                 `json:"id" yaml:"id"`
-	Modified         time.Time              `json:"modified" yaml:"modified"`
-	Published        time.Time              `json:"published,omitempty" yaml:"published,omitempty"`
-	Withdrawn        time.Time              `json:"withdrawn,omitempty" yaml:"withdrawn,omitempty"`
-	Aliases          []string               `json:"aliases,omitempty" yaml:"aliases,omitempty"`
-	Related          []string               `json:"related,omitempty" yaml:"related,omitempty"`
-	Summary          string                 `json:"summary,omitempty" yaml:"summary,omitempty"`
-	Details          string                 `json:"details,omitempty" yaml:"details,omitempty"`
-	Affected         []Affected             `json:"affected,omitempty" yaml:"affected,omitempty"`
-	Severity         []Severity             `json:"severity,omitempty" yaml:"severity,omitempty"`
-	References       []Reference            `json:"references,omitempty" yaml:"references,omitempty"`
-	Credits          []Credit               `json:"credits,omitempty" yaml:"credits,omitempty"`
+	SchemaVersion    string                 `json:"schema_version,omitempty"    yaml:"schema_version,omitempty"`
+	ID               string                 `json:"id"                          yaml:"id"`
+	Modified         time.Time              `json:"modified"                    yaml:"modified"`
+	Published        time.Time              `json:"published,omitempty"         yaml:"published,omitempty"`
+	Withdrawn        time.Time              `json:"withdrawn,omitempty"         yaml:"withdrawn,omitempty"`
+	Aliases          []string               `json:"aliases,omitempty"           yaml:"aliases,omitempty"`
+	Related          []string               `json:"related,omitempty"           yaml:"related,omitempty"`
+	Summary          string                 `json:"summary,omitempty"           yaml:"summary,omitempty"`
+	Details          string                 `json:"details,omitempty"           yaml:"details,omitempty"`
+	Affected         []Affected             `json:"affected,omitempty"          yaml:"affected,omitempty"`
+	Severity         []Severity             `json:"severity,omitempty"          yaml:"severity,omitempty"`
+	References       []Reference            `json:"references,omitempty"        yaml:"references,omitempty"`
+	Credits          []Credit               `json:"credits,omitempty"           yaml:"credits,omitempty"`
 	DatabaseSpecific map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"`
 }
 
diff --git a/pkg/osvscanner/fixtures/filter/all/configs/b/osv-scanner.toml b/pkg/osvscanner/fixtures/filter/all/configs/b/osv-scanner.toml
index 5c4e697..cb205e2 100644
--- a/pkg/osvscanner/fixtures/filter/all/configs/b/osv-scanner.toml
+++ b/pkg/osvscanner/fixtures/filter/all/configs/b/osv-scanner.toml
@@ -11,6 +11,6 @@ reason = "Ignore 2"
 # Alias of GHSA-xrjj-mj9h-534m
 
 [[IgnoredVulns]]
-id = "GO-2023-1571"
+id = "CVE-2022-41723"
 reason = "Ignore 3"
-# Alias of GHSA-vvpx-j8f3-3w6h
+# Alias of GHSA-vvpx-j8f3-3w6h and GO-2023-1571
diff --git a/pkg/osvscanner/fixtures/filter/all/input.json b/pkg/osvscanner/fixtures/filter/all/input.json
index 0188dfe..9335336 100644
--- a/pkg/osvscanner/fixtures/filter/all/input.json
+++ b/pkg/osvscanner/fixtures/filter/all/input.json
@@ -149,6 +149,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -393,6 +397,10 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
@@ -867,17 +875,29 @@
               "ids": [
                 "GHSA-fxg5-wq6x-vr4w",
                 "GO-2023-1495"
+              ],
+              "aliases": [
+                "GHSA-fxg5-wq6x-vr4w",
+                "GO-2023-1495"
               ]
             },
             {
               "ids": [
                 "GO-2022-1144"
+              ],
+              "aliases": [
+                "GO-2022-1144"
               ]
             },
             {
               "ids": [
                 "GHSA-vvpx-j8f3-3w6h",
                 "GO-2023-1571"
+              ],
+              "aliases": [
+                "GHSA-vvpx-j8f3-3w6h",
+                "GO-2023-1571",
+                "CVE-2022-41723"
               ]
             }
           ]
@@ -1021,11 +1041,17 @@
             {
               "ids": [
                 "GHSA-mrrw-grhq-86gf"
+              ],
+              "aliases": [
+                "GHSA-mrrw-grhq-86gf"
               ]
             },
             {
               "ids": [
                 "RUSTSEC-2023-0015"
+              ],
+              "aliases": [
+                "RUSTSEC-2023-0015"
               ]
             }
           ]
@@ -1173,6 +1199,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -1417,6 +1447,10 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
diff --git a/pkg/osvscanner/fixtures/filter/all/want.json b/pkg/osvscanner/fixtures/filter/all/want.json
index 8932c42..786594f 100644
--- a/pkg/osvscanner/fixtures/filter/all/want.json
+++ b/pkg/osvscanner/fixtures/filter/all/want.json
@@ -1,3 +1,12 @@
 {
-  "results": []
+  "results": [],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
 }
diff --git a/pkg/osvscanner/fixtures/filter/none/input.json b/pkg/osvscanner/fixtures/filter/none/input.json
index 32716a6..c5f6f25 100644
--- a/pkg/osvscanner/fixtures/filter/none/input.json
+++ b/pkg/osvscanner/fixtures/filter/none/input.json
@@ -149,6 +149,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -393,6 +397,10 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
@@ -867,17 +875,28 @@
               "ids": [
                 "GHSA-fxg5-wq6x-vr4w",
                 "GO-2023-1495"
+              ],
+              "aliases": [
+                "GHSA-fxg5-wq6x-vr4w",
+                "GO-2023-1495"
               ]
             },
             {
               "ids": [
                 "GO-2022-1144"
+              ],
+              "aliases": [
+                "GO-2022-1144"
               ]
             },
             {
               "ids": [
                 "GHSA-vvpx-j8f3-3w6h",
                 "GO-2023-1571"
+              ],
+              "aliases": [
+                "GHSA-vvpx-j8f3-3w6h",
+                "GO-2023-1571"
               ]
             }
           ]
@@ -1021,11 +1040,17 @@
             {
               "ids": [
                 "GHSA-mrrw-grhq-86gf"
+              ],
+              "aliases": [
+                "GHSA-mrrw-grhq-86gf"
               ]
             },
             {
               "ids": [
                 "RUSTSEC-2023-0015"
+              ],
+              "aliases": [
+                "RUSTSEC-2023-0015"    
               ]
             }
           ]
@@ -1173,6 +1198,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -1417,6 +1446,10 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
diff --git a/pkg/osvscanner/fixtures/filter/none/want.json b/pkg/osvscanner/fixtures/filter/none/want.json
index 32716a6..81c730b 100644
--- a/pkg/osvscanner/fixtures/filter/none/want.json
+++ b/pkg/osvscanner/fixtures/filter/none/want.json
@@ -14,11 +14,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-mc8h-8q98-g5hr",
               "modified": "2023-02-24T16:23:59Z",
               "published": "2023-02-24T16:23:59Z",
-              "aliases": null,
+              "schema_version": "1.4.0",
+              "id": "GHSA-mc8h-8q98-g5hr",
               "summary": "Race Condition Enabling Link Following and Time-of-check Time-of-use (TOCTOU) Race Condition in remove_dir_all",
               "details": "The `remove_dir_all` crate is a Rust library that offers additional features over the Rust standard library `fs::remove_dir_all` function. It suffers the same class of failure as the code it was layering over: TOCTOU race conditions, with the ability to cause arbitrary paths to be deleted by substituting a symlink for a path after the type of the path was checked.\n\nThanks to the Rust security team for identifying the problem and alerting us to it.",
               "affected": [
@@ -76,10 +75,10 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "RUSTSEC-2023-0018",
               "modified": "2023-03-04T21:50:30Z",
               "published": "2023-02-24T12:00:00Z",
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2023-0018",
               "aliases": [
                 "GHSA-mc8h-8q98-g5hr"
               ],
@@ -149,6 +148,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -161,10 +164,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-wcg3-cvx6-7396",
               "modified": "2022-12-06T00:16:25Z",
               "published": "2021-08-25T20:56:46Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-wcg3-cvx6-7396",
               "aliases": [
                 "CVE-2020-26235"
               ],
@@ -263,10 +266,10 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "RUSTSEC-2020-0071",
               "modified": "2023-02-08T15:06:38Z",
               "published": "2020-11-18T12:00:00Z",
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2020-0071",
               "aliases": [
                 "CVE-2020-26235"
               ],
@@ -393,6 +396,10 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
@@ -413,10 +420,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-fxg5-wq6x-vr4w",
               "modified": "2023-01-24T18:56:46Z",
               "published": "2023-01-14T00:30:23Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-fxg5-wq6x-vr4w",
               "aliases": [
                 "CVE-2022-41721"
               ],
@@ -503,15 +510,14 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "GO-2023-1495",
               "modified": "2023-01-31T21:39:17Z",
               "published": "2023-01-13T22:39:40Z",
+              "schema_version": "1.4.0",
+              "id": "GO-2023-1495",
               "aliases": [
                 "CVE-2022-41721",
                 "GHSA-fxg5-wq6x-vr4w"
               ],
-              "summary": "",
               "details": "A request smuggling attack is possible when using MaxBytesHandler.\n\nWhen using MaxBytesHandler, the body of an HTTP request is not fully consumed. When the server attempts to read HTTP2 frames from the connection, it will instead be reading the body of the HTTP request, which could be attacker-manipulated to represent arbitrary HTTP2 requests.",
               "affected": [
                 {
@@ -562,15 +568,14 @@
               ]
             },
             {
-              "schema_version": "1.4.0",
-              "id": "GO-2022-1144",
               "modified": "2023-01-31T21:39:15Z",
               "published": "2022-12-08T19:01:21Z",
+              "schema_version": "1.4.0",
+              "id": "GO-2022-1144",
               "aliases": [
                 "CVE-2022-41717",
                 "GHSA-xrjj-mj9h-534m"
               ],
-              "summary": "",
               "details": "An attacker can cause excessive memory growth in a Go server accepting HTTP/2 requests.\n\nHTTP/2 server connections contain a cache of HTTP header keys sent by the client. While the total number of entries in this cache is capped, an attacker sending very large keys can cause the server to allocate approximately 64 MiB per open connection.",
               "affected": [
                 {
@@ -678,10 +683,10 @@
               ]
             },
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-vvpx-j8f3-3w6h",
               "modified": "2023-03-09T21:20:44Z",
               "published": "2023-02-17T14:00:02Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-vvpx-j8f3-3w6h",
               "aliases": [
                 "CVE-2022-41723"
               ],
@@ -753,15 +758,14 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "GO-2023-1571",
               "modified": "2023-02-22T20:13:12Z",
               "published": "2023-02-16T22:31:36Z",
+              "schema_version": "1.4.0",
+              "id": "GO-2023-1571",
               "aliases": [
                 "CVE-2022-41723",
                 "GHSA-vvpx-j8f3-3w6h"
               ],
-              "summary": "",
               "details": "A maliciously crafted HTTP/2 stream could cause excessive CPU consumption in the HPACK decoder, sufficient to cause a denial of service from a small number of small requests.",
               "affected": [
                 {
@@ -867,17 +871,28 @@
               "ids": [
                 "GHSA-fxg5-wq6x-vr4w",
                 "GO-2023-1495"
+              ],
+              "aliases": [
+                "GHSA-fxg5-wq6x-vr4w",
+                "GO-2023-1495"
               ]
             },
             {
               "ids": [
                 "GO-2022-1144"
+              ],
+              "aliases": [
+                "GO-2022-1144"
               ]
             },
             {
               "ids": [
                 "GHSA-vvpx-j8f3-3w6h",
                 "GO-2023-1571"
+              ],
+              "aliases": [
+                "GHSA-vvpx-j8f3-3w6h",
+                "GO-2023-1571"
               ]
             }
           ]
@@ -898,11 +913,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-mrrw-grhq-86gf",
               "modified": "2023-02-28T20:30:10Z",
               "published": "2023-02-28T20:30:10Z",
-              "aliases": null,
+              "schema_version": "1.4.0",
+              "id": "GHSA-mrrw-grhq-86gf",
               "summary": "Ascii (crate) allows out-of-bounds array indexing in safe code",
               "details": "Affected version of this crate had implementation of `From\u003c\u0026mut AsciiStr\u003e` for `\u0026mut [u8]` and `\u0026mut str`. This can result in out-of-bounds array indexing in safe code.\n\nThe flaw was corrected in commit [8a6c779](https://github.com/tomprogrammer/rust-ascii/pull/63/commits/8a6c7798c202766bd57d70fb8d12739dd68fb9dc) by removing those impls.\n",
               "affected": [
@@ -957,11 +971,10 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "RUSTSEC-2023-0015",
               "modified": "2023-02-25T15:13:09Z",
               "published": "2023-02-25T12:00:00Z",
-              "aliases": null,
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2023-0015",
               "summary": "Ascii allows out-of-bounds array indexing in safe code",
               "details": "Affected version of this crate had implementation of `From\u003c\u0026mut AsciiStr\u003e` for `\u0026mut [u8]` and `\u0026mut str`. This can result in out-of-bounds array indexing in safe code.\n\nThe flaw was corrected in commit [8a6c779](https://github.com/tomprogrammer/rust-ascii/pull/63/commits/8a6c7798c202766bd57d70fb8d12739dd68fb9dc) by removing those impls.",
               "affected": [
@@ -1021,11 +1034,17 @@
             {
               "ids": [
                 "GHSA-mrrw-grhq-86gf"
+              ],
+              "aliases": [
+                "GHSA-mrrw-grhq-86gf"
               ]
             },
             {
               "ids": [
                 "RUSTSEC-2023-0015"
+              ],
+              "aliases": [
+                "RUSTSEC-2023-0015"
               ]
             }
           ]
@@ -1038,11 +1057,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-mc8h-8q98-g5hr",
               "modified": "2023-02-24T16:23:59Z",
               "published": "2023-02-24T16:23:59Z",
-              "aliases": null,
+              "schema_version": "1.4.0",
+              "id": "GHSA-mc8h-8q98-g5hr",
               "summary": "Race Condition Enabling Link Following and Time-of-check Time-of-use (TOCTOU) Race Condition in remove_dir_all",
               "details": "The `remove_dir_all` crate is a Rust library that offers additional features over the Rust standard library `fs::remove_dir_all` function. It suffers the same class of failure as the code it was layering over: TOCTOU race conditions, with the ability to cause arbitrary paths to be deleted by substituting a symlink for a path after the type of the path was checked.\n\nThanks to the Rust security team for identifying the problem and alerting us to it.",
               "affected": [
@@ -1100,10 +1118,10 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "RUSTSEC-2023-0018",
               "modified": "2023-03-04T21:50:30Z",
               "published": "2023-02-24T12:00:00Z",
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2023-0018",
               "aliases": [
                 "GHSA-mc8h-8q98-g5hr"
               ],
@@ -1173,6 +1191,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -1185,10 +1207,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-wcg3-cvx6-7396",
               "modified": "2022-12-06T00:16:25Z",
               "published": "2021-08-25T20:56:46Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-wcg3-cvx6-7396",
               "aliases": [
                 "CVE-2020-26235"
               ],
@@ -1287,10 +1309,10 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "RUSTSEC-2020-0071",
               "modified": "2023-02-08T15:06:38Z",
               "published": "2020-11-18T12:00:00Z",
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2020-0071",
               "aliases": [
                 "CVE-2020-26235"
               ],
@@ -1417,11 +1439,24 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
         }
       ]
     }
-  ]
+  ],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
 }
diff --git a/pkg/osvscanner/fixtures/filter/some/input.json b/pkg/osvscanner/fixtures/filter/some/input.json
index 7a70a02..b7e9fd6 100644
--- a/pkg/osvscanner/fixtures/filter/some/input.json
+++ b/pkg/osvscanner/fixtures/filter/some/input.json
@@ -149,6 +149,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -393,6 +397,10 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
@@ -867,17 +875,28 @@
               "ids": [
                 "GHSA-fxg5-wq6x-vr4w",
                 "GO-2023-1495"
+              ],
+              "aliases": [
+                "GHSA-fxg5-wq6x-vr4w",
+                "GO-2023-1495"
               ]
             },
             {
               "ids": [
                 "GO-2022-1144"
+              ],
+              "aliases": [
+                "GO-2022-1144"
               ]
             },
             {
               "ids": [
                 "GHSA-vvpx-j8f3-3w6h",
                 "GO-2023-1571"
+              ],
+              "aliases": [
+                "GHSA-vvpx-j8f3-3w6h",
+                "GO-2023-1571"
               ]
             }
           ]
@@ -1021,11 +1040,17 @@
             {
               "ids": [
                 "GHSA-mrrw-grhq-86gf"
+              ],
+              "aliases": [
+                "GHSA-mrrw-grhq-86gf"
               ]
             },
             {
               "ids": [
                 "RUSTSEC-2023-0015"
+              ],
+              "aliases": [
+                "RUSTSEC-2023-0015"
               ]
             }
           ]
@@ -1173,6 +1198,10 @@
               "ids": [
                 "GHSA-mc8h-8q98-g5hr",
                 "RUSTSEC-2023-0018"
+              ],
+              "aliases": [
+                "GHSA-mc8h-8q98-g5hr",
+                "RUSTSEC-2023-0018"
               ]
             }
           ]
@@ -1417,6 +1446,10 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
diff --git a/pkg/osvscanner/fixtures/filter/some/want.json b/pkg/osvscanner/fixtures/filter/some/want.json
index 6c7bd2c..4b00059 100644
--- a/pkg/osvscanner/fixtures/filter/some/want.json
+++ b/pkg/osvscanner/fixtures/filter/some/want.json
@@ -14,10 +14,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-vvpx-j8f3-3w6h",
               "modified": "2023-03-09T21:20:44Z",
               "published": "2023-02-17T14:00:02Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-vvpx-j8f3-3w6h",
               "aliases": [
                 "CVE-2022-41723"
               ],
@@ -89,15 +89,14 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "GO-2023-1571",
               "modified": "2023-02-22T20:13:12Z",
               "published": "2023-02-16T22:31:36Z",
+              "schema_version": "1.4.0",
+              "id": "GO-2023-1571",
               "aliases": [
                 "CVE-2022-41723",
                 "GHSA-vvpx-j8f3-3w6h"
               ],
-              "summary": "",
               "details": "A maliciously crafted HTTP/2 stream could cause excessive CPU consumption in the HPACK decoder, sufficient to cause a denial of service from a small number of small requests.",
               "affected": [
                 {
@@ -203,6 +202,10 @@
               "ids": [
                 "GHSA-vvpx-j8f3-3w6h",
                 "GO-2023-1571"
+              ],
+              "aliases": [
+                "GHSA-vvpx-j8f3-3w6h",
+                "GO-2023-1571"
               ]
             }
           ]
@@ -223,11 +226,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-mrrw-grhq-86gf",
               "modified": "2023-02-28T20:30:10Z",
               "published": "2023-02-28T20:30:10Z",
-              "aliases": null,
+              "schema_version": "1.4.0",
+              "id": "GHSA-mrrw-grhq-86gf",
               "summary": "Ascii (crate) allows out-of-bounds array indexing in safe code",
               "details": "Affected version of this crate had implementation of `From\u003c\u0026mut AsciiStr\u003e` for `\u0026mut [u8]` and `\u0026mut str`. This can result in out-of-bounds array indexing in safe code.\n\nThe flaw was corrected in commit [8a6c779](https://github.com/tomprogrammer/rust-ascii/pull/63/commits/8a6c7798c202766bd57d70fb8d12739dd68fb9dc) by removing those impls.\n",
               "affected": [
@@ -286,6 +288,9 @@
             {
               "ids": [
                 "GHSA-mrrw-grhq-86gf"
+              ],
+              "aliases": [
+                "GHSA-mrrw-grhq-86gf"
               ]
             }
           ]
@@ -298,10 +303,10 @@
           },
           "vulnerabilities": [
             {
-              "schema_version": "1.4.0",
-              "id": "GHSA-wcg3-cvx6-7396",
               "modified": "2022-12-06T00:16:25Z",
               "published": "2021-08-25T20:56:46Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-wcg3-cvx6-7396",
               "aliases": [
                 "CVE-2020-26235"
               ],
@@ -400,10 +405,10 @@
               }
             },
             {
-              "schema_version": "1.4.0",
-              "id": "RUSTSEC-2020-0071",
               "modified": "2023-02-08T15:06:38Z",
               "published": "2020-11-18T12:00:00Z",
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2020-0071",
               "aliases": [
                 "CVE-2020-26235"
               ],
@@ -530,11 +535,24 @@
               "ids": [
                 "GHSA-wcg3-cvx6-7396",
                 "RUSTSEC-2020-0071"
+              ],
+              "aliases": [
+                "GHSA-wcg3-cvx6-7396",
+                "RUSTSEC-2020-0071"
               ]
             }
           ]
         }
       ]
     }
-  ]
+  ],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
 }
diff --git a/pkg/osvscanner/osvscanner.go b/pkg/osvscanner/osvscanner.go
index bc334e7..bd8bdc5 100644
--- a/pkg/osvscanner/osvscanner.go
+++ b/pkg/osvscanner/osvscanner.go
@@ -39,12 +39,12 @@ type ScannerActions struct {
 	NoIgnore             bool
 	DockerContainerNames []string
 	ConfigOverridePath   string
+	CallAnalysisStates   map[string]bool
 
 	ExperimentalScannerActions
 }
 
 type ExperimentalScannerActions struct {
-	CallAnalysis          bool
 	CompareLocally        bool
 	CompareOffline        bool
 	ShowAllPackages       bool
@@ -59,21 +59,17 @@ type ExperimentalScannerActions struct {
 //nolint:errname,stylecheck // Would require version major bump to change
 var NoPackagesFoundErr = errors.New("no packages found in scan")
 
+// VulnerabilitiesFoundErr includes both vulnerabilities being found or license violations being found,
+// however, will not be raised if only uncalled vulnerabilities are found.
+//
 //nolint:errname,stylecheck // Would require version major bump to change
 var VulnerabilitiesFoundErr = errors.New("vulnerabilities found")
 
+// Deprecated: This error is no longer returned, check the results to determine if this is the case
+//
 //nolint:errname,stylecheck // Would require version bump to change
 var OnlyUncalledVulnerabilitiesFoundErr = errors.New("only uncalled vulnerabilities found")
 
-//nolint:errname,stylecheck // Would require version bump to change
-var LicenseViolationsErr = errors.New("license violations found")
-
-//nolint:errname,stylecheck // Would require version bump to change
-var VulnerabilitiesFoundAndLicenseViolationsErr = errors.New("vulnerabilities found and license violations found")
-
-//nolint:errname,stylecheck // Would require version bump to change
-var OnlyUncalledVulnerabilitiesFoundAndLicenseViolationsErr = errors.New("only uncalled vulnerabilities found and license violations found")
-
 var (
 	vendoredLibNames = map[string]struct{}{
 		"3rdparty":    {},
@@ -247,6 +243,10 @@ func queryDetermineVersions(repoDir string) (*osv.DetermineVersionResponse, erro
 				// results with our regular git commit scanning.
 				return filepath.SkipDir
 			}
+			if _, ok := vendoredLibNames[strings.ToLower(info.Name())]; ok {
+				// Ignore nested vendored libraries, as they can cause bad matches.
+				return filepath.SkipDir
+			}
 
 			return nil
 		}
@@ -635,20 +635,20 @@ func filterPackageVulns(r reporter.Reporter, pkgVulns models.PackageVulns, confi
 	var newGroups []models.GroupInfo
 	for _, group := range pkgVulns.Groups {
 		ignore := false
-		for _, id := range group.IDs {
+		for _, id := range group.Aliases {
 			var ignoreLine config.IgnoreEntry
 			if ignore, ignoreLine = configToUse.ShouldIgnore(id); ignore {
-				for _, id := range group.IDs {
+				for _, id := range group.Aliases {
 					ignoredVulns[id] = struct{}{}
 				}
 				// NB: This only prints the first reason encountered in all the aliases.
-				switch len(group.IDs) {
+				switch len(group.Aliases) {
 				case 1:
 					r.PrintText(fmt.Sprintf("%s has been filtered out because: %s\n", ignoreLine.ID, ignoreLine.Reason))
 				case 2:
 					r.PrintText(fmt.Sprintf("%s and 1 alias have been filtered out because: %s\n", ignoreLine.ID, ignoreLine.Reason))
 				default:
-					r.PrintText(fmt.Sprintf("%s and %d aliases have been filtered out because: %s\n", ignoreLine.ID, len(group.IDs)-1, ignoreLine.Reason))
+					r.PrintText(fmt.Sprintf("%s and %d aliases have been filtered out because: %s\n", ignoreLine.ID, len(group.Aliases)-1, ignoreLine.Reason))
 				}
 
 				break
@@ -792,7 +792,8 @@ func DoScan(actions ScannerActions, r reporter.Reporter) (models.VulnerabilityRe
 			return models.VulnerabilityResults{}, err
 		}
 	}
-	results := buildVulnerabilityResults(r, filteredScannedPackages, vulnsResp, licensesResp, actions.CallAnalysis, actions.ShowAllPackages, actions.ScanLicenses, actions.ScanLicensesAllowlist)
+
+	results := buildVulnerabilityResults(r, filteredScannedPackages, vulnsResp, licensesResp, actions.CallAnalysisStates, actions.ShowAllPackages, actions.ScanLicenses, actions.ScanLicensesAllowlist)
 
 	filtered := filterResults(r, &results, &configManager, actions.ShowAllPackages)
 	if filtered > 0 {
@@ -823,25 +824,11 @@ func DoScan(actions ScannerActions, r reporter.Reporter) (models.VulnerabilityRe
 		}
 		onlyUncalledVuln = onlyUncalledVuln && vuln
 
-		switch {
-		case !vuln && !onlyUncalledVuln && !licenseViolation:
+		if (!vuln || onlyUncalledVuln) && !licenseViolation {
 			// There is no error.
 			return results, nil
-		case vuln && !onlyUncalledVuln && !licenseViolation:
+		} else {
 			return results, VulnerabilitiesFoundErr
-		case !vuln && onlyUncalledVuln && !licenseViolation:
-			// Impossible state.
-			panic("internal error: uncalled vulnerabilities exist but no vulnerabilities exist")
-		case vuln && onlyUncalledVuln && !licenseViolation:
-			return results, OnlyUncalledVulnerabilitiesFoundErr
-		case !vuln && !onlyUncalledVuln && licenseViolation:
-			return results, LicenseViolationsErr
-		case vuln && !onlyUncalledVuln && licenseViolation:
-			return results, VulnerabilitiesFoundAndLicenseViolationsErr
-		case !vuln && onlyUncalledVuln && licenseViolation:
-			panic("internal error: uncalled vulnerabilities exist but no vulnerabilities exist")
-		case vuln && onlyUncalledVuln && licenseViolation:
-			return results, OnlyUncalledVulnerabilitiesFoundAndLicenseViolationsErr
 		}
 	}
 
diff --git a/pkg/osvscanner/vulnerability_result.go b/pkg/osvscanner/vulnerability_result.go
index 8eb0b48..6797a9a 100644
--- a/pkg/osvscanner/vulnerability_result.go
+++ b/pkg/osvscanner/vulnerability_result.go
@@ -3,6 +3,7 @@ package osvscanner
 import (
 	"fmt"
 	"sort"
+	"strings"
 
 	"github.com/google/osv-scanner/internal/sourceanalysis"
 	"github.com/google/osv-scanner/pkg/grouper"
@@ -20,7 +21,7 @@ func buildVulnerabilityResults(
 	packages []scannedPackage,
 	vulnsResp *osv.HydratedBatchedResponse,
 	licensesResp [][]models.License,
-	callAnalysis bool,
+	callAnalysisStates map[string]bool,
 	allPackages bool,
 	licenses bool,
 	licensesAllowlist []string,
@@ -32,7 +33,6 @@ func buildVulnerabilityResults(
 	if len(licensesAllowlist) == 1 && licensesAllowlist[0] == "" {
 		licensesAllowlist = []string{}
 	}
-
 	for i, rawPkg := range packages {
 		includePackage := allPackages
 		var pkg models.PackageVulns
@@ -66,10 +66,10 @@ func buildVulnerabilityResults(
 			pkg.Licenses = licensesResp[i]
 			allowlist := make(map[string]bool)
 			for _, license := range licensesAllowlist {
-				allowlist[license] = true
+				allowlist[strings.ToLower(license)] = true
 			}
 			for _, license := range pkg.Licenses {
-				if !allowlist[string(license)] {
+				if !allowlist[strings.ToLower(string(license))] {
 					pkg.LicenseViolations = append(pkg.LicenseViolations, license)
 				}
 			}
@@ -83,9 +83,7 @@ func buildVulnerabilityResults(
 	}
 
 	for source, packages := range groupedBySource {
-		if callAnalysis {
-			sourceanalysis.Run(r, source, packages)
-		}
+		sourceanalysis.Run(r, source, packages, callAnalysisStates)
 		output.Results = append(output.Results, models.PackageSource{
 			Source:   source,
 			Packages: packages,
@@ -108,9 +106,6 @@ func buildVulnerabilityResults(
 		}
 		output.ExperimentalAnalysisConfig.Licenses.Allowlist = allowlist
 	}
-	if callAnalysis {
-		output.ExperimentalAnalysisConfig.CallAnalysis.Enabled = true
-	}
 
 	return output
 }
diff --git a/scripts/generators/generate-cran-versions.R b/scripts/generators/generate-cran-versions.R
new file mode 100755
index 0000000..10013dd
--- /dev/null
+++ b/scripts/generators/generate-cran-versions.R
@@ -0,0 +1,183 @@
+#!/usr/bin/env Rscript
+
+install.packages("jsonlite", repos = 'https://cran.r-project.org')
+
+library(utils)
+library(jsonlite)
+
+# An array of version comparisons that are known to be unsupported and so
+# should be commented out in the generated fixture.
+#
+# Generally this is because the native implementation has a suspected bug
+# that causes the comparison to return incorrect results, and so supporting
+# such comparisons in the detector would in fact be wrong.
+UNSUPPORTED_COMPARISONS <- c()
+
+download_cran_db <- function() {
+  url <- "https://osv-vulnerabilities.storage.googleapis.com/CRAN/all.zip"
+  dest <- "cran-db.zip"
+  download.file(url, dest, method = "auto")
+}
+
+extract_packages_with_versions <- function(osvs) {
+  result <- list()
+
+  for (osv in osvs) {
+    for (affected in osv$affected) {
+      package <- affected$package$name
+
+      if (!(package %in% names(result))) {
+        result[[package]] <- list()
+      }
+
+      for (version in affected$versions) {
+        tryCatch(
+          {
+            as.package_version(version)
+            result[[package]] <- c(result[[package]], version)
+          },
+          error = function(e) {
+            cat(sprintf("skipping invalid version %s for %s\n", version, package))
+          }
+        )
+      }
+    }
+  }
+
+  # deduplicate and sort the versions for each package
+  for (package in names(result)) {
+    result[[package]] <- sort(numeric_version(unique(result[[package]])))
+  }
+
+  return(result)
+}
+
+is_unsupported_comparison <- function(line) {
+  line %in% UNSUPPORTED_COMPARISONS
+}
+
+uncomment <- function(line) {
+  if (startsWith(line, "#")) {
+    return(substr(line, 2, nchar(line)))
+  }
+  if (startsWith(line, "//")) {
+    return(substr(line, 3, nchar(line)))
+  }
+  return(line)
+}
+
+compare <- function(v1, relate, v2) {
+  ops <- list('<' = function(result) result < 0,
+              '=' = function(result) result == 0,
+              '>' = function(result) result > 0)
+
+  return(ops[[relate]](compareVersion(v1, v2)))
+}
+
+compare_versions <- function(lines, select="all") {
+  has_any_failed <- FALSE
+
+  for (line in lines) {
+    line <- trimws(line)
+
+    if (line == "" || grepl("^#", line) || grepl("^//", line)) {
+      maybe_unsupported <- trimws(uncomment(line))
+
+      if (is_unsupported_comparison(maybe_unsupported)) {
+        cat(sprintf("\033[96mS\033[0m: \033[93m%s\033[0m\n", maybe_unsupported))
+      }
+      next
+    }
+
+    parts <- strsplit(trimws(line), " ")[[1]]
+    v1 <- parts[1]
+    op <- parts[2]
+    v2 <- parts[3]
+
+    r <- compare(v1, op, v2)
+
+    if (!r) {
+      has_any_failed <- TRUE
+    }
+
+    if (select == "failures" && r) {
+      next
+    }
+
+    if (select == "successes" && !r) {
+      next
+    }
+
+    color <- ifelse(r, '\033[92m', '\033[91m')
+    rs <- ifelse(r, "T", "F")
+    cat(sprintf("%s%s\033[0m: \033[93m%s\033[0m\n", color, rs, line))
+  }
+  return(has_any_failed)
+}
+
+compare_versions_in_file <- function(filepath, select="all") {
+  lines <- readLines(filepath)
+  return(compare_versions(lines, select))
+}
+
+generate_version_compares <- function(versions) {
+  comparisons <- character()
+
+  for (i in seq_along(versions)) {
+    if (i == 1) {
+      next
+    }
+
+    comparison <- sprintf("%s < %s", versions[i - 1], versions[i])
+
+    if (is_unsupported_comparison(trimws(comparison))) {
+      comparison <- paste("#", comparison)
+    }
+
+    comparisons <- c(comparisons, comparison)
+  }
+
+  return(comparisons)
+}
+
+generate_package_compares <- function(packages) {
+  comparisons <- character()
+
+  for (package in names(packages)) {
+    versions <- packages[[package]]
+    comparisons <- c(comparisons, generate_version_compares(versions))
+  }
+
+  # return unique comparisons
+  return(unique(comparisons))
+}
+
+fetch_packages_versions <- function() {
+  download_cran_db()
+  osvs <- list()
+
+  with_zip <- unzip("cran-db.zip", list = TRUE)
+
+  for (fname in with_zip$Name) {
+    osv <- jsonlite::fromJSON(unzip("cran-db.zip", files = fname, exdir = tempdir()), simplifyDataFrame = FALSE)
+    osvs <- c(osvs, list(osv))
+  }
+
+  return(extract_packages_with_versions(osvs))
+}
+
+outfile <- "internal/semantic/fixtures/cran-versions-generated.txt"
+
+packs <- fetch_packages_versions()
+writeLines(generate_package_compares(packs), outfile, sep = "\n")
+cat("\n")
+
+# set this to either "failures" or "successes" to only have those comparison results
+# printed; setting it to anything else will have all comparison results printed
+show <- Sys.getenv("VERSION_GENERATOR_PRINT", "failures")
+
+did_any_fail <- compare_versions_in_file(outfile, show)
+
+if (did_any_fail) {
+  q(status = 1)
+}
diff --git a/scripts/run_lints.sh b/scripts/run_lints.sh
index 42ee567..f23aa2a 100755
--- a/scripts/run_lints.sh
+++ b/scripts/run_lints.sh
@@ -2,4 +2,4 @@
 
 set -ex
 
-go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.1 run ./... --max-same-issues 0
+go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 run ./... --max-same-issues 0


Test patch:
diff --git a/cmd/osv-scanner/callanalysis_parser_test.go b/cmd/osv-scanner/callanalysis_parser_test.go
new file mode 100644
index 0000000..db8c0bc
--- /dev/null
+++ b/cmd/osv-scanner/callanalysis_parser_test.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestCreateCallAnalysisStates(t *testing.T) {
+	t.Parallel()
+	testCases := []struct {
+		enabledCallAnalysis        []string
+		disabledCallAnalysis       []string
+		expectedCallAnalysisStates map[string]bool
+	}{
+		{
+			enabledCallAnalysis:  []string{"go", "rust"},
+			disabledCallAnalysis: []string{},
+			expectedCallAnalysisStates: map[string]bool{
+				"go":   true,
+				"rust": true,
+			},
+		},
+		{
+			enabledCallAnalysis:  []string{"all"},
+			disabledCallAnalysis: []string{"rust"},
+			expectedCallAnalysisStates: map[string]bool{
+				"go":   true,
+				"rust": false,
+			},
+		},
+		{
+			enabledCallAnalysis:  []string{},
+			disabledCallAnalysis: []string{"all"},
+			expectedCallAnalysisStates: map[string]bool{
+				"go":   false,
+				"rust": false,
+			},
+		},
+		{
+			enabledCallAnalysis:  []string{},
+			disabledCallAnalysis: []string{"rust"},
+			expectedCallAnalysisStates: map[string]bool{
+				"go":   true,
+				"rust": false,
+			},
+		},
+		{
+			enabledCallAnalysis:  []string{"all", "rust"},
+			disabledCallAnalysis: []string{"go"},
+			expectedCallAnalysisStates: map[string]bool{
+				"go":   false,
+				"rust": true,
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		actualCallAnalysisStates := createCallAnalysisStates(testCase.enabledCallAnalysis, testCase.disabledCallAnalysis)
+
+		if !reflect.DeepEqual(actualCallAnalysisStates, testCase.expectedCallAnalysisStates) {
+			t.Errorf("expected call analysis states to be %v, but got %v", testCase.expectedCallAnalysisStates, actualCallAnalysisStates)
+		}
+	}
+}
diff --git a/cmd/osv-scanner/fixtures/locks-test-ignore/osv-scanner.toml b/cmd/osv-scanner/fixtures/locks-test-ignore/osv-scanner.toml
new file mode 100644
index 0000000..e05bc8f
--- /dev/null
+++ b/cmd/osv-scanner/fixtures/locks-test-ignore/osv-scanner.toml
@@ -0,0 +1,3 @@
+[[IgnoredVulns]]
+id = "CVE-2021-23424"
+reason = "Test manifest file (alpine.cdx.xml)"
diff --git a/cmd/osv-scanner/fixtures/locks-test-ignore/package-lock.json b/cmd/osv-scanner/fixtures/locks-test-ignore/package-lock.json
new file mode 100644
index 0000000..e3a2d44
--- /dev/null
+++ b/cmd/osv-scanner/fixtures/locks-test-ignore/package-lock.json
@@ -0,0 +1,9 @@
+{
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "ansi-html": {
+      "version": "0.0.1"
+    }
+  }
+}
diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go
index 20efc57..88e4caa 100644
--- a/cmd/osv-scanner/main_test.go
+++ b/cmd/osv-scanner/main_test.go
@@ -7,11 +7,13 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
+	"strconv"
 	"strings"
 	"testing"
 
 	"github.com/go-git/go-git/v5"
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/osv-scanner/internal/testutility"
 	"github.com/google/osv-scanner/internal/version"
 )
 
@@ -58,7 +60,7 @@ func dedent(t *testing.T, str string) string {
 			}
 		}
 
-		re := regexp.MustCompile(`\n[\t ]{` + fmt.Sprint(size) + `}`)
+		re := regexp.MustCompile(`\n[\t ]{` + strconv.Itoa(size) + `}`)
 		str = re.ReplaceAllString(str, "\n")
 	}
 
@@ -105,6 +107,16 @@ func expectAreEqual(t *testing.T, subject, actual, expect string) {
 	}
 }
 
+// Attempts to normalize any file paths in the given `output` so that they can
+// be compared reliably regardless of the file path separator being used.
+//
+// Namely, escaped forward slashes are replaced with backslashes.
+func normalizeFilePaths(t *testing.T, output string) string {
+	t.Helper()
+
+	return strings.ReplaceAll(strings.ReplaceAll(output, "\\\\", "/"), "\\", "/")
+}
+
 // normalizeRootDirectory attempts to replace references to the current working
 // directory with "<rootdir>", in order to reduce the noise of the cmp diff
 func normalizeRootDirectory(t *testing.T, str string) string {
@@ -115,6 +127,11 @@ func normalizeRootDirectory(t *testing.T, str string) string {
 		t.Errorf("could not get cwd (%v) - results and diff might be inaccurate!", err)
 	}
 
+	cwd = normalizeFilePaths(t, cwd)
+
+	// file uris with Windows end up with three slashes, so we normalize that too
+	str = strings.ReplaceAll(str, "file:///"+cwd, "file://<rootdir>")
+
 	return strings.ReplaceAll(str, cwd, "<rootdir>")
 }
 
@@ -127,11 +144,11 @@ func testCli(t *testing.T, tc cliTestCase) {
 	ec := run(tc.args, stdoutBuffer, stderrBuffer)
 	// ec := run(tc.args, os.Stdout, os.Stderr)
 
-	stdout := stdoutBuffer.String()
-	stderr := stderrBuffer.String()
+	stdout := normalizeRootDirectory(t, normalizeFilePaths(t, stdoutBuffer.String()))
+	stderr := normalizeRootDirectory(t, normalizeFilePaths(t, stderrBuffer.String()))
 
-	stdout = normalizeRootDirectory(t, stdout)
-	stderr = normalizeRootDirectory(t, stderr)
+	tc.wantStdout = normalizeFilePaths(t, tc.wantStdout)
+	tc.wantStderr = normalizeFilePaths(t, tc.wantStderr)
 
 	if ec != tc.wantExitCode {
 		t.Errorf("cli exited with code %d, not %d", ec, tc.wantExitCode)
@@ -249,7 +266,7 @@ func TestRun(t *testing.T) {
 				Scanned <rootdir>/fixtures/locks-many/yarn.lock file and found 1 package
 				Loaded filter from: <rootdir>/fixtures/locks-many/osv-scanner.toml
 				CVE-2022-48174 has been filtered out because: Test manifest file (alpine.cdx.xml)
-				GHSA-whgm-jr23-g3j9 has been filtered out because: Test manifest file
+				GHSA-whgm-jr23-g3j9 and 1 alias have been filtered out because: Test manifest file
 				Filtered 2 vulnerabilities from output
 				No issues found
 			`,
@@ -257,7 +274,7 @@ func TestRun(t *testing.T) {
 		},
 		// all supported lockfiles in the directory should be checked
 		{
-			name:         "",
+			name:         "all supported lockfiles in the directory should be checked",
 			args:         []string{"", "./fixtures/locks-many-with-invalid"},
 			wantExitCode: 127,
 			wantStdout: `
@@ -271,7 +288,7 @@ func TestRun(t *testing.T) {
 		},
 		// only the files in the given directories are checked by default (no recursion)
 		{
-			name:         "",
+			name:         "only the files in the given directories are checked by default (no recursion)",
 			args:         []string{"", "./fixtures/locks-one-with-nested"},
 			wantExitCode: 0,
 			wantStdout: `
@@ -283,7 +300,7 @@ func TestRun(t *testing.T) {
 		},
 		// nested directories are checked when `--recursive` is passed
 		{
-			name:         "",
+			name:         "nested directories are checked when `--recursive` is passed",
 			args:         []string{"", "--recursive", "./fixtures/locks-one-with-nested"},
 			wantExitCode: 0,
 			wantStdout: `
@@ -328,16 +345,13 @@ func TestRun(t *testing.T) {
 		},
 		// output with json
 		{
-			name:         "",
+			name:         "json output 1",
 			args:         []string{"", "--json", "./fixtures/locks-many/composer.lock"},
 			wantExitCode: 0,
 			wantStdout: `
 				{
 					"results": [],
 					"experimental_config": {
-						"call_analysis": {
-							"enabled": false
-						},
 						"licenses": {
 							"enabled": false,
 							"allowlist": null
@@ -351,16 +365,13 @@ func TestRun(t *testing.T) {
 			`,
 		},
 		{
-			name:         "",
+			name:         "json output 2",
 			args:         []string{"", "--format", "json", "./fixtures/locks-many/composer.lock"},
 			wantExitCode: 0,
 			wantStdout: `
 				{
 					"results": [],
 					"experimental_config": {
-						"call_analysis": {
-							"enabled": false
-						},
 						"licenses": {
 							"enabled": false,
 							"allowlist": null
@@ -522,6 +533,21 @@ func TestRun(t *testing.T) {
 				unsupported output format "unknown" - must be one of: table, json, markdown, sarif, gh-annotations
 			`,
 		},
+		// one specific supported lockfile with ignore
+		{
+			name:         "one specific supported lockfile with ignore",
+			args:         []string{"", "./fixtures/locks-test-ignore/package-lock.json"},
+			wantExitCode: 0,
+			wantStdout: `
+				Scanning dir ./fixtures/locks-test-ignore/package-lock.json
+				Scanned <rootdir>/fixtures/locks-test-ignore/package-lock.json file and found 1 package
+				Loaded filter from: <rootdir>/fixtures/locks-test-ignore/osv-scanner.toml
+				CVE-2021-23424 and 1 alias have been filtered out because: Test manifest file (alpine.cdx.xml)
+				Filtered 1 vulnerability from output
+				No issues found
+			`,
+			wantStderr: "",
+		},
 	}
 	for _, tt := range tests {
 		tt := tt
@@ -572,9 +598,10 @@ func TestRun_LockfileWithExplicitParseAs(t *testing.T) {
 			},
 			wantExitCode: 127,
 			wantStdout:   "",
-			wantStderr: `
-				open <rootdir>/path/to/my:file: no such file or directory
-			`,
+			wantStderr: testutility.ValueIfOnWindows(
+				"open <rootdir>/path/to/my:file: The system cannot find the path specified.",
+				"open <rootdir>/path/to/my:file: no such file or directory",
+			),
 		},
 		{
 			name: "",
@@ -585,9 +612,10 @@ func TestRun_LockfileWithExplicitParseAs(t *testing.T) {
 			},
 			wantExitCode: 127,
 			wantStdout:   "",
-			wantStderr: `
-				open <rootdir>/path/to/my:project/package-lock.json: no such file or directory
-			`,
+			wantStderr: testutility.ValueIfOnWindows(
+				"open <rootdir>/path/to/my:project/package-lock.json: The filename, directory name, or volume label syntax is incorrect.",
+				"open <rootdir>/path/to/my:project/package-lock.json: no such file or directory",
+			),
 		},
 		// one lockfile with local path
 		{
@@ -950,7 +978,7 @@ func TestRun_LocalDatabases(t *testing.T) {
 				Loaded Packagist local db from %%/osv-scanner/Packagist/all.zip
 				Loaded npm local db from %%/osv-scanner/npm/all.zip
 				Loaded filter from: <rootdir>/fixtures/locks-many/osv-scanner.toml
-				GHSA-whgm-jr23-g3j9 has been filtered out because: Test manifest file
+				GHSA-whgm-jr23-g3j9 and 1 alias have been filtered out because: Test manifest file
 				Filtered 1 vulnerability from output
 				No issues found
 			`,
@@ -1046,9 +1074,6 @@ func TestRun_LocalDatabases(t *testing.T) {
 				{
 					"results": [],
 					"experimental_config": {
-						"call_analysis": {
-							"enabled": false
-						},
 						"licenses": {
 							"enabled": false,
 							"allowlist": null
@@ -1070,9 +1095,6 @@ func TestRun_LocalDatabases(t *testing.T) {
 				{
 					"results": [],
 					"experimental_config": {
-						"call_analysis": {
-							"enabled": false
-						},
 						"licenses": {
 							"enabled": false,
 							"allowlist": null
@@ -1127,7 +1149,7 @@ func TestRun_Licenses(t *testing.T) {
 		{
 			name:         "No vulnerabilities but contains license violations",
 			args:         []string{"", "--experimental-licenses", "", "./fixtures/locks-many"},
-			wantExitCode: 4,
+			wantExitCode: 1,
 			wantStdout: `
 				Scanning dir ./fixtures/locks-many
 				Scanned <rootdir>/fixtures/locks-many/Gemfile.lock file and found 1 package
@@ -1137,7 +1159,7 @@ func TestRun_Licenses(t *testing.T) {
 				Scanned <rootdir>/fixtures/locks-many/yarn.lock file and found 1 package
 				Loaded filter from: <rootdir>/fixtures/locks-many/osv-scanner.toml
 				CVE-2022-48174 has been filtered out because: Test manifest file (alpine.cdx.xml)
-				GHSA-whgm-jr23-g3j9 has been filtered out because: Test manifest file
+				GHSA-whgm-jr23-g3j9 and 1 alias have been filtered out because: Test manifest file
 				Filtered 2 vulnerabilities from output
 				+------------+-------------------------+
 				| LICENSE    | NO. OF PACKAGE VERSIONS |
@@ -1152,7 +1174,7 @@ func TestRun_Licenses(t *testing.T) {
 		{
 			name:         "No vulnerabilities but contains license violations markdown",
 			args:         []string{"", "--experimental-licenses", "", "--format=markdown", "./fixtures/locks-many"},
-			wantExitCode: 4,
+			wantExitCode: 1,
 			wantStdout: `Scanning dir ./fixtures/locks-many
 Scanned <rootdir>/fixtures/locks-many/Gemfile.lock file and found 1 package
 Scanned <rootdir>/fixtures/locks-many/alpine.cdx.xml as CycloneDX SBOM and found 15 packages
@@ -1161,7 +1183,7 @@ Scanned <rootdir>/fixtures/locks-many/package-lock.json file and found 1 package
 Scanned <rootdir>/fixtures/locks-many/yarn.lock file and found 1 package
 Loaded filter from: <rootdir>/fixtures/locks-many/osv-scanner.toml
 CVE-2022-48174 has been filtered out because: Test manifest file (alpine.cdx.xml)
-GHSA-whgm-jr23-g3j9 has been filtered out because: Test manifest file
+GHSA-whgm-jr23-g3j9 and 1 alias have been filtered out because: Test manifest file
 Filtered 2 vulnerabilities from output
 | License | No. of package versions |
 | --- | ---:|
@@ -1174,7 +1196,7 @@ Filtered 2 vulnerabilities from output
 		{
 			name:         "Vulnerabilities and license violations",
 			args:         []string{"", "--experimental-licenses", "", "--config=./fixtures/osv-scanner-empty-config.toml", "./fixtures/locks-many/package-lock.json"},
-			wantExitCode: 5,
+			wantExitCode: 1,
 			wantStdout: `
 				Scanning dir ./fixtures/locks-many/package-lock.json
 				Scanned <rootdir>/fixtures/locks-many/package-lock.json file and found 1 package
@@ -1194,7 +1216,7 @@ Filtered 2 vulnerabilities from output
 		{
 			name:         "Vulnerabilities and license violations with allowlist",
 			args:         []string{"", "--experimental-licenses", "MIT", "--config=./fixtures/osv-scanner-empty-config.toml", "./fixtures/locks-many/package-lock.json"},
-			wantExitCode: 5,
+			wantExitCode: 1,
 			wantStdout: `
 				Scanning dir ./fixtures/locks-many/package-lock.json
 				Scanned <rootdir>/fixtures/locks-many/package-lock.json file and found 1 package
@@ -1229,7 +1251,7 @@ Filtered 2 vulnerabilities from output
 		{
 			name:         "Some packages with license violations and show-all-packages in json",
 			args:         []string{"", "--format=json", "--experimental-licenses", "MIT", "--experimental-all-packages", "./fixtures/locks-licenses/package-lock.json"},
-			wantExitCode: 4,
+			wantExitCode: 1,
 			wantStdout: `
 			{
 				"results": [
@@ -1243,8 +1265,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "babel",
 									"version": "6.23.0",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1254,8 +1275,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "human-signals",
 									"version": "5.0.0",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"Apache-2.0"
@@ -1268,8 +1288,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "ms",
 									"version": "2.1.3",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1279,9 +1298,6 @@ Filtered 2 vulnerabilities from output
 					}
 				],
 				"experimental_config": {
-					"call_analysis": {
-						"enabled": false
-					},
 					"licenses": {
 						"enabled": true,
 						"allowlist": [
@@ -1299,7 +1315,7 @@ Filtered 2 vulnerabilities from output
 		{
 			name:         "Some packages with license violations in json",
 			args:         []string{"", "--format=json", "--experimental-licenses", "MIT", "./fixtures/locks-licenses/package-lock.json"},
-			wantExitCode: 4,
+			wantExitCode: 1,
 			wantStdout: `
 			{
 				"results": [
@@ -1313,8 +1329,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "human-signals",
 									"version": "5.0.0",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"Apache-2.0"
@@ -1327,9 +1342,6 @@ Filtered 2 vulnerabilities from output
 					}
 				],
 				"experimental_config": {
-					"call_analysis": {
-						"enabled": false
-					},
 					"licenses": {
 						"enabled": true,
 						"allowlist": [
@@ -1361,8 +1373,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "babel",
 									"version": "6.23.0",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1372,8 +1383,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "human-signals",
 									"version": "5.0.0",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"Apache-2.0"
@@ -1383,8 +1393,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "ms",
 									"version": "2.1.3",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1394,9 +1403,6 @@ Filtered 2 vulnerabilities from output
 					}
 				],
 				"experimental_config": {
-					"call_analysis": {
-						"enabled": false
-					},
 					"licenses": {
 						"enabled": true,
 						"allowlist": [
diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-a-a-1.json b/internal/ci/fixtures/vulns/test-vuln-diff-a-a-1.json
index 668cedf..8fd83e0 100644
--- a/internal/ci/fixtures/vulns/test-vuln-diff-a-a-1.json
+++ b/internal/ci/fixtures/vulns/test-vuln-diff-a-a-1.json
@@ -75,11 +75,21 @@
             {
               "ids": [
                 "GO-2021-0053"
-              ]
+              ],
+              "aliases": null
             }
           ]
         }
       ]
     }
-  ]
-}
\ No newline at end of file
+  ],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
+}
diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-a-a.json b/internal/ci/fixtures/vulns/test-vuln-diff-a-a.json
index 8932c42..786594f 100644
--- a/internal/ci/fixtures/vulns/test-vuln-diff-a-a.json
+++ b/internal/ci/fixtures/vulns/test-vuln-diff-a-a.json
@@ -1,3 +1,12 @@
 {
-  "results": []
+  "results": [],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
 }
diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-a-b.json b/internal/ci/fixtures/vulns/test-vuln-diff-a-b.json
index 06324f9..8b29d0a 100644
--- a/internal/ci/fixtures/vulns/test-vuln-diff-a-b.json
+++ b/internal/ci/fixtures/vulns/test-vuln-diff-a-b.json
@@ -112,11 +112,24 @@
             {
               "ids": [
                 "GHSA-c3h9-896r-86jm"
+              ],
+              "aliases": [
+                "CVE-2021-3121",
+                "GHSA-c3h9-896r-86jm"
               ]
             }
           ]
         }
       ]
     }
-  ]
-}
\ No newline at end of file
+  ],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
+}
diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-b-a.json b/internal/ci/fixtures/vulns/test-vuln-diff-b-a.json
index 8932c42..786594f 100644
--- a/internal/ci/fixtures/vulns/test-vuln-diff-b-a.json
+++ b/internal/ci/fixtures/vulns/test-vuln-diff-b-a.json
@@ -1,3 +1,12 @@
 {
-  "results": []
+  "results": [],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
 }
diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-b-c.json b/internal/ci/fixtures/vulns/test-vuln-diff-b-c.json
index 8932c42..786594f 100644
--- a/internal/ci/fixtures/vulns/test-vuln-diff-b-c.json
+++ b/internal/ci/fixtures/vulns/test-vuln-diff-b-c.json
@@ -1,3 +1,12 @@
 {
-  "results": []
+  "results": [],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
 }
diff --git a/internal/ci/fixtures/vulns/test-vuln-diff-c-b.json b/internal/ci/fixtures/vulns/test-vuln-diff-c-b.json
index a8ef63e..76d77a5 100644
--- a/internal/ci/fixtures/vulns/test-vuln-diff-c-b.json
+++ b/internal/ci/fixtures/vulns/test-vuln-diff-c-b.json
@@ -112,6 +112,10 @@
             {
               "ids": [
                 "GHSA-c3h9-896r-86jm"
+              ],
+              "aliases": [
+                "CVE-2021-3121",
+                "GHSA-c3h9-896r-86jm"
               ]
             }
           ]
@@ -319,11 +323,21 @@
               "ids": [
                 "GHSA-m5pq-gvj9-9vr8",
                 "RUSTSEC-2022-0013"
-              ]
+              ],
+              "aliases": null
             }
           ]
         }
       ]
     }
-  ]
-}
\ No newline at end of file
+  ],
+  "experimental_config": {
+    "call_analysis": {
+      "enabled": false
+    },
+    "licenses": {
+      "enabled": false,
+      "allowlist": null
+    }
+  }
+}
diff --git a/internal/output/fixtures/test-vuln-results-a-grouped_windows.json b/internal/output/fixtures/test-vuln-results-a-grouped_windows.json
new file mode 100644
index 0000000..44b9fbe
--- /dev/null
+++ b/internal/output/fixtures/test-vuln-results-a-grouped_windows.json
@@ -0,0 +1,495 @@
+{
+  "GHSA-m5pq-gvj9-9vr8": {
+    "DisplayID": "CVE-2022-24713",
+    "PkgSource": [
+      {
+        "Package": {
+          "name": "regex",
+          "version": "1.5.1",
+          "ecosystem": "crates.io",
+          "commit": ""
+        },
+        "Source": {
+          "path": "D:\\path\\to\\sub-rust-project\\Cargo.lock",
+          "type": "lockfile"
+        }
+      }
+    ],
+    "AliasedVulns": {
+      "GHSA-m5pq-gvj9-9vr8": {
+        "modified": "2022-08-11T20:38:52Z",
+        "published": "2022-03-08T20:00:36Z",
+        "schema_version": "1.4.0",
+        "id": "GHSA-m5pq-gvj9-9vr8",
+        "aliases": [
+          "CVE-2022-24713"
+        ],
+        "summary": "Rust's regex crate vulnerable to regular expression denial of service",
+        "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json"
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713"
+          },
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e"
+          },
+          {
+            "type": "PACKAGE",
+            "url": "https://github.com/rust-lang/regex/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-08"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-14"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5113"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5118"
+          }
+        ],
+        "database_specific": {
+          "cwe_ids": [
+            "CWE-400"
+          ],
+          "github_reviewed": true,
+          "github_reviewed_at": "2022-03-08T20:00:36Z",
+          "nvd_published_at": "2022-03-08T19:15:00Z",
+          "severity": "HIGH"
+        }
+      },
+      "RUSTSEC-2022-0013": {
+        "modified": "2023-06-13T13:10:24Z",
+        "published": "2022-03-08T12:00:00Z",
+        "schema_version": "1.4.0",
+        "id": "RUSTSEC-2022-0013",
+        "aliases": [
+          "CVE-2022-24713",
+          "GHSA-m5pq-gvj9-9vr8"
+        ],
+        "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse",
+        "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0.0.0-0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "categories": [
+                "denial-of-service"
+              ],
+              "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+              "informational": null,
+              "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json"
+            },
+            "ecosystem_specific": {
+              "affects": {
+                "arch": [],
+                "functions": [],
+                "os": []
+              }
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "PACKAGE",
+            "url": "https://crates.io/crates/regex"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          }
+        ]
+      }
+    },
+    "AliasedIDList": [
+      "CVE-2022-24713",
+      "RUSTSEC-2022-0013",
+      "GHSA-m5pq-gvj9-9vr8"
+    ]
+  },
+  "GO-2021-0053": {
+    "DisplayID": "CVE-2021-3121",
+    "PkgSource": [
+      {
+        "Package": {
+          "name": "github.com/gogo/protobuf",
+          "version": "1.3.1",
+          "ecosystem": "Go",
+          "commit": ""
+        },
+        "Source": {
+          "path": "D:\\path\\to\\go.mod",
+          "type": "lockfile"
+        }
+      }
+    ],
+    "AliasedVulns": {
+      "GO-2021-0053": {
+        "modified": "2023-06-12T18:45:41Z",
+        "published": "2021-04-14T20:04:52Z",
+        "schema_version": "1.4.0",
+        "id": "GO-2021-0053",
+        "aliases": [
+          "CVE-2021-3121",
+          "GHSA-c3h9-896r-86jm"
+        ],
+        "summary": "Panic due to improper input validation in github.com/gogo/protobuf",
+        "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "Go",
+              "name": "github.com/gogo/protobuf",
+              "purl": "pkg:golang/github.com/gogo/protobuf"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0"
+                  },
+                  {
+                    "fixed": "1.3.2"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "source": "https://vuln.go.dev/ID/GO-2021-0053.json"
+            },
+            "ecosystem_specific": {
+              "imports": [
+                {
+                  "path": "github.com/gogo/protobuf/plugin/unmarshal",
+                  "symbols": [
+                    "unmarshal.Generate",
+                    "unmarshal.field"
+                  ]
+                }
+              ]
+            }
+          }
+        ],
+        "references": [
+          {
+            "type": "FIX",
+            "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc"
+          }
+        ],
+        "database_specific": {
+          "url": "https://pkg.go.dev/vuln/GO-2021-0053"
+        }
+      }
+    },
+    "AliasedIDList": [
+      "CVE-2021-3121",
+      "GO-2021-0053",
+      "GHSA-c3h9-896r-86jm"
+    ]
+  },
+  "RUSTSEC-2022-0013": {
+    "DisplayID": "CVE-2022-24713",
+    "PkgSource": [
+      {
+        "Package": {
+          "name": "regex",
+          "version": "1.5.1",
+          "ecosystem": "crates.io",
+          "commit": ""
+        },
+        "Source": {
+          "path": "D:\\path\\to\\sub-rust-project\\Cargo.lock",
+          "type": "lockfile"
+        }
+      }
+    ],
+    "AliasedVulns": {
+      "GHSA-m5pq-gvj9-9vr8": {
+        "modified": "2022-08-11T20:38:52Z",
+        "published": "2022-03-08T20:00:36Z",
+        "schema_version": "1.4.0",
+        "id": "GHSA-m5pq-gvj9-9vr8",
+        "aliases": [
+          "CVE-2022-24713"
+        ],
+        "summary": "Rust's regex crate vulnerable to regular expression denial of service",
+        "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json"
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713"
+          },
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e"
+          },
+          {
+            "type": "PACKAGE",
+            "url": "https://github.com/rust-lang/regex/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-08"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-14"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5113"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5118"
+          }
+        ],
+        "database_specific": {
+          "cwe_ids": [
+            "CWE-400"
+          ],
+          "github_reviewed": true,
+          "github_reviewed_at": "2022-03-08T20:00:36Z",
+          "nvd_published_at": "2022-03-08T19:15:00Z",
+          "severity": "HIGH"
+        }
+      },
+      "RUSTSEC-2022-0013": {
+        "modified": "2023-06-13T13:10:24Z",
+        "published": "2022-03-08T12:00:00Z",
+        "schema_version": "1.4.0",
+        "id": "RUSTSEC-2022-0013",
+        "aliases": [
+          "CVE-2022-24713",
+          "GHSA-m5pq-gvj9-9vr8"
+        ],
+        "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse",
+        "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0.0.0-0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "categories": [
+                "denial-of-service"
+              ],
+              "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+              "informational": null,
+              "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json"
+            },
+            "ecosystem_specific": {
+              "affects": {
+                "arch": [],
+                "functions": [],
+                "os": []
+              }
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "PACKAGE",
+            "url": "https://crates.io/crates/regex"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          }
+        ]
+      }
+    },
+    "AliasedIDList": [
+      "CVE-2022-24713",
+      "RUSTSEC-2022-0013",
+      "GHSA-m5pq-gvj9-9vr8"
+    ]
+  }
+}
diff --git a/internal/output/fixtures/test-vuln-results-a.json b/internal/output/fixtures/test-vuln-results-a.json
index 0749a28..605ee93 100644
--- a/internal/output/fixtures/test-vuln-results-a.json
+++ b/internal/output/fixtures/test-vuln-results-a.json
@@ -289,4 +289,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/internal/output/fixtures/test-vuln-results-a_windows.json b/internal/output/fixtures/test-vuln-results-a_windows.json
new file mode 100644
index 0000000..6840352
--- /dev/null
+++ b/internal/output/fixtures/test-vuln-results-a_windows.json
@@ -0,0 +1,292 @@
+{
+  "results": [
+    {
+      "source": {
+        "path": "D:\\path\\to\\go.mod",
+        "type": "lockfile"
+      },
+      "packages": [
+        {
+          "package": {
+            "name": "github.com/gogo/protobuf",
+            "version": "1.3.1",
+            "ecosystem": "Go"
+          },
+          "vulnerabilities": [
+            {
+              "modified": "2023-06-12T18:45:41Z",
+              "published": "2021-04-14T20:04:52Z",
+              "schema_version": "1.4.0",
+              "id": "GO-2021-0053",
+              "aliases": [
+                "CVE-2021-3121",
+                "GHSA-c3h9-896r-86jm"
+              ],
+              "summary": "Panic due to improper input validation in github.com/gogo/protobuf",
+              "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.",
+              "affected": [
+                {
+                  "package": {
+                    "ecosystem": "Go",
+                    "name": "github.com/gogo/protobuf",
+                    "purl": "pkg:golang/github.com/gogo/protobuf"
+                  },
+                  "ranges": [
+                    {
+                      "type": "SEMVER",
+                      "events": [
+                        {
+                          "introduced": "0"
+                        },
+                        {
+                          "fixed": "1.3.2"
+                        }
+                      ]
+                    }
+                  ],
+                  "database_specific": {
+                    "source": "https://vuln.go.dev/ID/GO-2021-0053.json"
+                  },
+                  "ecosystem_specific": {
+                    "imports": [
+                      {
+                        "path": "github.com/gogo/protobuf/plugin/unmarshal",
+                        "symbols": [
+                          "unmarshal.Generate",
+                          "unmarshal.field"
+                        ]
+                      }
+                    ]
+                  }
+                }
+              ],
+              "references": [
+                {
+                  "type": "FIX",
+                  "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc"
+                }
+              ],
+              "database_specific": {
+                "url": "https://pkg.go.dev/vuln/GO-2021-0053"
+              }
+            }
+          ],
+          "groups": [
+            {
+              "ids": [
+                "GO-2021-0053"
+              ]
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "source": {
+        "path": "D:\\path\\to\\sub-rust-project\\Cargo.lock",
+        "type": "lockfile"
+      },
+      "packages": [
+        {
+          "package": {
+            "name": "regex",
+            "version": "1.5.1",
+            "ecosystem": "crates.io"
+          },
+          "vulnerabilities": [
+            {
+              "modified": "2022-08-11T20:38:52Z",
+              "published": "2022-03-08T20:00:36Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-m5pq-gvj9-9vr8",
+              "aliases": [
+                "CVE-2022-24713"
+              ],
+              "summary": "Rust's regex crate vulnerable to regular expression denial of service",
+              "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.",
+              "affected": [
+                {
+                  "package": {
+                    "ecosystem": "crates.io",
+                    "name": "regex",
+                    "purl": "pkg:cargo/regex"
+                  },
+                  "ranges": [
+                    {
+                      "type": "SEMVER",
+                      "events": [
+                        {
+                          "introduced": "0"
+                        },
+                        {
+                          "fixed": "1.5.5"
+                        }
+                      ]
+                    }
+                  ],
+                  "database_specific": {
+                    "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json"
+                  }
+                }
+              ],
+              "severity": [
+                {
+                  "type": "CVSS_V3",
+                  "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+                }
+              ],
+              "references": [
+                {
+                  "type": "WEB",
+                  "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8"
+                },
+                {
+                  "type": "ADVISORY",
+                  "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e"
+                },
+                {
+                  "type": "PACKAGE",
+                  "url": "https://github.com/rust-lang/regex/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://security.gentoo.org/glsa/202208-08"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://security.gentoo.org/glsa/202208-14"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://www.debian.org/security/2022/dsa-5113"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://www.debian.org/security/2022/dsa-5118"
+                }
+              ],
+              "database_specific": {
+                "cwe_ids": [
+                  "CWE-400"
+                ],
+                "github_reviewed": true,
+                "github_reviewed_at": "2022-03-08T20:00:36Z",
+                "nvd_published_at": "2022-03-08T19:15:00Z",
+                "severity": "HIGH"
+              }
+            },
+            {
+              "modified": "2023-06-13T13:10:24Z",
+              "published": "2022-03-08T12:00:00Z",
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2022-0013",
+              "aliases": [
+                "CVE-2022-24713",
+                "GHSA-m5pq-gvj9-9vr8"
+              ],
+              "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse",
+              "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+              "affected": [
+                {
+                  "package": {
+                    "ecosystem": "crates.io",
+                    "name": "regex",
+                    "purl": "pkg:cargo/regex"
+                  },
+                  "ranges": [
+                    {
+                      "type": "SEMVER",
+                      "events": [
+                        {
+                          "introduced": "0.0.0-0"
+                        },
+                        {
+                          "fixed": "1.5.5"
+                        }
+                      ]
+                    }
+                  ],
+                  "database_specific": {
+                    "categories": [
+                      "denial-of-service"
+                    ],
+                    "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+                    "informational": null,
+                    "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json"
+                  },
+                  "ecosystem_specific": {
+                    "affects": {
+                      "arch": [],
+                      "functions": [],
+                      "os": []
+                    }
+                  }
+                }
+              ],
+              "severity": [
+                {
+                  "type": "CVSS_V3",
+                  "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+                }
+              ],
+              "references": [
+                {
+                  "type": "PACKAGE",
+                  "url": "https://crates.io/crates/regex"
+                },
+                {
+                  "type": "ADVISORY",
+                  "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+                }
+              ]
+            }
+          ],
+          "groups": [
+            {
+              "ids": [
+                "GHSA-m5pq-gvj9-9vr8",
+                "RUSTSEC-2022-0013"
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/internal/output/fixtures/test-vuln-results-a_windows.sarif b/internal/output/fixtures/test-vuln-results-a_windows.sarif
new file mode 100644
index 0000000..9182728
--- /dev/null
+++ b/internal/output/fixtures/test-vuln-results-a_windows.sarif
@@ -0,0 +1,124 @@
+{
+  "version": "2.1.0",
+  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "informationUri": "https://github.com/google/osv-scanner",
+          "name": "osv-scanner",
+          "rules": [
+            {
+              "id": "CVE-2022-24713",
+              "name": "CVE-2022-24713",
+              "shortDescription": {
+                "text": "CVE-2022-24713: Regexes with large repetitions on empty sub-expressions take a very long time to parse"
+              },
+              "fullDescription": {
+                "text": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+                "markdown": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security"
+              },
+              "deprecatedIds": [
+                "CVE-2022-24713",
+                "RUSTSEC-2022-0013",
+                "GHSA-m5pq-gvj9-9vr8"
+              ],
+              "help": {
+                "text": "**Your dependency is vulnerable to [CVE-2022-24713](https://osv.dev/list?q=CVE-2022-24713)**\n(Also published as: [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013), [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8), ).\n\n## [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e The Rust Security Response WG was notified that the `regex` crate did not\n\u003e properly limit the complexity of the regular expressions (regex) it parses. An\n\u003e attacker could use this security issue to perform a denial of service, by\n\u003e sending a specially crafted regex to a service accepting untrusted regexes. No\n\u003e known vulnerability is present when parsing untrusted input with trusted\n\u003e regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability\n\u003e is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\n\u003e of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service\n\u003e attacks caused by untrusted regexes, or untrusted input matched by trusted\n\u003e regexes. Those (tunable) mitigations already provide sane defaults to prevent\n\u003e attacks. This guarantee is documented and it's considered part of the crate's\n\u003e API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent\n\u003e untrusted regexes to take an arbitrary amount of time during parsing, and it's\n\u003e possible to craft regexes that bypass such mitigations. This makes it possible\n\u003e to perform denial of service attacks by sending specially crafted regexes to\n\u003e services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this\n\u003e issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately\n\u003e to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are\n\u003e practically infinite regexes that could be crafted to exploit this\n\u003e vulnerability. Because of this, we do not recommend denying known problematic\n\u003e regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according\n\u003e to the [Rust security policy][1], and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini\n\u003e for coordinating the disclosure and writing this advisory.\n\u003e \n\u003e [1]: https://www.rust-lang.org/policies/security\n\n\u003c/details\u003e\n\n## [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e \u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\u003e \n\u003e [advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\u003e \n\u003e The Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\sub-rust-project\\Cargo.lock | regex | 1.5.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GHSA-m5pq-gvj9-9vr8 | regex | 1.5.5 |\n| RUSTSEC-2022-0013 | regex | 1.5.5 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to\\sub-rust-project/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2022-24713\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n",
+                "markdown": "**Your dependency is vulnerable to [CVE-2022-24713](https://osv.dev/list?q=CVE-2022-24713)**\n(Also published as: [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013), [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8), ).\n\n## [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e The Rust Security Response WG was notified that the `regex` crate did not\n\u003e properly limit the complexity of the regular expressions (regex) it parses. An\n\u003e attacker could use this security issue to perform a denial of service, by\n\u003e sending a specially crafted regex to a service accepting untrusted regexes. No\n\u003e known vulnerability is present when parsing untrusted input with trusted\n\u003e regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability\n\u003e is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\n\u003e of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service\n\u003e attacks caused by untrusted regexes, or untrusted input matched by trusted\n\u003e regexes. Those (tunable) mitigations already provide sane defaults to prevent\n\u003e attacks. This guarantee is documented and it's considered part of the crate's\n\u003e API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent\n\u003e untrusted regexes to take an arbitrary amount of time during parsing, and it's\n\u003e possible to craft regexes that bypass such mitigations. This makes it possible\n\u003e to perform denial of service attacks by sending specially crafted regexes to\n\u003e services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this\n\u003e issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately\n\u003e to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are\n\u003e practically infinite regexes that could be crafted to exploit this\n\u003e vulnerability. Because of this, we do not recommend denying known problematic\n\u003e regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according\n\u003e to the [Rust security policy][1], and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini\n\u003e for coordinating the disclosure and writing this advisory.\n\u003e \n\u003e [1]: https://www.rust-lang.org/policies/security\n\n\u003c/details\u003e\n\n## [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e \u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\u003e \n\u003e [advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\u003e \n\u003e The Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\sub-rust-project\\Cargo.lock | regex | 1.5.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GHSA-m5pq-gvj9-9vr8 | regex | 1.5.5 |\n| RUSTSEC-2022-0013 | regex | 1.5.5 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to\\sub-rust-project/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2022-24713\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n"
+              }
+            },
+            {
+              "id": "CVE-2021-3121",
+              "name": "CVE-2021-3121",
+              "shortDescription": {
+                "text": "CVE-2021-3121: Panic due to improper input validation in github.com/gogo/protobuf"
+              },
+              "fullDescription": {
+                "text": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.",
+                "markdown": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector."
+              },
+              "deprecatedIds": [
+                "CVE-2021-3121",
+                "GO-2021-0053",
+                "GHSA-c3h9-896r-86jm"
+              ],
+              "help": {
+                "text": "**Your dependency is vulnerable to [CVE-2021-3121](https://osv.dev/list?q=CVE-2021-3121)**.\n\n## [GO-2021-0053](https://osv.dev/vulnerability/GO-2021-0053)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\go.mod | github.com/gogo/protobuf | 1.3.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GO-2021-0053 | github.com/gogo/protobuf | 1.3.2 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2021-3121\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n",
+                "markdown": "**Your dependency is vulnerable to [CVE-2021-3121](https://osv.dev/list?q=CVE-2021-3121)**.\n\n## [GO-2021-0053](https://osv.dev/vulnerability/GO-2021-0053)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\go.mod | github.com/gogo/protobuf | 1.3.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GO-2021-0053 | github.com/gogo/protobuf | 1.3.2 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2021-3121\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n"
+              }
+            }
+          ],
+          "version": "1.4.3"
+        }
+      },
+      "artifacts": [
+        {
+          "location": {
+            "uri": "file:///D:/path/to/sub-rust-project/Cargo.lock"
+          },
+          "length": -1
+        },
+        {
+          "location": {
+            "uri": "file:///D:/path/to/go.mod"
+          },
+          "length": -1
+        }
+      ],
+      "results": [
+        {
+          "ruleId": "CVE-2022-24713",
+          "ruleIndex": 0,
+          "level": "warning",
+          "message": {
+            "text": "Package 'regex@1.5.1' is vulnerable to 'CVE-2022-24713' (also known as 'RUSTSEC-2022-0013', 'GHSA-m5pq-gvj9-9vr8')."
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "file:///D:/path/to/sub-rust-project/Cargo.lock"
+                }
+              }
+            }
+          ]
+        },
+        {
+          "ruleId": "CVE-2021-3121",
+          "ruleIndex": 1,
+          "level": "warning",
+          "message": {
+            "text": "Package 'github.com/gogo/protobuf@1.3.1' is vulnerable to 'CVE-2021-3121' (also known as 'GO-2021-0053', 'GHSA-c3h9-896r-86jm')."
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "file:///D:/path/to/go.mod"
+                }
+              }
+            }
+          ]
+        },
+        {
+          "ruleId": "CVE-2022-24713",
+          "ruleIndex": 0,
+          "level": "warning",
+          "message": {
+            "text": "Package 'regex@1.5.1' is vulnerable to 'CVE-2022-24713' (also known as 'RUSTSEC-2022-0013', 'GHSA-m5pq-gvj9-9vr8')."
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "file:///D:/path/to/sub-rust-project/Cargo.lock"
+                }
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/internal/semantic/compare_test.go b/internal/semantic/compare_test.go
index de20024..99ed53b 100644
--- a/internal/semantic/compare_test.go
+++ b/internal/semantic/compare_test.go
@@ -217,6 +217,14 @@ func TestVersion_Compare_Ecosystems(t *testing.T) {
 			name: "Debian",
 			file: "debian-versions-generated.txt",
 		},
+		{
+			name: "CRAN",
+			file: "cran-versions.txt",
+		},
+		{
+			name: "CRAN",
+			file: "cran-versions-generated.txt",
+		},
 	}
 	for _, tt := range tests {
 		tt := tt
diff --git a/internal/semantic/parse_test.go b/internal/semantic/parse_test.go
index 6072aca..8fcea3f 100644
--- a/internal/semantic/parse_test.go
+++ b/internal/semantic/parse_test.go
@@ -13,6 +13,9 @@ func TestParse(t *testing.T) {
 
 	ecosystems := lockfile.KnownEcosystems()
 
+	// todo: remove once CRAN is supported by lockfile
+	ecosystems = append(ecosystems, "CRAN")
+
 	for _, ecosystem := range ecosystems {
 		_, err := semantic.Parse("", ecosystem)
 
@@ -33,6 +36,9 @@ func TestMustParse(t *testing.T) {
 
 	ecosystems := lockfile.KnownEcosystems()
 
+	// todo: remove once CRAN is supported by lockfile
+	ecosystems = append(ecosystems, "CRAN")
+
 	for _, ecosystem := range ecosystems {
 		semantic.MustParse("", ecosystem)
 	}
diff --git a/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json b/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json
index 355e95d..94e7867 100755
--- a/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json
+++ b/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json
@@ -18,7 +18,7 @@
                   "column":22,
                   "filename":"<Any value>",
                   "line":13,
-                  "offset":234
+                  "offset":-1
                }
             }
          ]
diff --git a/internal/sourceanalysis/integration_test.go b/internal/sourceanalysis/integration_test.go
index c7b47c4..b30113a 100644
--- a/internal/sourceanalysis/integration_test.go
+++ b/internal/sourceanalysis/integration_test.go
@@ -50,5 +50,6 @@ func Test_RunGoVulnCheck(t *testing.T) {
 	}
 
 	res["GO-2023-1558"][0].Trace[1].Position.Filename = "<Any value>"
+	res["GO-2023-1558"][0].Trace[1].Position.Offset = -1
 	testutility.AssertMatchFixtureJSON(t, filepath.Join(fixturesDir, "snapshots/govulncheckshim_test.json"), res)
 }
diff --git a/internal/sourceanalysis/rust_test.go b/internal/sourceanalysis/rust_test.go
index be97cd0..c5a34b9 100644
--- a/internal/sourceanalysis/rust_test.go
+++ b/internal/sourceanalysis/rust_test.go
@@ -94,7 +94,7 @@ func Test_rustBuildSource(t *testing.T) {
 				},
 			},
 			want: []string{
-				workingDir + "/fixtures-rust/rust-project/target/release/test-project",
+				workingDir + filepath.FromSlash("/fixtures-rust/rust-project/target/release/test-project") + testutility.ValueIfOnWindows(".exe", ""),
 			},
 		},
 	}
diff --git a/internal/testutility/utility.go b/internal/testutility/utility.go
index 1501c5e..3817a39 100644
--- a/internal/testutility/utility.go
+++ b/internal/testutility/utility.go
@@ -4,7 +4,10 @@ import (
 	"encoding/json"
 	"fmt"
 	"os"
+	"path/filepath"
 	"reflect"
+	"runtime"
+	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -13,41 +16,75 @@ import (
 	"github.com/hexops/gotextdiff/span"
 )
 
-// LoadJSONFixture loads a JSON fixture file and returns the decoded version.
-func LoadJSONFixture[V any](t *testing.T, path string) V {
+func determineWindowsFixturePath(t *testing.T, path string) string {
 	t.Helper()
-	file, err := os.Open(path)
-	if err != nil {
-		t.Fatalf("Failed to open fixture: %s", err)
-	}
-	var value V
-	err = json.NewDecoder(file).Decode(&value)
-	if err != nil {
-		t.Fatalf("Failed to parse fixture: %s", err)
-	}
 
-	return value
+	ext := filepath.Ext(path)
+
+	return strings.TrimSuffix(path, ext) + "_windows" + ext
 }
 
-// AssertMatchFixtureJSON matches the JSON at path with the value val, failing if not equal, printing out the difference.
-func AssertMatchFixtureJSON[V any](t *testing.T, path string, val V) {
+func loadFixture(t *testing.T, path string) ([]byte, string) {
 	t.Helper()
-	fileA, err := os.ReadFile(path)
+
+	var file []byte
+	var err error
+
+	// when on Windows, check if there is a version of the fixture whose filename
+	// ends with _windows and if so use that instead
+	if //goland:noinspection GoBoolExpressions
+	runtime.GOOS == "windows" {
+		winFixturePath := determineWindowsFixturePath(t, path)
+
+		file, err = os.ReadFile(winFixturePath)
+
+		if err == nil {
+			return file, winFixturePath
+		}
+		// load the original file if a Windows-specific version does not exist
+		if !os.IsNotExist(err) {
+			t.Fatalf("Failed to open fixture: %s", err)
+		}
+	}
+
+	file, err = os.ReadFile(path)
+
 	if err != nil {
 		t.Fatalf("Failed to open fixture: %s", err)
 	}
 
+	return file, path
+}
+
+// LoadJSONFixture loads a JSON fixture file and returns the decoded version.
+func LoadJSONFixture[V any](t *testing.T, path string) V {
+	t.Helper()
+
+	file, _ := loadFixture(t, path)
+
 	var elem V
-	err = json.Unmarshal(fileA, &elem)
+	err := json.Unmarshal(file, &elem)
 	if err != nil {
 		t.Fatalf("Failed to unmarshal val: %s", err)
 	}
 
+	return elem
+}
+
+// AssertMatchFixtureJSON matches the JSON at path with the value val, failing if not equal, printing out the difference.
+func AssertMatchFixtureJSON[V any](t *testing.T, path string, val V) {
+	t.Helper()
+
+	elem := LoadJSONFixture[V](t, path)
+
 	if !reflect.DeepEqual(val, elem) {
 		t.Errorf("Not equal: \n%s", cmp.Diff(val, elem))
 	}
 }
 
+// Deprecated: Not actually deprecated, deprecated tag used to warn users from checking in as
+// it's meant to be used as a helper func to generate fixtures
+//
 // CreateJSONFixture creates a JSON file at path of the value val,
 // can be used with AssertMatchFixtureJSON to compare against future values.
 func CreateJSONFixture[V any](t *testing.T, path string, val V) {
@@ -80,16 +117,20 @@ func CreateTextFixture(t *testing.T, path string, val string) {
 	}
 }
 
+func normalizeNewlines(content string) string {
+	return strings.ReplaceAll(content, "\r\n", "\n")
+}
+
 // AssertMatchFixtureText matches the Text file at path with actual
 func AssertMatchFixtureText(t *testing.T, path string, actual string) {
 	t.Helper()
-	fileA, err := os.ReadFile(path)
-	if err != nil {
-		t.Fatalf("Failed to open fixture: %s", err)
-	}
 
+	fileA, path := loadFixture(t, path)
+
+	actual = normalizeNewlines(actual)
 	expect := string(fileA)
-	if actual != string(fileA) {
+	expect = normalizeNewlines(expect)
+	if actual != expect {
 		if os.Getenv("TEST_NO_DIFF") == "true" {
 			t.Errorf("\nactual %s does not match expected:\n got:\n%s\n\n want:\n%s", path, actual, expect)
 		} else {
@@ -108,3 +149,12 @@ func AcceptanceTests(t *testing.T, reason string) {
 		t.Skip("Skipping extended test: ", reason)
 	}
 }
+
+func ValueIfOnWindows(win, or string) string {
+	if //goland:noinspection GoBoolExpressions
+	runtime.GOOS == "windows" {
+		return win
+	}
+
+	return or
+}
diff --git a/pkg/grouper/grouper_test.go b/pkg/grouper/grouper_test.go
index be06f14..32cf2c9 100644
--- a/pkg/grouper/grouper_test.go
+++ b/pkg/grouper/grouper_test.go
@@ -81,16 +81,20 @@ func TestGroup(t *testing.T) {
 			},
 			want: []models.GroupInfo{
 				{
-					IDs: []string{v1.ID, v2.ID, v3.ID},
+					IDs:     []string{v1.ID, v2.ID, v3.ID},
+					Aliases: []string{v1.ID, v2.ID, v3.ID},
 				},
 				{
-					IDs: []string{v4.ID, v5.ID, v6.ID},
+					IDs:     []string{v4.ID, v5.ID, v6.ID},
+					Aliases: []string{v4.ID, v5.ID, v6.ID, v4.Aliases[0], v4.Aliases[1], v5.Aliases[1]},
 				},
 				{
-					IDs: []string{v7.ID},
+					IDs:     []string{v7.ID},
+					Aliases: []string{v7.Aliases[0], v7.ID},
 				},
 				{
-					IDs: []string{v8.ID},
+					IDs:     []string{v8.ID},
+					Aliases: []string{v8.Aliases[0], v8.ID},
 				},
 			},
 		},
@@ -100,22 +104,28 @@ func TestGroup(t *testing.T) {
 			},
 			want: []models.GroupInfo{
 				{
-					IDs: []string{v8.ID},
+					IDs:     []string{v8.ID},
+					Aliases: []string{v8.Aliases[0], v8.ID},
 				},
 				{
-					IDs: []string{v1.ID, v2.ID, v3.ID}, // Deterministic order
+					IDs:     []string{v1.ID, v2.ID, v3.ID}, // Deterministic order
+					Aliases: []string{v1.ID, v2.ID, v3.ID}, // Deterministic order
 				},
 				{
-					IDs: []string{v4.ID, v5.ID, v6.ID},
+					IDs:     []string{v4.ID, v5.ID, v6.ID},
+					Aliases: []string{v4.ID, v5.ID, v6.ID, v4.Aliases[0], v4.Aliases[1], v5.Aliases[1]},
 				},
 				{
-					IDs: []string{v7.ID},
+					IDs:     []string{v7.ID},
+					Aliases: []string{v7.Aliases[0], v7.ID},
 				},
 				{
-					IDs: []string{v9.ID},
+					IDs:     []string{v9.ID},
+					Aliases: []string{v9.ID},
 				},
 				{
-					IDs: []string{v10.ID},
+					IDs:     []string{v10.ID},
+					Aliases: []string{v10.ID},
 				},
 			},
 		},
@@ -125,10 +135,12 @@ func TestGroup(t *testing.T) {
 			},
 			want: []models.GroupInfo{
 				{
-					IDs: []string{v9.ID},
+					IDs:     []string{v9.ID},
+					Aliases: []string{v9.ID},
 				},
 				{
-					IDs: []string{v10.ID},
+					IDs:     []string{v10.ID},
+					Aliases: []string{v10.ID},
 				},
 			},
 		},
diff --git a/pkg/lockfile/extract_test.go b/pkg/lockfile/extract_test.go
index b014e1e..647bf3b 100644
--- a/pkg/lockfile/extract_test.go
+++ b/pkg/lockfile/extract_test.go
@@ -48,6 +48,7 @@ func TestFindExtractor(t *testing.T) {
 		"poetry.lock":                 "poetry.lock",
 		"pom.xml":                     "pom.xml",
 		"pubspec.lock":                "pubspec.lock",
+		"renv.lock":                   "renv.lock",
 		"requirements.txt":            "requirements.txt",
 		"yarn.lock":                   "yarn.lock",
 	}
@@ -98,6 +99,7 @@ func TestExtractDeps_FindsExpectedExtractor(t *testing.T) {
 		"poetry.lock",
 		"pom.xml",
 		"pubspec.lock",
+		"renv.lock",
 		"requirements.txt",
 		"yarn.lock",
 	}
diff --git a/pkg/lockfile/parse-renv-lock_test.go b/pkg/lockfile/parse-renv-lock_test.go
new file mode 100644
index 0000000..a4f8eec
--- /dev/null
+++ b/pkg/lockfile/parse-renv-lock_test.go
@@ -0,0 +1,133 @@
+package lockfile_test
+
+import (
+	"io/fs"
+	"testing"
+
+	"github.com/google/osv-scanner/pkg/lockfile"
+)
+
+func TestParseRenvLock_FileDoesNotExist(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/does-not-exist")
+
+	expectErrIs(t, err, fs.ErrNotExist)
+	expectPackages(t, packages, []lockfile.PackageDetails{})
+}
+
+func TestParseRenvLock_InvalidJson(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/not-json.txt")
+
+	expectErrContaining(t, err, "could not extract from")
+	expectPackages(t, packages, []lockfile.PackageDetails{})
+}
+
+func TestParseRenvLock_NoPackages(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/empty.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{})
+}
+
+func TestParseRenvLock_OnePackage(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/one-package.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "morning",
+			Version:   "0.1.0",
+			Ecosystem: lockfile.CRANEcosystem,
+			CompareAs: lockfile.CRANEcosystem,
+		},
+	})
+}
+
+func TestParseRenvLock_TwoPackages(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/two-packages.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "markdown",
+			Version:   "1.0",
+			Ecosystem: lockfile.CRANEcosystem,
+			CompareAs: lockfile.CRANEcosystem,
+		},
+		{
+			Name:      "mime",
+			Version:   "0.7",
+			Ecosystem: lockfile.CRANEcosystem,
+			CompareAs: lockfile.CRANEcosystem,
+		},
+	})
+}
+
+func TestParseRenvLock_WithMixedSources(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/with-mixed-sources.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "markdown",
+			Version:   "1.0",
+			Ecosystem: lockfile.CRANEcosystem,
+			CompareAs: lockfile.CRANEcosystem,
+		},
+	})
+}
+
+func TestParseRenvLock_WithBioconductor(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/with-bioconductor.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	// currently Bioconductor is not supported
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "BH",
+			Version:   "1.75.0-0",
+			Ecosystem: lockfile.CRANEcosystem,
+			CompareAs: lockfile.CRANEcosystem,
+		},
+	})
+}
+
+func TestParseRenvLock_WithoutRepository(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRenvLock("fixtures/renv/without-repository.lock")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{})
+}
diff --git a/pkg/lockfile/parse_test.go b/pkg/lockfile/parse_test.go
index aaef2c1..51f6191 100644
--- a/pkg/lockfile/parse_test.go
+++ b/pkg/lockfile/parse_test.go
@@ -57,6 +57,7 @@ func TestFindParser(t *testing.T) {
 		"poetry.lock",
 		"pom.xml",
 		"pubspec.lock",
+		"renv.lock",
 		"requirements.txt",
 		"yarn.lock",
 	}
@@ -107,6 +108,7 @@ func TestParse_FindsExpectedParsers(t *testing.T) {
 		"poetry.lock",
 		"pom.xml",
 		"pubspec.lock",
+		"renv.lock",
 		"requirements.txt",
 		"yarn.lock",
 	}
diff --git a/pkg/osvscanner/osvscanner_internal_test.go b/pkg/osvscanner/osvscanner_internal_test.go
index f5b1340..2986aff 100644
--- a/pkg/osvscanner/osvscanner_internal_test.go
+++ b/pkg/osvscanner/osvscanner_internal_test.go
@@ -65,6 +65,7 @@ func Test_filterResults(t *testing.T) {
 				out := filepath.Join(tt.testCase.path, "out.json")
 				t.Errorf("filterResults() returned an unexpected results (-want, +got):\n%s\n"+
 					"Full json output written to %s", diff, out)
+				//nolint:staticcheck
 				testutility.CreateJSONFixture(t, out, got)
 			}
 			if filtered != tt.testCase.numFiltered {
diff --git a/pkg/osvscanner/vulnerability_result_internal_test.go b/pkg/osvscanner/vulnerability_result_internal_test.go
index 5a81b67..9fa6fc2 100644
--- a/pkg/osvscanner/vulnerability_result_internal_test.go
+++ b/pkg/osvscanner/vulnerability_result_internal_test.go
@@ -13,14 +13,14 @@ import (
 func Test_assembleResult(t *testing.T) {
 	t.Parallel()
 	type args struct {
-		r                 reporter.Reporter
-		packages          []scannedPackage
-		vulnsResp         *osv.HydratedBatchedResponse
-		licensesResp      [][]models.License
-		callAnalysis      bool
-		allPackages       bool
-		licenses          bool
-		licensesAllowlist []string
+		r                  reporter.Reporter
+		packages           []scannedPackage
+		vulnsResp          *osv.HydratedBatchedResponse
+		licensesResp       [][]models.License
+		callAnalysisStates map[string]bool
+		allPackages        bool
+		licenses           bool
+		licensesAllowlist  []string
 	}
 	packages := []scannedPackage{
 		{
@@ -75,6 +75,8 @@ func Test_assembleResult(t *testing.T) {
 		{models.License("UNKNOWN")},
 	}
 
+	callAnalysisStates := make(map[string]bool)
+
 	tests := []struct {
 		name string
 		args args
@@ -82,14 +84,14 @@ func Test_assembleResult(t *testing.T) {
 	}{{
 		name: "group vulnerabilities",
 		args: args{
-			r:                 &reporter.VoidReporter{},
-			packages:          packages,
-			vulnsResp:         vulnsResp,
-			licensesResp:      licensesResp,
-			allPackages:       false,
-			callAnalysis:      false,
-			licenses:          false,
-			licensesAllowlist: nil,
+			r:                  &reporter.VoidReporter{},
+			packages:           packages,
+			vulnsResp:          vulnsResp,
+			licensesResp:       licensesResp,
+			allPackages:        false,
+			callAnalysisStates: callAnalysisStates,
+			licenses:           false,
+			licensesAllowlist:  nil,
 		},
 		want: models.VulnerabilityResults{
 			Results: []models.PackageSource{
@@ -115,7 +117,10 @@ func Test_assembleResult(t *testing.T) {
 								},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"CVE-123", "GHSA-123"}},
+								{
+									IDs:     []string{"CVE-123", "GHSA-123"},
+									Aliases: []string{"CVE-123", "GHSA-123"},
+								},
 							},
 						},
 					},
@@ -136,7 +141,10 @@ func Test_assembleResult(t *testing.T) {
 								{ID: "GHSA-456"},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"GHSA-456"}},
+								{
+									IDs:     []string{"GHSA-456"},
+									Aliases: []string{"GHSA-456"},
+								},
 							},
 						},
 					},
@@ -146,14 +154,14 @@ func Test_assembleResult(t *testing.T) {
 	}, {
 		name: "group vulnerabilities, with all packages included",
 		args: args{
-			r:                 &reporter.VoidReporter{},
-			packages:          packages,
-			vulnsResp:         vulnsResp,
-			licensesResp:      licensesResp,
-			allPackages:       true,
-			callAnalysis:      false,
-			licenses:          false,
-			licensesAllowlist: nil,
+			r:                  &reporter.VoidReporter{},
+			packages:           packages,
+			vulnsResp:          vulnsResp,
+			licensesResp:       licensesResp,
+			allPackages:        true,
+			callAnalysisStates: callAnalysisStates,
+			licenses:           false,
+			licensesAllowlist:  nil,
 		},
 		want: models.VulnerabilityResults{
 			Results: []models.PackageSource{
@@ -179,7 +187,10 @@ func Test_assembleResult(t *testing.T) {
 								},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"CVE-123", "GHSA-123"}},
+								{
+									IDs:     []string{"CVE-123", "GHSA-123"},
+									Aliases: []string{"CVE-123", "GHSA-123"},
+								},
 							},
 						}, {
 							Package: models.PackageInfo{
@@ -206,7 +217,10 @@ func Test_assembleResult(t *testing.T) {
 								{ID: "GHSA-456"},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"GHSA-456"}},
+								{
+									IDs:     []string{"GHSA-456"},
+									Aliases: []string{"GHSA-456"},
+								},
 							},
 						},
 					},
@@ -216,14 +230,14 @@ func Test_assembleResult(t *testing.T) {
 	}, {
 		name: "group vulnerabilities with licenses",
 		args: args{
-			r:                 &reporter.VoidReporter{},
-			packages:          packages,
-			vulnsResp:         vulnsResp,
-			licensesResp:      licensesResp,
-			allPackages:       false,
-			callAnalysis:      false,
-			licenses:          true,
-			licensesAllowlist: nil,
+			r:                  &reporter.VoidReporter{},
+			packages:           packages,
+			vulnsResp:          vulnsResp,
+			licensesResp:       licensesResp,
+			allPackages:        false,
+			callAnalysisStates: callAnalysisStates,
+			licenses:           true,
+			licensesAllowlist:  nil,
 		},
 		want: models.VulnerabilityResults{
 			ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{
@@ -255,7 +269,10 @@ func Test_assembleResult(t *testing.T) {
 								},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"CVE-123", "GHSA-123"}},
+								{
+									IDs:     []string{"CVE-123", "GHSA-123"},
+									Aliases: []string{"CVE-123", "GHSA-123"},
+								},
 							},
 							Licenses:          makeLicenses([]string{"MIT", "0BSD"}),
 							LicenseViolations: makeLicenses([]string{"MIT", "0BSD"}),
@@ -286,7 +303,10 @@ func Test_assembleResult(t *testing.T) {
 								{ID: "GHSA-456"},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"GHSA-456"}},
+								{
+									IDs:     []string{"GHSA-456"},
+									Aliases: []string{"GHSA-456"},
+								},
 							},
 							Licenses:          makeLicenses([]string{"UNKNOWN"}),
 							LicenseViolations: makeLicenses([]string{"UNKNOWN"}),
@@ -298,14 +318,14 @@ func Test_assembleResult(t *testing.T) {
 	}, {
 		name: "group vulnerabilities with license allowlist",
 		args: args{
-			r:                 &reporter.VoidReporter{},
-			packages:          packages,
-			vulnsResp:         vulnsResp,
-			licensesResp:      licensesResp,
-			allPackages:       false,
-			callAnalysis:      false,
-			licenses:          true,
-			licensesAllowlist: []string{"MIT", "0BSD"},
+			r:                  &reporter.VoidReporter{},
+			packages:           packages,
+			vulnsResp:          vulnsResp,
+			licensesResp:       licensesResp,
+			allPackages:        false,
+			callAnalysisStates: callAnalysisStates,
+			licenses:           true,
+			licensesAllowlist:  []string{"MIT", "0BSD"},
 		},
 		want: models.VulnerabilityResults{
 			ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{
@@ -337,7 +357,10 @@ func Test_assembleResult(t *testing.T) {
 								},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"CVE-123", "GHSA-123"}},
+								{
+									IDs:     []string{"CVE-123", "GHSA-123"},
+									Aliases: []string{"CVE-123", "GHSA-123"},
+								},
 							},
 							Licenses: makeLicenses([]string{"MIT", "0BSD"}),
 						},
@@ -359,7 +382,10 @@ func Test_assembleResult(t *testing.T) {
 								{ID: "GHSA-456"},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"GHSA-456"}},
+								{
+									IDs:     []string{"GHSA-456"},
+									Aliases: []string{"GHSA-456"},
+								},
 							},
 							Licenses:          makeLicenses([]string{"UNKNOWN"}),
 							LicenseViolations: makeLicenses([]string{"UNKNOWN"}),
@@ -371,14 +397,14 @@ func Test_assembleResult(t *testing.T) {
 	}, {
 		name: "group vulnerabilities, with license allowlist and all packages",
 		args: args{
-			r:                 &reporter.VoidReporter{},
-			packages:          packages,
-			vulnsResp:         vulnsResp,
-			licensesResp:      licensesResp,
-			allPackages:       true,
-			callAnalysis:      false,
-			licenses:          true,
-			licensesAllowlist: []string{"MIT", "0BSD"},
+			r:                  &reporter.VoidReporter{},
+			packages:           packages,
+			vulnsResp:          vulnsResp,
+			licensesResp:       licensesResp,
+			allPackages:        true,
+			callAnalysisStates: callAnalysisStates,
+			licenses:           true,
+			licensesAllowlist:  []string{"MIT", "0BSD"},
 		},
 		want: models.VulnerabilityResults{
 			ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{
@@ -410,7 +436,10 @@ func Test_assembleResult(t *testing.T) {
 								},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"CVE-123", "GHSA-123"}},
+								{
+									IDs:     []string{"CVE-123", "GHSA-123"},
+									Aliases: []string{"CVE-123", "GHSA-123"},
+								},
 							},
 							Licenses: makeLicenses([]string{"MIT", "0BSD"}),
 						}, {
@@ -439,7 +468,10 @@ func Test_assembleResult(t *testing.T) {
 								{ID: "GHSA-456"},
 							},
 							Groups: []models.GroupInfo{
-								{IDs: []string{"GHSA-456"}},
+								{
+									IDs:     []string{"GHSA-456"},
+									Aliases: []string{"GHSA-456"},
+								},
 							},
 							Licenses:          makeLicenses([]string{"UNKNOWN"}),
 							LicenseViolations: makeLicenses([]string{"UNKNOWN"}),
@@ -453,7 +485,7 @@ func Test_assembleResult(t *testing.T) {
 		tt := tt // Reinitialize for t.Parallel()
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
-			if got := buildVulnerabilityResults(tt.args.r, tt.args.packages, tt.args.vulnsResp, tt.args.licensesResp, tt.args.callAnalysis, tt.args.allPackages, tt.args.licenses, tt.args.licensesAllowlist); !reflect.DeepEqual(got, tt.want) {
+			if got := buildVulnerabilityResults(tt.args.r, tt.args.packages, tt.args.vulnsResp, tt.args.licensesResp, tt.args.callAnalysisStates, tt.args.allPackages, tt.args.licenses, tt.args.licensesAllowlist); !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("buildVulnerabilityResults() = %v,\nwant %v", got, tt.want)
 			}
 		})