Repository: google/osv-scanner
Pull request #666 (2023-11-19 15:27:44)

chore(deps): update workflows
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| gaurav-nelson/github-action-markdown-link-check | action | digest | `a996638` -> `0f074c8` |
| [github/codeql-action](https://togithub.com/github/codeql-action) | action | patch | `v2.22.5` -> `v2.22.8` |

---

### Release Notes

<details>
<summary>github/codeql-action (github/codeql-action)</summary>

### [`v2.22.8`](https://togithub.com/github/codeql-action/compare/v2.22.7...v2.22.8)

[Compare Source](https://togithub.com/github/codeql-action/compare/v2.22.7...v2.22.8)

### [`v2.22.7`](https://togithub.com/github/codeql-action/compare/v2.22.6...v2.22.7)

[Compare Source](https://togithub.com/github/codeql-action/compare/v2.22.6...v2.22.7)

### [`v2.22.6`](https://togithub.com/github/codeql-action/compare/v2.22.5...v2.22.6)

[Compare Source](https://togithub.com/github/codeql-action/compare/v2.22.5...v2.22.6)

</details>

---

### Configuration

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

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

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

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired.

---

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

---

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


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/google/osv-scanner/pull/666?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
All modified and coverable lines are covered by tests :white_check_mark:
> 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 [(`19c4bfd`)](https://app.codecov.io/gh/google/osv-scanner/pull/666?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) 78.14%.


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


```diff
@@           Coverage Diff           @@
##             main     #666   +/-   ##
=======================================
  Coverage   78.14%   78.14%           
=======================================
  Files          81       81           
  Lines        5866     5866           
=======================================
  Hits         4584     4584           
  Misses       1082     1082           
  Partials      200      200           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/google/osv-scanner/pull/666?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).


Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index dce11b1..c7f43eb 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -44,7 +44,7 @@ jobs:
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+        uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
         with:
           languages: ${{ matrix.language }}
           # If you wish to specify custom queries, you can do so here or in a config file.
@@ -55,7 +55,7 @@ jobs:
       # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
       # If this step fails, then you should remove it and run the build manually (see below)
       - name: Autobuild
-        uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+        uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
 
       # ℹ️ Command-line programs to run using the OS shell.
       # 📚 https://git.io/JvXDl
@@ -69,4 +69,4 @@ jobs:
       #   make release
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+        uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 720c70b..d7c1fda 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -1,16 +1,9 @@
 name: Release new version
 
 on:
-  workflow_dispatch:
-    inputs:
-      version:
-        description: 'The version tag to release, (e.g. v1.2.3)'
-        required: true
-        type: string
-      commit:
-        description: 'The commit hash to release'
-        required: true
-        type: string
+  push:
+    tags:
+      - "*" # triggers only if push new tag version, like `v0.8.4` 
 
 permissions:
   contents: read # to fetch code (actions/checkout)
@@ -18,70 +11,6 @@ permissions:
   security-events: write
 
 jobs:
-  osv-scan:
-    uses: ./.github/workflows/osv-scanner-reusable.yml
-    with:
-      # Only scan the top level go.mod file without recursively scanning directories since
-      # this is pipeline is about releasing the go module and binary
-      scan-args: |-
-        --skip-git
-        ./
-
-  lint:
-    name: golangci-lint
-    runs-on: ubuntu-latest
-    steps:
-      - name: Check out code
-        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
-        with:
-          persist-credentials: false
-          ref: ${{ inputs.commit }}
-      - name: Set up Go
-        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
-        with:
-          go-version-file: .go-version
-          check-latest: true
-      - name: Run lint action
-        uses: ./.github/workflows/lint-action
-  tests:
-    name: Run unit tests
-    strategy:
-      fail-fast: false
-      matrix:
-        os: [ubuntu-latest, macos-latest, windows-latest]
-    runs-on: ${{ matrix.os }}
-    steps:
-      - name: Check out code
-        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
-        with:
-          persist-credentials: false
-          ref: ${{ inputs.commit }}
-      - name: Set up Go
-        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
-        with:
-          go-version-file: .go-version
-          check-latest: true
-      - name: Run test action
-        uses: ./.github/workflows/test-action
-  tag-release:
-    runs-on: ubuntu-latest
-    needs:
-      - lint
-      - tests
-      - osv-scan
-    permissions:
-      contents: write  # to write a tag
-    steps:
-      - name: Create tag
-        uses: actions/github-script@v6
-        with:
-          script: |
-            github.rest.git.createRef({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              ref: 'refs/tags/${{ inputs.version }}',
-              sha: '${{ inputs.commit }}'
-            })
   goreleaser:
     outputs:
       hashes: ${{ steps.hash.outputs.hashes }}
@@ -89,8 +18,6 @@ jobs:
       contents: write  # for goreleaser/goreleaser-action to create a GitHub release
       packages: write # for goreleaser/goreleaser-action to publish docker images
     runs-on: ubuntu-latest
-    needs:
-      - tag-release
     env:
       # Required for buildx on docker 19.x
       DOCKER_CLI_EXPERIMENTAL: "enabled"
@@ -140,4 +67,3 @@ jobs:
       base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
       upload-assets: true # upload to a new release
       draft-release: true # upload to a new draft release
-      upload-tag-name: "${{ inputs.version }}"
diff --git a/.github/workflows/link-check-on-push.yml b/.github/workflows/link-check-on-push.yml
index 62804c8..78098be 100644
--- a/.github/workflows/link-check-on-push.yml
+++ b/.github/workflows/link-check-on-push.yml
@@ -8,7 +8,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
-    - uses: gaurav-nelson/github-action-markdown-link-check@a996638015fbc9ef96beef1a41bbad7df8e06154
+    - uses: gaurav-nelson/github-action-markdown-link-check@0f074c8562c5a8fed38282b7c741d1970bb1512d
       with:
         use-quiet-mode: "yes"
         base-branch: "main"
diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml
index a83338c..83ec3a6 100644
--- a/.github/workflows/link-check.yml
+++ b/.github/workflows/link-check.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
-    - uses: gaurav-nelson/github-action-markdown-link-check@a996638015fbc9ef96beef1a41bbad7df8e06154
+    - uses: gaurav-nelson/github-action-markdown-link-check@0f074c8562c5a8fed38282b7c741d1970bb1512d
       with:
         use-quiet-mode: "yes"
 # Documentation available here: https://github.com/marketplace/actions/markdown-link-check
diff --git a/.github/workflows/lint-action/action.yml b/.github/workflows/lint-action/action.yml
index 245842e..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.54.2
+        version: v1.55.2
         # https://github.com/golangci/golangci-lint-action/issues/135
         skip-pkg-cache: true
diff --git a/.github/workflows/osv-scanner-reusable-pr.yml b/.github/workflows/osv-scanner-reusable-pr.yml
index 345c663..3deb344 100644
--- a/.github/workflows/osv-scanner-reusable-pr.yml
+++ b/.github/workflows/osv-scanner-reusable-pr.yml
@@ -95,6 +95,6 @@ jobs:
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
         if: '!cancelled()'
-        uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+        uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
         with:
           sarif_file: ${{ inputs.results-file-name }}
diff --git a/.github/workflows/osv-scanner-reusable.yml b/.github/workflows/osv-scanner-reusable.yml
index 84a6d70..f1824d3 100644
--- a/.github/workflows/osv-scanner-reusable.yml
+++ b/.github/workflows/osv-scanner-reusable.yml
@@ -78,7 +78,7 @@ jobs:
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
         if: '!cancelled()'
-        uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+        uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
         with:
           sarif_file: ${{ inputs.results-file-name }}
 
diff --git a/.github/workflows/prerelease-check.yml b/.github/workflows/prerelease-check.yml
new file mode 100644
index 0000000..77ed541
--- /dev/null
+++ b/.github/workflows/prerelease-check.yml
@@ -0,0 +1,81 @@
+name: Pre-release check
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: 'The version tag to release, (e.g. v1.2.3)'
+        required: true
+        type: string
+      commit:
+        description: 'The commit hash to release'
+        required: true
+        type: string
+
+permissions:
+  contents: read # to fetch code (actions/checkout)
+  # Require writing security events to upload SARIF file to security tab
+  security-events: write
+
+jobs:
+  osv-scan:
+    uses: ./.github/workflows/osv-scanner-reusable.yml
+    with:
+      # Only scan the top level go.mod file without recursively scanning directories since
+      # this is pipeline is about releasing the go module and binary
+      scan-args: |-
+        --skip-git
+        ./
+
+  lint:
+    name: golangci-lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out code
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+        with:
+          persist-credentials: false
+          ref: ${{ inputs.commit }}
+      - name: Set up Go
+        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+        with:
+          go-version-file: .go-version
+          check-latest: true
+      - name: Run lint action
+        uses: ./.github/workflows/lint-action
+  tests:
+    name: Run unit tests
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest, macos-latest, windows-latest]
+    runs-on: ${{ matrix.os }}
+    steps:
+      - name: Check out code
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+        with:
+          persist-credentials: false
+          ref: ${{ inputs.commit }}
+      - name: Set up Go
+        uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0
+        with:
+          go-version-file: .go-version
+          check-latest: true
+      - name: Run test action
+        uses: ./.github/workflows/test-action
+  release-helper:
+    runs-on: ubuntu-latest
+    needs:
+      - lint
+      - tests
+      - osv-scan
+    steps:
+      - name: Print Scripts
+        env:
+          OUTPUT: |
+            git fetch upstream &&
+            git tag ${{ inputs.version }} ${{ inputs.commit }} &&
+            git push upstream ${{ inputs.version }}
+        shell: bash
+        run: |
+          echo $OUTPUT
\ No newline at end of file
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 293b2aa..b2554c8 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -67,6 +67,6 @@ jobs:
 
       # Upload the results to GitHub's code scanning dashboard.
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
+        uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8
         with:
           sarif_file: results.sarif
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..e5bcfb0 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",
@@ -124,9 +132,13 @@ func run(args []string, stdout, stderr io.Writer) int {
 				Name:  "experimental-all-packages",
 				Usage: "when json output is selected, prints all packages",
 			},
+			&cli.BoolFlag{
+				Name:  "experimental-licenses-summary",
+				Usage: "report a license summary, implying the --experimental-all-packages flag",
+			},
 			&cli.StringSliceFlag{
 				Name:  "experimental-licenses",
-				Usage: "report on licenses",
+				Usage: "report on licenses based on an allowlist",
 			},
 		},
 		ArgsUsage: "[directory1 directory2...]",
@@ -155,10 +167,29 @@ func run(args []string, stdout, stderr io.Writer) int {
 				}
 			}
 
+			if context.Bool("experimental-licenses-summary") && context.IsSet("experimental-licenses") {
+				return fmt.Errorf("--experimental-licenses-summary and --experimental-licenses flags cannot be set")
+			}
+			allowlist := context.StringSlice("experimental-licenses")
+			if context.IsSet("experimental-licenses") &&
+				(len(allowlist) == 0 ||
+					(len(allowlist) == 1 && allowlist[0] == "")) {
+				return fmt.Errorf("--experimental-licenses requires at least one value")
+			}
+			// TODO: verify that the licenses they passed in are indeed spdx.
+
 			if r, err = reporter.New(format, stdout, stderr, termWidth); err != nil {
 				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,27 +199,26 @@ 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"),
-					ScanLicenses:          context.IsSet("experimental-licenses"),
+					LocalDBPath:    context.String("experimental-local-db-path"),
+					CompareLocally: context.Bool("experimental-local-db"),
+					CompareOffline: context.Bool("experimental-offline"),
+					// License summary mode causes all
+					// packages to appear in the json as
+					// every package has a license - even
+					// if it's just the UNKNOWN license.
+					ShowAllPackages: context.Bool("experimental-all-packages") ||
+						context.Bool("experimental-licenses-summary"),
+					ScanLicensesSummary:   context.Bool("experimental-licenses-summary"),
 					ScanLicensesAllowlist: context.StringSlice("experimental-licenses"),
 				},
 			}, 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 +234,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/experimental.md b/docs/experimental.md
index bb2b77e..f2fdd5c 100644
--- a/docs/experimental.md
+++ b/docs/experimental.md
@@ -20,46 +20,6 @@ nav_order: 8
 {: .note }
 Features and flags with the `experimental` prefix might change or be removed with only a minor version update.
 
-## Scanning with call analysis  
-
-Call stack analysis can be performed on some languages to check if the 
-vulnerable code is actually being executed by your project. If the code
-is not being executed, these vulnerabilities will be marked as unexecuted.
-
-To enable call analysis, call OSV-Scanner with the `--experimental-call-analysis` flag.
-
-### Call analysis in Go
-
-OSV-Scanner uses the `govulncheck` library to analyze Go source code to identify called vulnerable functions.
-
-#### Additional Dependencies
-
-`go` compiler needs to be installed and available on `PATH`    
-
-### Call analysis in Rust
-
-OSV-Scanner compiles Rust source code and analyzes the output binary's DWARF debug information to identify called vulnerable functions.
-
-#### Additional Dependencies
-
-Rust toolchain (including `cargo`) that can compile the source code being scanned needs to be installed and available on `PATH`.
-
-The installed Rust toolchain must be capable of compiling every crate/target in the scanned code, for code with
-a lot of dependencies this will take a few minutes.
-
-### Limitations
-
-Current implementation has a few limitations:
-
-- Does not support dependencies on proc-macros (Tracked in [#464](https://github.com/google/osv-scanner/issues/464))
-- Does not support any dependencies that are dynamically linked
-- Does not support dependencies that link external non-rust code
-
-### Example
-```bash
-osv-scanner --experimental-call-analysis ./my/project/path
-```
-
 ## Offline mode
 
 OSV-Scanner now supports offline scanning as an experimental feature. Offline scanning checks your project against a local database instead of calling the OSV.dev API.
@@ -128,3 +88,38 @@ Set the location of your manually downloaded database by following the instructi
 ### Limitations
 
 1. Commit level scanning is not supported. 
+
+## License scanning
+
+OSV-Scanner supports license checking as an experimental feature. The data comes from the [deps.dev API](https://docs.deps.dev/api/).
+
+### License summary
+
+If you want a summary of your dependencies licenses, use the `--experimental-licenses-summary` flag:
+
+```bash
+osv-scanner --experimental-licenses-summary path/to/repository`
+```
+
+### License violations
+
+To set an allowed license list and see the details of packages that do not conform, use the `--experimental-licenses` flag:
+
+```bash
+osv-scanner --experimental-licenses="comma-separated list of allowed licenses" path/to/directory
+```
+Include your allowed licenses as a comma-separated list. OSV-Scanner recognizes licenses in SPDX format. Please indicate your allowed licenses using [SPDX license](https://spdx.org/licenses/) identifiers. 
+
+#### License violations example
+
+If you wanted to allow the following licenses:
+
+- [BSD 3-Clause "New" or "Revised" License](https://spdx.org/licenses/BSD-3-Clause.html)
+- [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html)
+- [MIT](https://spdx.org/licenses/MIT.html)
+
+Your command would be in this form:
+
+```bash
+osv-scanner --experimental-licenses="BSD-3-Clause,Apache-2.0,MIT" path/to/directory
+```
\ No newline at end of file
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/docs/usage.md b/docs/usage.md
index db8619e..ac34a50 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -145,3 +145,46 @@ OSV-Scanner's C/C++ support is based on commit-level data. OSV's commit-level da
 ### Vendored dependencies
 
 Vendored dependencies have been directly copied into the project folder, but do not retain their Git histories. OSV-Scanner uses OSV's [determineversion API](https://google.github.io/osv.dev/post-v1-determineversion/) to estimate each dependency's version (and associated Git commit). Vulnerabilities for the estimated version are returned. This process requires no additional work from the user. Run OSV-Scanner as you normally would. 
+
+## Scanning with call analysis
+
+Call stack analysis can be performed on some languages to check if the 
+vulnerable code is actually being executed by your project. If the code
+is not being executed, these vulnerabilities will be marked as unexecuted.
+
+To enable call analysis in all languages, call OSV-Scanner with the `--call-analysis=all` flag. By default, call analysis in Go is enabled, but you can disable it using the `--no-call-analysis=go` flag.
+
+### Call analysis in Go
+
+OSV-Scanner uses the [`govulncheck`](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) library to analyze Go source code to identify called vulnerable functions.
+
+#### Additional Dependencies
+
+`go` compiler needs to be installed and available on `PATH`
+
+### Call analysis in Rust
+
+Experimental {: .label }
+Call analysis in Rust is still considered experimental. 
+
+OSV-Scanner compiles Rust source code and analyzes the output binary's DWARF debug information to identify called vulnerable functions.
+
+#### Additional Dependencies
+
+Rust toolchain (including `cargo`) that can compile the source code being scanned needs to be installed and available on `PATH`.
+
+The installed Rust toolchain must be capable of compiling every crate/target in the scanned code, for code with
+a lot of dependencies this will take a few minutes.
+
+### Limitations
+
+Current implementation has a few limitations:
+
+- Does not support dependencies on proc-macros (Tracked in [#464](https://github.com/google/osv-scanner/issues/464))
+- Does not support any dependencies that are dynamically linked
+- Does not support dependencies that link external non-rust code
+
+### Example
+```bash
+osv-scanner --call-analysis=rust --no-call-analysis=go ./my/project/path
+```
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/table.go b/internal/output/table.go
index 3f26023..eae4154 100644
--- a/internal/output/table.go
+++ b/internal/output/table.go
@@ -173,14 +173,13 @@ func MaxSeverity(group models.GroupInfo, pkg models.PackageVulns) string {
 
 func licenseTableBuilder(outputTable table.Writer, vulnResult *models.VulnerabilityResults) table.Writer {
 	licenseConfig := vulnResult.ExperimentalAnalysisConfig.Licenses
-	if !licenseConfig.Enabled {
-		return outputTable
-	}
-	if len(licenseConfig.Allowlist) == 0 {
+	if licenseConfig.Summary {
 		return licenseSummaryTableBuilder(outputTable, vulnResult)
+	} else if len(licenseConfig.Allowlist) > 0 {
+		return licenseViolationsTableBuilder(outputTable, vulnResult)
+	} else {
+		return outputTable
 	}
-
-	return licenseViolationsTableBuilder(outputTable, vulnResult)
 }
 
 func licenseSummaryTableBuilder(outputTable table.Writer, vulnResult *models.VulnerabilityResults) table.Writer {
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/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/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-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 9e1a80a..5e09984 100644
--- a/pkg/lockfile/parse.go
+++ b/pkg/lockfile/parse.go
@@ -33,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..4aba0d5 100644
--- a/pkg/models/results.go
+++ b/pkg/models/results.go
@@ -15,16 +15,11 @@ 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 {
-	Enabled   bool      `json:"enabled"`
+	Summary   bool      `json:"summary"`
 	Allowlist []License `json:"allowlist"`
 }
 
@@ -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/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 38ed2f7..03a7aed 100644
--- a/pkg/osvscanner/osvscanner.go
+++ b/pkg/osvscanner/osvscanner.go
@@ -39,16 +39,16 @@ 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
-	ScanLicenses          bool
+	ScanLicensesSummary   bool
 	ScanLicensesAllowlist []string
 
 	LocalDBPath string
@@ -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":    {},
@@ -639,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
@@ -790,13 +786,13 @@ func DoScan(actions ScannerActions, r reporter.Reporter) (models.VulnerabilityRe
 	}
 
 	var licensesResp [][]models.License
-	if actions.ScanLicenses {
+	if len(actions.ScanLicensesAllowlist) > 0 || actions.ScanLicensesSummary {
 		licensesResp, err = makeLicensesRequests(filteredScannedPackages)
 		if err != nil {
 			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)
 
 	filtered := filterResults(r, &results, &configManager, actions.ShowAllPackages)
 	if filtered > 0 {
@@ -826,26 +822,13 @@ func DoScan(actions ScannerActions, r reporter.Reporter) (models.VulnerabilityRe
 			}
 		}
 		onlyUncalledVuln = onlyUncalledVuln && vuln
+		licenseViolation = licenseViolation && len(actions.ScanLicensesAllowlist) > 0
 
-		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 cbdb104..2d20e4e 100644
--- a/pkg/osvscanner/vulnerability_result.go
+++ b/pkg/osvscanner/vulnerability_result.go
@@ -21,21 +21,14 @@ func buildVulnerabilityResults(
 	packages []scannedPackage,
 	vulnsResp *osv.HydratedBatchedResponse,
 	licensesResp [][]models.License,
-	callAnalysis bool,
-	allPackages bool,
-	licenses bool,
-	licensesAllowlist []string,
+	actions ScannerActions,
 ) models.VulnerabilityResults {
 	output := models.VulnerabilityResults{
 		Results: []models.PackageSource{},
 	}
 	groupedBySource := map[models.SourceInfo][]models.PackageVulns{}
-	if len(licensesAllowlist) == 1 && licensesAllowlist[0] == "" {
-		licensesAllowlist = []string{}
-	}
-
 	for i, rawPkg := range packages {
-		includePackage := allPackages
+		includePackage := actions.ShowAllPackages
 		var pkg models.PackageVulns
 		if rawPkg.Commit != "" {
 			pkg.Package.Commit = rawPkg.Commit
@@ -63,10 +56,10 @@ func buildVulnerabilityResults(
 			pkg.Vulnerabilities = vulnsResp.Results[i].Vulns
 			pkg.Groups = grouper.Group(grouper.ConvertVulnerabilityToIDAliases(pkg.Vulnerabilities))
 		}
-		if licenses {
+		if len(actions.ScanLicensesAllowlist) > 0 {
 			pkg.Licenses = licensesResp[i]
 			allowlist := make(map[string]bool)
-			for _, license := range licensesAllowlist {
+			for _, license := range actions.ScanLicensesAllowlist {
 				allowlist[strings.ToLower(license)] = true
 			}
 			for _, license := range pkg.Licenses {
@@ -78,15 +71,16 @@ func buildVulnerabilityResults(
 				includePackage = true
 			}
 		}
+		if actions.ScanLicensesSummary {
+			pkg.Licenses = licensesResp[i]
+		}
 		if includePackage {
 			groupedBySource[rawPkg.Source] = append(groupedBySource[rawPkg.Source], pkg)
 		}
 	}
 
 	for source, packages := range groupedBySource {
-		if callAnalysis {
-			sourceanalysis.Run(r, source, packages)
-		}
+		sourceanalysis.Run(r, source, packages, actions.CallAnalysisStates)
 		output.Results = append(output.Results, models.PackageSource{
 			Source:   source,
 			Packages: packages,
@@ -101,17 +95,14 @@ func buildVulnerabilityResults(
 		return output.Results[i].Source.Path < output.Results[j].Source.Path
 	})
 
-	if licenses {
-		output.ExperimentalAnalysisConfig.Licenses.Enabled = true
-		allowlist := make([]models.License, len(licensesAllowlist))
-		for i, l := range licensesAllowlist {
+	if len(actions.ScanLicensesAllowlist) > 0 || actions.ScanLicensesSummary {
+		output.ExperimentalAnalysisConfig.Licenses.Summary = actions.ScanLicensesSummary
+		allowlist := make([]models.License, len(actions.ScanLicensesAllowlist))
+		for i, l := range actions.ScanLicensesAllowlist {
 			allowlist[i] = models.License(l)
 		}
 		output.ExperimentalAnalysisConfig.Licenses.Allowlist = allowlist
 	}
-	if callAnalysis {
-		output.ExperimentalAnalysisConfig.CallAnalysis.Enabled = true
-	}
 
 	return output
 }
diff --git a/scripts/run_lints.sh b/scripts/run_lints.sh
index 0bc3e76..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.54.2 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 d105487..05afb81 100644
--- a/cmd/osv-scanner/main_test.go
+++ b/cmd/osv-scanner/main_test.go
@@ -7,6 +7,7 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
+	"strconv"
 	"strings"
 	"testing"
 
@@ -59,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")
 	}
 
@@ -265,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
 			`,
@@ -273,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: `
@@ -287,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: `
@@ -299,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: `
@@ -344,18 +345,15 @@ 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,
+							"summary": false,
 							"allowlist": null
 						}
 					}
@@ -367,18 +365,15 @@ 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,
+							"summary": false,
 							"allowlist": null
 						}
 					}
@@ -538,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
@@ -968,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
 			`,
@@ -1064,11 +1074,8 @@ func TestRun_LocalDatabases(t *testing.T) {
 				{
 					"results": [],
 					"experimental_config": {
-						"call_analysis": {
-							"enabled": false
-						},
 						"licenses": {
-							"enabled": false,
+							"summary": false,
 							"allowlist": null
 						}
 					}
@@ -1088,11 +1095,8 @@ func TestRun_LocalDatabases(t *testing.T) {
 				{
 					"results": [],
 					"experimental_config": {
-						"call_analysis": {
-							"enabled": false
-						},
 						"licenses": {
-							"enabled": false,
+							"summary": false,
 							"allowlist": null
 						}
 					}
@@ -1143,9 +1147,9 @@ func TestRun_Licenses(t *testing.T) {
 	t.Parallel()
 	tests := []cliTestCase{
 		{
-			name:         "No vulnerabilities but contains license violations",
-			args:         []string{"", "--experimental-licenses", "", "./fixtures/locks-many"},
-			wantExitCode: 4,
+			name:         "No vulnerabilities with license summary",
+			args:         []string{"", "--experimental-licenses-summary", "./fixtures/locks-many"},
+			wantExitCode: 0,
 			wantStdout: `
 				Scanning dir ./fixtures/locks-many
 				Scanned <rootdir>/fixtures/locks-many/Gemfile.lock file and found 1 package
@@ -1155,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 |
@@ -1168,9 +1172,9 @@ func TestRun_Licenses(t *testing.T) {
 			wantStderr: "",
 		},
 		{
-			name:         "No vulnerabilities but contains license violations markdown",
-			args:         []string{"", "--experimental-licenses", "", "--format=markdown", "./fixtures/locks-many"},
-			wantExitCode: 4,
+			name:         "No vulnerabilities with license summary in markdown",
+			args:         []string{"", "--experimental-licenses-summary", "--format=markdown", "./fixtures/locks-many"},
+			wantExitCode: 0,
 			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
@@ -1179,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 |
 | --- | ---:|
@@ -1190,9 +1194,9 @@ Filtered 2 vulnerabilities from output
 			wantStderr: "",
 		},
 		{
-			name:         "Vulnerabilities and license violations",
-			args:         []string{"", "--experimental-licenses", "", "--config=./fixtures/osv-scanner-empty-config.toml", "./fixtures/locks-many/package-lock.json"},
-			wantExitCode: 5,
+			name:         "Vulnerabilities and license summary",
+			args:         []string{"", "--experimental-licenses-summary", "--config=./fixtures/osv-scanner-empty-config.toml", "./fixtures/locks-many/package-lock.json"},
+			wantExitCode: 1,
 			wantStdout: `
 				Scanning dir ./fixtures/locks-many/package-lock.json
 				Scanned <rootdir>/fixtures/locks-many/package-lock.json file and found 1 package
@@ -1212,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
@@ -1247,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": [
@@ -1261,8 +1265,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "babel",
 									"version": "6.23.0",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1272,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"
@@ -1286,8 +1288,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "ms",
 									"version": "2.1.3",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1297,11 +1298,8 @@ Filtered 2 vulnerabilities from output
 					}
 				],
 				"experimental_config": {
-					"call_analysis": {
-						"enabled": false
-					},
 					"licenses": {
-						"enabled": true,
+						"summary": false,
 						"allowlist": [
 							"MIT"
 						]
@@ -1317,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": [
@@ -1331,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"
@@ -1345,11 +1342,8 @@ Filtered 2 vulnerabilities from output
 					}
 				],
 				"experimental_config": {
-					"call_analysis": {
-						"enabled": false
-					},
 					"licenses": {
-						"enabled": true,
+						"summary": false,
 						"allowlist": [
 							"MIT"
 						]
@@ -1379,8 +1373,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "babel",
 									"version": "6.23.0",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1390,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"
@@ -1401,8 +1393,7 @@ Filtered 2 vulnerabilities from output
 								"package": {
 									"name": "ms",
 									"version": "2.1.3",
-									"ecosystem": "npm",
-									"commit": ""
+									"ecosystem": "npm"
 								},
 								"licenses": [
 									"MIT"
@@ -1412,11 +1403,8 @@ Filtered 2 vulnerabilities from output
 					}
 				],
 				"experimental_config": {
-					"call_analysis": {
-						"enabled": false
-					},
 					"licenses": {
-						"enabled": true,
+						"summary": false,
 						"allowlist": [
 							"MIT",
 							"Apache-2.0"
@@ -1430,6 +1418,65 @@ Filtered 2 vulnerabilities from output
 			Scanned <rootdir>/fixtures/locks-licenses/package-lock.json file and found 3 packages
 			`,
 		},
+		{
+			name:         "Licenses in summary mode json",
+			args:         []string{"", "--format=json", "--experimental-licenses-summary", "./fixtures/locks-licenses/package-lock.json"},
+			wantExitCode: 0,
+			wantStdout: `
+			{
+				"results": [
+					{
+						"source": {
+							"path": "<rootdir>/fixtures/locks-licenses/package-lock.json",
+							"type": "lockfile"
+						},
+						"packages": [
+							{
+								"package": {
+									"name": "babel",
+									"version": "6.23.0",
+									"ecosystem": "npm"
+								},
+								"licenses": [
+									"MIT"
+								]
+							},
+							{
+								"package": {
+									"name": "human-signals",
+									"version": "5.0.0",
+									"ecosystem": "npm"
+								},
+								"licenses": [
+									"Apache-2.0"
+								]
+							},
+							{
+								"package": {
+									"name": "ms",
+									"version": "2.1.3",
+									"ecosystem": "npm"
+								},
+								"licenses": [
+									"MIT"
+								]
+							}
+						]
+					}
+				],
+				"experimental_config": {
+					"licenses": {
+						"summary": true,
+						"allowlist": []
+					}
+				}
+			}
+		`,
+			wantStderr: `
+			Scanning dir ./fixtures/locks-licenses/package-lock.json
+			Scanned <rootdir>/fixtures/locks-licenses/package-lock.json file and found 3 packages
+			`,
+		},
 	}
 	for _, tt := range tests {
 		tt := tt
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/testutility/utility.go b/internal/testutility/utility.go
index 361581a..3817a39 100644
--- a/internal/testutility/utility.go
+++ b/internal/testutility/utility.go
@@ -82,6 +82,9 @@ func AssertMatchFixtureJSON[V any](t *testing.T, path string, val V) {
 	}
 }
 
+// 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) {
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..5781f4e 100644
--- a/pkg/osvscanner/vulnerability_result_internal_test.go
+++ b/pkg/osvscanner/vulnerability_result_internal_test.go
@@ -13,14 +13,11 @@ 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
+		actions      ScannerActions
 	}
 	packages := []scannedPackage{
 		{
@@ -75,6 +72,8 @@ func Test_assembleResult(t *testing.T) {
 		{models.License("UNKNOWN")},
 	}
 
+	callAnalysisStates := make(map[string]bool)
+
 	tests := []struct {
 		name string
 		args args
@@ -82,14 +81,17 @@ 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,
+			actions: ScannerActions{
+				ExperimentalScannerActions: ExperimentalScannerActions{
+					ShowAllPackages:       false,
+					ScanLicensesAllowlist: nil,
+				},
+				CallAnalysisStates: callAnalysisStates,
+			},
 		},
 		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,17 @@ 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,
+			actions: ScannerActions{
+				ExperimentalScannerActions: ExperimentalScannerActions{
+					ShowAllPackages:       true,
+					ScanLicensesAllowlist: nil,
+				},
+				CallAnalysisStates: callAnalysisStates,
+			},
 		},
 		want: models.VulnerabilityResults{
 			Results: []models.PackageSource{
@@ -179,7 +190,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 +220,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,19 +233,23 @@ 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,
+			actions: ScannerActions{
+				ExperimentalScannerActions: ExperimentalScannerActions{
+					ShowAllPackages:       true,
+					ScanLicensesSummary:   true,
+					ScanLicensesAllowlist: nil,
+				},
+				CallAnalysisStates: callAnalysisStates,
+			},
 		},
 		want: models.VulnerabilityResults{
 			ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{
 				Licenses: models.ExperimentalLicenseConfig{
-					Enabled:   true,
+					Summary:   true,
 					Allowlist: []models.License{},
 				},
 			},
@@ -255,18 +276,19 @@ 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"}),
+							Licenses: makeLicenses([]string{"MIT", "0BSD"}),
 						}, {
 							Package: models.PackageInfo{
 								Name:      "pkg-2",
 								Ecosystem: "npm",
 								Version:   "1.0.0",
 							},
-							Licenses:          makeLicenses([]string{"MIT"}),
-							LicenseViolations: makeLicenses([]string{"MIT"}),
+							Licenses: makeLicenses([]string{"MIT"}),
 						},
 					},
 				},
@@ -286,10 +308,12 @@ 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"}),
+							Licenses: makeLicenses([]string{"UNKNOWN"}),
 						},
 					},
 				},
@@ -298,19 +322,21 @@ 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,
+			actions: ScannerActions{
+				ExperimentalScannerActions: ExperimentalScannerActions{
+					ShowAllPackages:       false,
+					ScanLicensesAllowlist: []string{"MIT", "0BSD"},
+				},
+				CallAnalysisStates: callAnalysisStates,
+			},
 		},
 		want: models.VulnerabilityResults{
 			ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{
 				Licenses: models.ExperimentalLicenseConfig{
-					Enabled:   true,
 					Allowlist: []models.License{models.License("MIT"), models.License("0BSD")},
 				},
 			},
@@ -337,7 +363,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 +388,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,19 +403,21 @@ 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,
+			actions: ScannerActions{
+				ExperimentalScannerActions: ExperimentalScannerActions{
+					ShowAllPackages:       true,
+					ScanLicensesAllowlist: []string{"MIT", "0BSD"},
+				},
+				CallAnalysisStates: callAnalysisStates,
+			},
 		},
 		want: models.VulnerabilityResults{
 			ExperimentalAnalysisConfig: models.ExperimentalAnalysisConfig{
 				Licenses: models.ExperimentalLicenseConfig{
-					Enabled:   true,
 					Allowlist: []models.License{models.License("MIT"), models.License("0BSD")},
 				},
 			},
@@ -410,7 +444,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 +476,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 +493,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.actions); !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("buildVulnerabilityResults() = %v,\nwant %v", got, tt.want)
 			}
 		})