Repository: google/osv-scanner
Pull request #652 (2023-11-12 13:03:43)

fix(deps): update osv-scanner minor
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [deps.dev/api/v3alpha](https://togithub.com/google/deps.dev) | require | digest | `a2ccd03` -> `e40c4d5` |
| golang.org/x/exp | require | digest | `7918f67` -> `9a3e603` |
| golang.org/x/term | require | minor | `v0.13.0` -> `v0.14.0` |

---

### 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:eyJjcmVhdGVkSW5WZXIiOiIzNy40Ni4wIiwidXBkYXRlZEluVmVyIjoiMzcuNDYuMCIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/google/osv-scanner/pull/652?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) Report
> Merging [#652](https://app.codecov.io/gh/google/osv-scanner/pull/652?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (869760f) into [main](https://app.codecov.io/gh/google/osv-scanner/commit/007b8e4be4d1366dce16602712b6969bceee6dd5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=google) (007b8e4) will **not change** coverage.
> The diff coverage is `n/a`.

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





Patch:
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 9b0a8d1..488587b 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -47,7 +47,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 78a2a82..720c70b 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -48,7 +48,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [ubuntu-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
diff --git a/.github/workflows/lint-action/action.yml b/.github/workflows/lint-action/action.yml
index a0322ae..0eb6f39 100644
--- a/.github/workflows/lint-action/action.yml
+++ b/.github/workflows/lint-action/action.yml
@@ -22,6 +22,6 @@ runs:
       uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0
       with:
         # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
-        version: v1.51.1
+        version: v1.55.2
         # https://github.com/golangci/golangci-lint-action/issues/135
-        skip-pkg-cache: true
\ No newline at end of file
+        skip-pkg-cache: true
diff --git a/.golangci.yaml b/.golangci.yaml
index 8e21412..ad85722 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -30,7 +30,7 @@ linters:
     - testpackage      # will re-add later (another-rex)
     - goerr113         # will re-add later (another-rex)
     - nonamedreturns   # disagree with, for now (another-rex)
-    - depguard         # not necessary at the moment (another-rex)
+    - goconst          # not everything should be a constant
   presets:
     - bugs
     - comment
@@ -42,6 +42,16 @@ linters:
     - unused
 
 linters-settings:
+  depguard:
+    rules:
+      regexp:
+        files:
+          - '!**/internal/cachedregexp/**'
+          - '!**/main_test.go'
+        deny:
+          - pkg: 'regexp'
+            desc:
+              'Use github.com/google/osv-scanner/internal/cachedregexp instead'
   gocritic:
     disabled-checks:
       - ifElseChain
diff --git a/docs/configuration.md b/docs/configuration.md
index b4b0cc6..aece78f 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -2,7 +2,7 @@
 layout: page
 title: Configuration
 permalink: /configuration/
-nav_order: 4
+nav_order: 5
 ---
 # Configure OSV-Scanner
 
diff --git a/docs/contribute.md b/docs/contribute.md
index a12cb39..7ab7823 100644
--- a/docs/contribute.md
+++ b/docs/contribute.md
@@ -2,7 +2,7 @@
 layout: page
 title: Contribute
 permalink: /contribute/
-nav_order: 8
+nav_order: 9
 ---
 # Contribute
 
diff --git a/docs/experimental.md b/docs/experimental.md
index 0d1d801..bb2b77e 100644
--- a/docs/experimental.md
+++ b/docs/experimental.md
@@ -2,7 +2,7 @@
 layout: page
 title: Experimental Features
 permalink: /experimental/
-nav_order: 7
+nav_order: 8
 ---
 # Experimental Features
 
diff --git a/docs/github-action.md b/docs/github-action.md
index 7dee78e..601b71f 100644
--- a/docs/github-action.md
+++ b/docs/github-action.md
@@ -2,7 +2,7 @@
 layout: page
 title: GitHub Action
 permalink: /github-action/
-nav_order: 6
+nav_order: 7
 ---
 
 # GitHub Action
diff --git a/docs/index.md b/docs/index.md
index 5be8477..c6be03d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,4 +22,4 @@ OSV-Scanner provides an officially supported frontend to the [OSV database](http
 
 The above all results in fewer, more actionable vulnerability notifications, which reduces the time needed to resolve them. Check out our [announcement blog post] for more details!
 
-[announcement blog post]: https://security.googleblog.com/2022/12/announcing-osv-scanner-vulnerability.html
+[announcement blog post]: https://security.googleblog.com/2022/12/announcing-osv-scanner-vulnerability.html
\ No newline at end of file
diff --git a/docs/installation.md b/docs/installation.md
index e7489b1..2f37752 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -2,7 +2,7 @@
 layout: page
 title: Installation
 permalink: /installation/
-nav_order: 2
+nav_order: 3
 ---
 
 # Installation
diff --git a/docs/output.md b/docs/output.md
index 9c56955..d1c8deb 100644
--- a/docs/output.md
+++ b/docs/output.md
@@ -2,7 +2,7 @@
 layout: page
 title: Output
 permalink: /output/
-nav_order: 5
+nav_order: 6
 ---
 # Output
 {: .no_toc }
diff --git a/docs/supported_languages_and_lockfiles.md b/docs/supported_languages_and_lockfiles.md
new file mode 100644
index 0000000..cdc6734
--- /dev/null
+++ b/docs/supported_languages_and_lockfiles.md
@@ -0,0 +1,105 @@
+---
+layout: page
+title: Supported Languages and Lockfiles
+permalink: /supported-languages-and-lockfiles/
+nav_order: 2
+---
+
+# Supported Languages and Lockfiles
+
+{: .no_toc }
+
+<details open markdown="block">
+  <summary>
+    Table of contents
+  </summary>
+  {: .text-delta }
+- TOC
+{:toc}
+</details>
+
+## Supported lockfiles
+
+A wide range of lockfiles are supported by utilizing this [lockfile package](https://github.com/google/osv-scanner/tree/main/pkg/lockfile).
+
+|  Language  | Compatible Lockfile(s) |
+|:-----------|:------------------|
+| C/C++ | `conan.lock`<br>[C/C++ commit scanning](#cc-scanning)|
+| Dart | `pubspec.lock`|
+| Elixir | `mix.lock` |
+| Go | `go.mod` |
+| Java | `buildscript-gradle.lockfile`<br>`gradle.lockfile`<br>`pom.xml`[\*](https://github.com/google/osv-scanner/issues/35) |
+| 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) |
+| Ruby       | `Gemfile.lock`|
+| Rust       | `Cargo.lock`|
+
+## Alpine Package Keeper and Debian Package Keeper
+
+The scanner also supports:
+- `installed` files used by the Alpine Package Keeper (apk) that typically live at `/lib/apk/db/installed`
+- `status` files used by the Debian Package manager (dpkg) that typically live at `/var/lib/dpkg/status`
+
+however you must [specify](./usage.md/#specify-lockfiles) them explicitly using the `--lockfile` flag:
+
+```bash
+osv-scanner --lockfile 'apk-installed:/lib/apk/db/installed'
+osv-scanner --lockfile 'dpkg-status:/var/lib/dpkg/status'
+```
+
+## C/C++ scanning
+
+With the addition of [vulnerable commit ranges](https://osv.dev/blog/posts/introducing-broad-c-c++-support/) to the OSV.dev database, OSV-Scanner now supports vendored and submoduled C/C++ dependencies 
+
+Because the C/C++ ecosystem does not have a centralized package manager, C/C++ dependencies tend to be bundled with the project. Dependencies are either [submoduled](#submoduled-dependencies) or [vendored](#vendored-dependencies). In either case, OSV-Scanner is able to find known vulnerabilities in your project dependencies. 
+
+OSV-Scanner's C/C++ support is based on commit-level data. OSV's commit-level data covers the majority of C/C++ vulnerabilities within the OSV database, but users should be aware that there may be vulnerabilities in their dependencies that could be excluded from OSV-Scanner results. Adding more commit-level data to the database is an ongoing project. 
+
+### Submoduled dependencies
+
+Submoduled dependencies are included in the project folder retain their Git histories. To scan a C/C++ project with submoduled dependencies:
+
+1. Navigate to the root folder of your project. 
+2. Ensure that your submodules are up to date using `git submodule update`. 
+3. Run scanner using `osv-scanner -r .`. 
+
+### 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. 
+
+## Custom Lockfiles
+
+If you have a custom lockfile that we do not support or prefer to do your own custom parsing, you can extract the custom lockfile information and create a custom intermediate file containing dependency information so that osv-scanner can still check for vulnerabilities.
+
+Once you extracted your own dependency information, place it in a `osv-scanner.json` file, with the same format as the JSON output of osv-scanner, e.g.:
+
+```
+{
+  "results": [
+    {
+      "packages": [
+        {
+          "package": {
+            "name": "github.com/repo/url",
+            "commit": "9a6bd55c9d0722cb101fe85a3b22d89e4ff4fe52"
+          }
+        },
+        {
+          "package": {
+            "name": "react",
+            "version": "1.2.3",
+            "ecosystem": "npm"
+          }
+        },
+        // ...
+      ]
+    }
+  ]
+}
+```
+
+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 e907cc2..db8619e 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -2,7 +2,7 @@
 layout: page
 title: Usage
 permalink: /usage/
-nav_order: 3
+nav_order: 4
 ---
 # Usage
 
@@ -69,36 +69,7 @@ It is possible to specify more than one lockfile at a time; you can also specify
 osv-scanner --lockfile 'requirements.txt:/path/to/your/extra-requirements.txt'
 ```
 
-A wide range of lockfiles are supported by utilizing this [lockfile package](https://github.com/google/osv-scanner/tree/main/pkg/lockfile). This is the current list of supported lockfiles:
-
-- `buildscript-gradle.lockfile`
-- `Cargo.lock`
-- `composer.lock`
-- `conan.lock`
-- `Gemfile.lock`
-- `go.mod`
-- `gradle.lockfile`
-- `mix.lock`
-- `package-lock.json`
-- `packages.lock.json`
-- `Pipfile.lock`
-- `pnpm-lock.yaml`
-- `poetry.lock`
-- `pom.xml`[\*](https://github.com/google/osv-scanner/issues/35)
-- `pubspec.lock`
-- `requirements.txt`[\*](https://github.com/google/osv-scanner/issues/34)
-- `yarn.lock`
-
-The scanner also supports:
-- `installed` files used by the Alpine Package Keeper (apk) that typically live at `/lib/apk/db/installed`
-- `status` files used by the Debian Package manager (dpkg) that typically live at `/var/lib/dpkg/status`
-
-however you must specify them explicitly using the `--lockfile` flag:
-
-```bash
-osv-scanner --lockfile 'apk-installed:/lib/apk/db/installed'
-osv-scanner --lockfile 'dpkg-status:/var/lib/dpkg/status'
-```
+The list of supported lockfile formats can be found [here](/osv-scanner/supported-languages-and-lockfiles/).
 
 If the file you are scanning is located in a directory that has a colon in its name,
 you can prefix the path to just a colon to explicitly signal to the scanner that
@@ -108,42 +79,6 @@ it should infer the parser based on the filename:
 osv-scanner --lockfile ':/path/to/my:projects/package-lock.json'
 ```
 
-### Custom Lockfiles
-
-If you have a custom lockfile that we do not support or prefer to do your own custom parsing, you can extract the custom lockfile information and create a custom intermediate file containing dependency information so that osv-scanner can still check for vulnerabilities.
-
-Once you extracted your own dependency information, place it in a `osv-scanner.json` file, with the same format as the JSON output of osv-scanner, e.g.:
-
-```
-{
-  "results": [
-    {
-      "packages": [
-        {
-          "package": {
-            "name": "github.com/repo/url",
-            "commit": "9a6bd55c9d0722cb101fe85a3b22d89e4ff4fe52"
-          }
-        },
-        {
-          "package": {
-            "name": "react",
-            "version": "1.2.3",
-            "ecosystem": "npm"
-          }
-        },
-        // ...
-      ]
-    }
-  ]
-}
-```
-
-Then pass this to `osv-scanner` with this:
-```
-osv-scanner --lockfile osv-scanner:/path/to/osv-scanner.json
-```
-
 ## Scanning a Debian based docker image packages
 Preview
 {: .label }
@@ -190,3 +125,23 @@ The `--output` flag can be used to save the scan results to a file instead of be
 ```bash
 osv-scanner -L package-lock.json --output scan-results.txt
 ```
+
+## C/C++ scanning
+
+OSV-Scanner supports C/C++ projects. 
+
+Because the C/C++ ecosystem does not have a centralized package manager, C/C++ dependencies tend to be bundled with the project's source code. Dependencies are either [submoduled](#submoduled-dependencies) or [vendored](#vendored-dependencies). In either case, OSV-Scanner is able to find known vulnerabilities in your project dependencies. 
+
+OSV-Scanner's C/C++ support is based on commit-level data. OSV's commit-level data covers the majority of C/C++ vulnerabilities within the OSV database, but users should be aware that there may be vulnerabilities in their dependencies that may not be in the OSV database and therefore not included in OSV-Scanner results. Adding more commit-level data to the database is an ongoing project, follow [#783](https://github.com/google/osv.dev/issues/783) for more details. 
+
+### Submoduled dependencies
+
+[Submoduled](https://git-scm.com/book/en/v2/Git-Tools-Submodules) dependencies are included in the project's source code and retain their Git histories. To scan a C/C++ project with submoduled dependencies:
+
+1. Navigate to the root folder of your project. 
+2. Ensure that your submodules are up to date using `git submodule update`. 
+3. Run scanner using `osv-scanner -r .`. 
+
+### 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. 
diff --git a/go.mod b/go.mod
index a4b435d..b47c9dd 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/google/osv-scanner
 go 1.20
 
 require (
-	deps.dev/api/v3alpha v0.0.0-20231029115524-a2ccd033c6d4
+	deps.dev/api/v3alpha v0.0.0-20231114023923-e40c4d5c34e5
 	github.com/BurntSushi/toml v1.3.2
 	github.com/CycloneDX/cyclonedx-go v0.7.2
 	github.com/go-git/go-billy/v5 v5.5.0
@@ -17,10 +17,10 @@ require (
 	github.com/package-url/packageurl-go v0.1.2
 	github.com/spdx/tools-golang v0.5.3
 	github.com/urfave/cli/v2 v2.25.7
-	golang.org/x/exp v0.0.0-20231006140011-7918f672742d
+	golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
 	golang.org/x/mod v0.14.0
 	golang.org/x/sync v0.5.0
-	golang.org/x/term v0.13.0
+	golang.org/x/term v0.14.0
 	golang.org/x/vuln v1.0.1
 	google.golang.org/grpc v1.59.0
 	gopkg.in/yaml.v3 v3.0.1
@@ -52,11 +52,11 @@ require (
 	github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89 // indirect
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
-	golang.org/x/crypto v0.14.0 // indirect
-	golang.org/x/net v0.17.0 // indirect
-	golang.org/x/sys v0.13.0 // indirect
-	golang.org/x/text v0.13.0 // indirect
-	golang.org/x/tools v0.14.0 // indirect
+	golang.org/x/crypto v0.15.0 // indirect
+	golang.org/x/net v0.18.0 // indirect
+	golang.org/x/sys v0.14.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/tools v0.15.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
diff --git a/go.sum b/go.sum
index d8c764e..fe75793 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
 dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
 dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-deps.dev/api/v3alpha v0.0.0-20231029115524-a2ccd033c6d4 h1:y/JsYdh5JCgcI1WtxfTYrhj48YK8DsZUHFVVtZT4Qwc=
-deps.dev/api/v3alpha v0.0.0-20231029115524-a2ccd033c6d4/go.mod h1:uRN72FJn1F0FD/2ZYUOqdyFMu8VUsyHxvmZAMW30/DA=
+deps.dev/api/v3alpha v0.0.0-20231114023923-e40c4d5c34e5 h1:Vvh14FIzt0+LaLWn2l09FKQbdXBVxIjtPbjFiykXYlI=
+deps.dev/api/v3alpha v0.0.0-20231114023923-e40c4d5c34e5/go.mod h1:uRN72FJn1F0FD/2ZYUOqdyFMu8VUsyHxvmZAMW30/DA=
 github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/CycloneDX/cyclonedx-go v0.7.2 h1:kKQ0t1dPOlugSIYVOMiMtFqeXI2wp/f5DBIdfux8gnQ=
@@ -136,10 +136,10 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
@@ -153,8 +153,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
+golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -174,15 +174,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
+golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -192,14 +192,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
-golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
+golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
 golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU=
 golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/output/fixtures/sarif-commit-output_windows.md b/internal/output/fixtures/sarif-commit-output_windows.md
new file mode 100644
index 0000000..42f5da8
--- /dev/null
+++ b/internal/output/fixtures/sarif-commit-output_windows.md
@@ -0,0 +1,52 @@
+**Your dependency is vulnerable to [OSV-2023-72](https://osv.dev/list?q=OSV-2023-72)**.
+
+## [OSV-2023-72](https://osv.dev/vulnerability/OSV-2023-72)
+
+<details>
+<summary>Details</summary>
+
+> OSS-Fuzz report: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=56057
+> 
+> ```
+> Crash type: Heap-buffer-overflow WRITE 4
+> Crash state:
+> perfetto::trace_processor::TrackEventParser::ParseTrackDescriptor
+> perfetto::trace_processor::TrackEventModule::ParseTracePacketData
+> perfetto::trace_processor::ProtoTraceParser::ParseTracePacket
+> ```
+> 
+
+</details>
+
+---
+
+### Affected Packages
+
+| Source | Package Name | Package Version |
+| --- | --- | --- |
+| lockfile:/usr/local/google/home/rexpan/Documents/Project/engine/deps_flatten.txt | https://fuchsia.googlesource.com/third_party/android.googlesource.com/platform/external/perfetto | b8da07095979310818f0efde2ef3c69ea70d62c5 |
+
+## Remediation
+
+To fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.
+
+### Fixed Versions
+
+| Vulnerability ID | Package Name | Fixed Version |
+| --- | --- | --- |
+| OSV-2023-72 | perfetto | 9a7f09383dd39f19e662d428321ca708a2a600a3 |
+
+If you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an
+`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.
+
+See the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/
+
+Add or append these values to the following config files to ignore this vulnerability:
+
+`\usr\local\google\home\rexpan\Documents\Project\engine/osv-scanner.toml`
+
+```
+[[IgnoredVulns]]
+id = "OSV-2023-72"
+reason = "Your reason for ignoring this vulnerability"
+```
diff --git a/internal/output/fixtures/sarif-output_windows.md b/internal/output/fixtures/sarif-output_windows.md
new file mode 100644
index 0000000..858a777
--- /dev/null
+++ b/internal/output/fixtures/sarif-output_windows.md
@@ -0,0 +1,130 @@
+**Your dependency is vulnerable to [CVE-2022-24713](https://osv.dev/list?q=CVE-2022-24713)**
+(Also published as: [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013), [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8), ).
+
+## [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013)
+
+<details>
+<summary>Details</summary>
+
+> The Rust Security Response WG was notified that the `regex` crate did not
+> properly limit the complexity of the regular expressions (regex) it parses. An
+> attacker could use this security issue to perform a denial of service, by
+> sending a specially crafted regex to a service accepting untrusted regexes. No
+> known vulnerability is present when parsing untrusted input with trusted
+> regexes.
+> 
+> This issue has been assigned CVE-2022-24713. The severity of this vulnerability
+> is "high" when the `regex` crate is used to parse untrusted regexes. Other uses
+> of the `regex` crate are not affected by this vulnerability.
+> 
+> ## Overview
+> 
+> The `regex` crate features built-in mitigations to prevent denial of service
+> attacks caused by untrusted regexes, or untrusted input matched by trusted
+> regexes. Those (tunable) mitigations already provide sane defaults to prevent
+> attacks. This guarantee is documented and it's considered part of the crate's
+> API.
+> 
+> Unfortunately a bug was discovered in the mitigations designed to prevent
+> untrusted regexes to take an arbitrary amount of time during parsing, and it's
+> possible to craft regexes that bypass such mitigations. This makes it possible
+> to perform denial of service attacks by sending specially crafted regexes to
+> services accepting user-controlled, untrusted regexes.
+> 
+> ## Affected versions
+> 
+> All versions of the `regex` crate before or equal to 1.5.4 are affected by this
+> issue. The fix is include starting from  `regex` 1.5.5.
+> 
+> ## Mitigations
+> 
+> We recommend everyone accepting user-controlled regexes to upgrade immediately
+> to the latest version of the `regex` crate.
+> 
+> Unfortunately there is no fixed set of problematic regexes, as there are
+> practically infinite regexes that could be crafted to exploit this
+> vulnerability. Because of this, we do not recommend denying known problematic
+> regexes.
+> 
+> ## Acknowledgements
+> 
+> We want to thank Addison Crump for responsibly disclosing this to us according
+> to the [Rust security policy][1], and for helping review the fix.
+> 
+> We also want to thank Andrew Gallant for developing the fix, and Pietro Albini
+> for coordinating the disclosure and writing this advisory.
+> 
+> [1]: https://www.rust-lang.org/policies/security
+
+</details>
+
+## [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8)
+
+<details>
+<summary>Details</summary>
+
+> > This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.
+> 
+> [advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw
+> 
+> The Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.
+> 
+> This issue has been assigned CVE-2022-24713. The severity of this vulnerability is "high" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.
+> 
+> ## Overview
+> 
+> The `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.
+> 
+> Unfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.
+> 
+> ## Affected versions
+> 
+> All versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.
+> 
+> ## Mitigations
+> 
+> We recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.
+> 
+> Unfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.
+> 
+> ## Acknowledgements
+> 
+> We want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.
+> 
+> We also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.
+
+</details>
+
+---
+
+### Affected Packages
+
+| Source | Package Name | Package Version |
+| --- | --- | --- |
+| lockfile:/path/to/sub-rust-project/Cargo.lock | regex | 1.5.1 |
+
+## Remediation
+
+To fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.
+
+### Fixed Versions
+
+| Vulnerability ID | Package Name | Fixed Version |
+| --- | --- | --- |
+| GHSA-m5pq-gvj9-9vr8 | regex | 1.5.5 |
+| RUSTSEC-2022-0013 | regex | 1.5.5 |
+
+If you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an
+`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.
+
+See the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/
+
+Add or append these values to the following config files to ignore this vulnerability:
+
+`\path\to\sub-rust-project/osv-scanner.toml`
+
+```
+[[IgnoredVulns]]
+id = "CVE-2022-24713"
+reason = "Your reason for ignoring this vulnerability"
+```
diff --git a/internal/sbom/sbom.go b/internal/sbom/sbom.go
index a35a3d6..f6b5b81 100644
--- a/internal/sbom/sbom.go
+++ b/internal/sbom/sbom.go
@@ -11,16 +11,16 @@ type Identifier struct {
 	PURL string
 }
 
-// SBOMReader is an interface for all SBOM providers.
-type SBOMReader interface {
+// Reader is an interface for all SBOM providers.
+type Reader interface {
 	Name() string
-	// Checks if the file path is a standard recognized file name
-	MatchesRecognizedFileNames(string) bool
-	GetPackages(io.ReadSeeker, func(Identifier) error) error
+	// MatchesRecognizedFileNames checks if the file path is a standard recognized file name
+	MatchesRecognizedFileNames(path string) bool
+	GetPackages(r io.ReadSeeker, callback func(Identifier) error) error
 }
 
 var (
-	Providers = []SBOMReader{
+	Providers = []Reader{
 		&SPDX{},
 		&CycloneDX{},
 	}
diff --git a/internal/semantic/fixtures/cran-versions-generated.txt b/internal/semantic/fixtures/cran-versions-generated.txt
new file mode 100644
index 0000000..5ec1f92
--- /dev/null
+++ b/internal/semantic/fixtures/cran-versions-generated.txt
@@ -0,0 +1,57 @@
+0.1.0 < 0.1.1
+0.1.1 < 1.0.0
+1.0.0 < 1.1.0
+1.1.0 < 1.2.0
+1.2.0 < 1.4.1
+0.9.12 < 0.9.13
+0.9.13 < 0.9.14
+0.9.14 < 0.9.15
+0.9.15 < 0.9.16
+0.9.16 < 0.9.17
+0.9.17 < 0.9.18
+0.9.18 < 0.9.19
+0.9.19 < 0.9.20
+0.9.20 < 0.9.21
+0.9.21 < 0.9.22
+0.9.22 < 1.0
+1.0 < 1.1
+1.1 < 1.2
+1.2 < 1.3
+1.3 < 1.4
+1.4 < 1.5
+1.5 < 1.6
+1.6 < 1.6.1
+1.6.1 < 1.7.0
+1.7.0 < 1.7.1
+1.7.1 < 1.7.2
+1.7.2 < 1.7.3
+1.7.3 < 1.8.0
+1.8.0 < 1.8.1
+1.8.1 < 1.8.2
+1.8.2 < 1.8.3
+1.8.3 < 1.8.4
+1.8.4 < 1.8.5
+1.8.5 < 1.8.6
+1.8.6 < 1.8.7
+0.7.1 < 1.0.0
+1.0.0 < 1.0.1
+1.0.1 < 1.1.1
+1.1.1 < 1.1.2
+1.1.2 < 1.2.1
+1.2.1 < 1.2.2
+0.1.1 < 0.2.0
+0.2.0 < 0.2.1
+0.2.1 < 1.0.0
+0.2 < 0.4
+0.4 < 0.5
+0.5 < 0.6
+0.6 < 0.7
+0.7 < 0.8
+0.8 < 0.9
+0.9 < 1.0
+1.2 < 1.4
+1.6 < 1.7
+1.7 < 1.8
+1.8 < 1.8.0
+1.8.1 < 1.9
+1.9 < 1.9.0
diff --git a/internal/semantic/fixtures/cran-versions.txt b/internal/semantic/fixtures/cran-versions.txt
new file mode 100644
index 0000000..1a8d08b
--- /dev/null
+++ b/internal/semantic/fixtures/cran-versions.txt
@@ -0,0 +1,34 @@
+0.01 < 0.1-0
+0.01.0 = 0.1-0
+0.9 < 0.75
+
+1.0-0 < 1.1-0
+
+# ---
+0.1.0.0 < 0.2.0
+0.1.0 < 0.2.0.0
+0.1.0 < 0.1.1.0
+0.1.0 < 0.1.0.0
+0.1.0 > 0.0.1.0
+
+# https://cran.r-project.org/src/contrib/Archive/abctools/
+0.1-2 < 0.2-2
+0.2 < 0.3-2
+1.0 < 1.0.1
+1.0.2 < 1.0.3
+
+# https://cran.r-project.org/src/contrib/Archive/AntibodyTiters/
+0.1.4 < 0.1.18
+
+# https://cran.r-project.org/src/contrib/Archive/AdaptGauss/
+1.0 < 1.1.0
+1.5 < 1.5.4
+
+# https://cran.r-project.org/src/contrib/Archive/AcceptanceSampling/
+0.1-1 < 0.1-4
+1.0-0 < 1.0-1
+
+# https://cran.r-project.org/src/contrib/Archive/DHARMa/
+0.1.0 < 0.1.0.0
+0.3.2.0 > 0.3.2
+0.3.2.0 < 0.3.3.0
diff --git a/internal/semantic/parse.go b/internal/semantic/parse.go
index 78f2f83..e19ec60 100644
--- a/internal/semantic/parse.go
+++ b/internal/semantic/parse.go
@@ -44,6 +44,8 @@ func Parse(str string, ecosystem Ecosystem) (Version, error) {
 		return parseSemverVersion(str), nil
 	case "ConanCenter":
 		return parseSemverVersion(str), nil
+	case "CRAN":
+		return parseCRANVersion(str), nil
 	}
 
 	return nil, fmt.Errorf("%w %s", ErrUnsupportedEcosystem, ecosystem)
diff --git a/internal/semantic/version-cran.go b/internal/semantic/version-cran.go
new file mode 100644
index 0000000..19fa37c
--- /dev/null
+++ b/internal/semantic/version-cran.go
@@ -0,0 +1,54 @@
+package semantic
+
+import (
+	"math/big"
+	"strings"
+)
+
+// CRANVersion is the representation of a version of a package that is held
+// in the CRAN ecosystem (https://cran.r-project.org/).
+//
+// A version is a sequence of at least two non-negative integers separated by
+// either a period or a dash.
+//
+// See https://astrostatistics.psu.edu/su07/R/html/base/html/package_version.html
+type CRANVersion struct {
+	components Components
+}
+
+func (v CRANVersion) Compare(w CRANVersion) int {
+	if diff := v.components.Cmp(w.components); diff != 0 {
+		return diff
+	}
+
+	// versions are only equal if they also have the same number of components,
+	// otherwise the longer one is considered greater
+	if len(v.components) == len(w.components) {
+		return 0
+	}
+
+	if len(v.components) > len(w.components) {
+		return 1
+	}
+
+	return -1
+}
+
+func (v CRANVersion) CompareStr(str string) int {
+	return v.Compare(parseCRANVersion(str))
+}
+
+func parseCRANVersion(str string) CRANVersion {
+	// dashes and periods have the same weight, so we can just normalize to periods
+	parts := strings.Split(strings.ReplaceAll(str, "-", "."), ".")
+
+	components := make(Components, 0, len(parts))
+
+	for _, s := range parts {
+		v, _ := new(big.Int).SetString(s, 10)
+
+		components = append(components, v)
+	}
+
+	return CRANVersion{components}
+}
diff --git a/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/lockfile/apk-installed.go b/pkg/lockfile/apk-installed.go
index 92a7ae6..413eba7 100644
--- a/pkg/lockfile/apk-installed.go
+++ b/pkg/lockfile/apk-installed.go
@@ -3,7 +3,6 @@ package lockfile
 import (
 	"bufio"
 	"fmt"
-	"os"
 	"sort"
 	"strings"
 )
@@ -34,7 +33,7 @@ func groupApkPackageLines(scanner *bufio.Scanner) [][]string {
 	return groups
 }
 
-func parseApkPackageGroup(group []string, pathToLockfile string) PackageDetails {
+func parseApkPackageGroup(group []string) PackageDetails {
 	var pkg = PackageDetails{
 		Ecosystem: AlpineEcosystem,
 		CompareAs: AlpineEcosystem,
@@ -52,20 +51,6 @@ func parseApkPackageGroup(group []string, pathToLockfile string) PackageDetails
 		}
 	}
 
-	if pkg.Version == "" {
-		pkgPrintName := pkg.Name
-		if pkgPrintName == "" {
-			pkgPrintName = unknownPkgName
-		}
-
-		_, _ = fmt.Fprintf(
-			os.Stderr,
-			"warning: malformed APK installed file. Found no version number in record. Package %s. File: %s\n",
-			pkgPrintName,
-			pathToLockfile,
-		)
-	}
-
 	return pkg
 }
 
@@ -87,15 +72,9 @@ func (e ApkInstalledExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 	packages := make([]PackageDetails, 0, len(packageGroups))
 
 	for _, group := range packageGroups {
-		pkg := parseApkPackageGroup(group, f.Path())
+		pkg := parseApkPackageGroup(group)
 
 		if pkg.Name == "" {
-			_, _ = fmt.Fprintf(
-				os.Stderr,
-				"warning: malformed APK installed file. Found no package name in record. File: %s\n",
-				f.Path(),
-			)
-
 			continue
 		}
 
diff --git a/pkg/lockfile/dpkg-status.go b/pkg/lockfile/dpkg-status.go
index 6afadd2..404ee1b 100644
--- a/pkg/lockfile/dpkg-status.go
+++ b/pkg/lockfile/dpkg-status.go
@@ -3,7 +3,6 @@ package lockfile
 import (
 	"bufio"
 	"fmt"
-	"os"
 	"sort"
 	"strings"
 
@@ -48,7 +47,7 @@ func parseSourceField(source string) (string, string) {
 	return strings.TrimSpace(source), ""
 }
 
-func parseDpkgPackageGroup(group []string, pathToLockfile string) PackageDetails {
+func parseDpkgPackageGroup(group []string) PackageDetails {
 	var pkg = PackageDetails{
 		Ecosystem: DebianEcosystem,
 		CompareAs: DebianEcosystem,
@@ -62,14 +61,8 @@ func parseDpkgPackageGroup(group []string, pathToLockfile string) PackageDetails
 		case strings.HasPrefix(line, "Status:"):
 			status := strings.TrimPrefix(line, "Status:")
 			tokens := strings.Fields(status)
-			// Staus field is malformed. Expected: "Status: Want Flag Status"
+			// Status field is malformed. Expected: "Status: Want Flag Status"
 			if len(tokens) != 3 {
-				_, _ = fmt.Fprintf(
-					os.Stderr,
-					"warning: malformed DPKG status file. Found no valid \"Source\" field. File: %s\n",
-					pathToLockfile,
-				)
-
 				return PackageDetails{}
 			}
 			// Status field has correct number of fields but package is not installed or has only config files left
@@ -105,20 +98,6 @@ func parseDpkgPackageGroup(group []string, pathToLockfile string) PackageDetails
 		}
 	}
 
-	if pkg.Version == "" {
-		pkgPrintName := pkg.Name
-		if pkgPrintName == "" {
-			pkgPrintName = unknownPkgName
-		}
-
-		_, _ = fmt.Fprintf(
-			os.Stderr,
-			"warning: malformed DPKG status file. Found no version number in record. Package %s. File: %s\n",
-			pkgPrintName,
-			pathToLockfile,
-		)
-	}
-
 	return pkg
 }
 
@@ -139,7 +118,7 @@ func (e DpkgStatusExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 	packages := make([]PackageDetails, 0, len(packageGroups))
 
 	for _, group := range packageGroups {
-		pkg := parseDpkgPackageGroup(group, f.Path())
+		pkg := parseDpkgPackageGroup(group)
 
 		// PackageDetails does not contain any field that represent a "not installed" state
 		// To manage this state and avoid false positives, empty struct means "not installed" so skip it
@@ -148,12 +127,6 @@ func (e DpkgStatusExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 		}
 
 		if pkg.Name == "" {
-			_, _ = fmt.Fprintf(
-				os.Stderr,
-				"warning: malformed DPKG status file. Found no package name in record. File: %s\n",
-				f.Path(),
-			)
-
 			continue
 		}
 
diff --git a/pkg/lockfile/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/parse-gradle-lock.go b/pkg/lockfile/parse-gradle-lock.go
index 2bfa13f..5413768 100644
--- a/pkg/lockfile/parse-gradle-lock.go
+++ b/pkg/lockfile/parse-gradle-lock.go
@@ -3,7 +3,6 @@ package lockfile
 import (
 	"bufio"
 	"fmt"
-	"os"
 	"path/filepath"
 	"strings"
 )
@@ -63,7 +62,6 @@ func (e GradleLockExtractor) Extract(f DepFile) ([]PackageDetails, error) {
 
 		pkg, err := parseToGradlePackageDetail(lockLine)
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "failed to extract from lockline: %s\n", err.Error())
 			continue
 		}
 
diff --git a/pkg/lockfile/parse.go b/pkg/lockfile/parse.go
index b10a72f..9e1a80a 100644
--- a/pkg/lockfile/parse.go
+++ b/pkg/lockfile/parse.go
@@ -8,11 +8,6 @@ import (
 	"strings"
 )
 
-// Represents when a package name could not be determined while parsing.
-// Currently, parsers are expected to omit such packages from their results.
-// Using a const is required to avoid linter error (goconst) due to multiple usage in parsers.
-const unknownPkgName = "<unknown>"
-
 func FindParser(pathToLockfile string, parseAs string) (PackageDetailsParser, string) {
 	if parseAs == "" {
 		parseAs = filepath.Base(pathToLockfile)
diff --git a/pkg/models/vulnerability.go b/pkg/models/vulnerability.go
index db7a0e1..1f87b5f 100644
--- a/pkg/models/vulnerability.go
+++ b/pkg/models/vulnerability.go
@@ -10,8 +10,8 @@ import (
 //
 // See: https://ossf.github.io/osv-schema/#affectedpackage-field
 type Package struct {
-	Ecosystem Ecosystem `json:"ecosystem" yaml:"ecosystem"`
-	Name      string    `json:"name" yaml:"name"`
+	Ecosystem Ecosystem `json:"ecosystem"      yaml:"ecosystem"`
+	Name      string    `json:"name"           yaml:"name"`
 	Purl      string    `json:"purl,omitempty" yaml:"purl,omitempty"`
 }
 
@@ -27,19 +27,19 @@ type Package struct {
 //
 // See: https://ossf.github.io/osv-schema/#affectedrangesevents-fields
 type Event struct {
-	Introduced   string `json:"introduced,omitempty" yaml:"introduced,omitempty"`
-	Fixed        string `json:"fixed,omitempty" yaml:"fixed,omitempty"`
+	Introduced   string `json:"introduced,omitempty"    yaml:"introduced,omitempty"`
+	Fixed        string `json:"fixed,omitempty"         yaml:"fixed,omitempty"`
 	LastAffected string `json:"last_affected,omitempty" yaml:"last_affected,omitempty"`
-	Limit        string `json:"limit,omitempty" yaml:"limit,omitempty"`
+	Limit        string `json:"limit,omitempty"         yaml:"limit,omitempty"`
 }
 
 // Range describes the affected range of given version for a specific package.
 //
 // See: https://ossf.github.io/osv-schema/#affectedranges-field
 type Range struct {
-	Type             RangeType              `json:"type" yaml:"type"`
-	Events           []Event                `json:"events" yaml:"events"`
-	Repo             string                 `json:"repo,omitempty" yaml:"repo,omitempty"`
+	Type             RangeType              `json:"type"                        yaml:"type"`
+	Events           []Event                `json:"events"                      yaml:"events"`
+	Repo             string                 `json:"repo,omitempty"              yaml:"repo,omitempty"`
 	DatabaseSpecific map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"`
 }
 
@@ -48,7 +48,7 @@ type Range struct {
 //
 // See: https://ossf.github.io/osv-schema/#severity-field
 type Severity struct {
-	Type  SeverityType `json:"type" yaml:"type"`
+	Type  SeverityType `json:"type"  yaml:"type"`
 	Score string       `json:"score" yaml:"score"`
 }
 
@@ -57,11 +57,11 @@ type Severity struct {
 //
 // See: https://ossf.github.io/osv-schema/#affected-fields
 type Affected struct {
-	Package           Package                `json:"package,omitempty" yaml:"package,omitempty"`
-	Severity          []Severity             `json:"severity,omitempty" yaml:"severity,omitempty"`
-	Ranges            []Range                `json:"ranges,omitempty" yaml:"ranges,omitempty"`
-	Versions          []string               `json:"versions,omitempty" yaml:"versions,omitempty"`
-	DatabaseSpecific  map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"`
+	Package           Package                `json:"package,omitempty"            yaml:"package,omitempty"`
+	Severity          []Severity             `json:"severity,omitempty"           yaml:"severity,omitempty"`
+	Ranges            []Range                `json:"ranges,omitempty"             yaml:"ranges,omitempty"`
+	Versions          []string               `json:"versions,omitempty"           yaml:"versions,omitempty"`
+	DatabaseSpecific  map[string]interface{} `json:"database_specific,omitempty"  yaml:"database_specific,omitempty"`
 	EcosystemSpecific map[string]interface{} `json:"ecosystem_specific,omitempty" yaml:"ecosystem_specific,omitempty"`
 }
 
@@ -92,7 +92,7 @@ func (a Affected) MarshalJSON() ([]byte, error) {
 // See: https://ossf.github.io/osv-schema/#references-field
 type Reference struct {
 	Type ReferenceType `json:"type" yaml:"type"`
-	URL  string        `json:"url" yaml:"url"`
+	URL  string        `json:"url"  yaml:"url"`
 }
 
 // Credit gives credit for the discovery, confirmation, patch, or other events
@@ -100,8 +100,8 @@ type Reference struct {
 //
 // See: https://ossf.github.io/osv-schema/#credits-fields
 type Credit struct {
-	Name    string     `json:"name" yaml:"name"`
-	Type    CreditType `json:"type,omitempty" yaml:"type,omitempty"`
+	Name    string     `json:"name"              yaml:"name"`
+	Type    CreditType `json:"type,omitempty"    yaml:"type,omitempty"`
 	Contact []string   `json:"contact,omitempty" yaml:"contact,omitempty"`
 }
 
@@ -110,19 +110,19 @@ type Credit struct {
 // The full documentation for the schema is available at
 // https://ossf.github.io/osv-schema.
 type Vulnerability struct {
-	SchemaVersion    string                 `json:"schema_version,omitempty" yaml:"schema_version,omitempty"`
-	ID               string                 `json:"id" yaml:"id"`
-	Modified         time.Time              `json:"modified" yaml:"modified"`
-	Published        time.Time              `json:"published,omitempty" yaml:"published,omitempty"`
-	Withdrawn        time.Time              `json:"withdrawn,omitempty" yaml:"withdrawn,omitempty"`
-	Aliases          []string               `json:"aliases,omitempty" yaml:"aliases,omitempty"`
-	Related          []string               `json:"related,omitempty" yaml:"related,omitempty"`
-	Summary          string                 `json:"summary,omitempty" yaml:"summary,omitempty"`
-	Details          string                 `json:"details,omitempty" yaml:"details,omitempty"`
-	Affected         []Affected             `json:"affected,omitempty" yaml:"affected,omitempty"`
-	Severity         []Severity             `json:"severity,omitempty" yaml:"severity,omitempty"`
-	References       []Reference            `json:"references,omitempty" yaml:"references,omitempty"`
-	Credits          []Credit               `json:"credits,omitempty" yaml:"credits,omitempty"`
+	SchemaVersion    string                 `json:"schema_version,omitempty"    yaml:"schema_version,omitempty"`
+	ID               string                 `json:"id"                          yaml:"id"`
+	Modified         time.Time              `json:"modified"                    yaml:"modified"`
+	Published        time.Time              `json:"published,omitempty"         yaml:"published,omitempty"`
+	Withdrawn        time.Time              `json:"withdrawn,omitempty"         yaml:"withdrawn,omitempty"`
+	Aliases          []string               `json:"aliases,omitempty"           yaml:"aliases,omitempty"`
+	Related          []string               `json:"related,omitempty"           yaml:"related,omitempty"`
+	Summary          string                 `json:"summary,omitempty"           yaml:"summary,omitempty"`
+	Details          string                 `json:"details,omitempty"           yaml:"details,omitempty"`
+	Affected         []Affected             `json:"affected,omitempty"          yaml:"affected,omitempty"`
+	Severity         []Severity             `json:"severity,omitempty"          yaml:"severity,omitempty"`
+	References       []Reference            `json:"references,omitempty"        yaml:"references,omitempty"`
+	Credits          []Credit               `json:"credits,omitempty"           yaml:"credits,omitempty"`
 	DatabaseSpecific map[string]interface{} `json:"database_specific,omitempty" yaml:"database_specific,omitempty"`
 }
 
diff --git a/pkg/osvscanner/osvscanner.go b/pkg/osvscanner/osvscanner.go
index bc334e7..38ed2f7 100644
--- a/pkg/osvscanner/osvscanner.go
+++ b/pkg/osvscanner/osvscanner.go
@@ -247,6 +247,10 @@ func queryDetermineVersions(repoDir string) (*osv.DetermineVersionResponse, erro
 				// results with our regular git commit scanning.
 				return filepath.SkipDir
 			}
+			if _, ok := vendoredLibNames[strings.ToLower(info.Name())]; ok {
+				// Ignore nested vendored libraries, as they can cause bad matches.
+				return filepath.SkipDir
+			}
 
 			return nil
 		}
diff --git a/pkg/osvscanner/vulnerability_result.go b/pkg/osvscanner/vulnerability_result.go
index 8eb0b48..cbdb104 100644
--- a/pkg/osvscanner/vulnerability_result.go
+++ b/pkg/osvscanner/vulnerability_result.go
@@ -3,6 +3,7 @@ package osvscanner
 import (
 	"fmt"
 	"sort"
+	"strings"
 
 	"github.com/google/osv-scanner/internal/sourceanalysis"
 	"github.com/google/osv-scanner/pkg/grouper"
@@ -66,10 +67,10 @@ func buildVulnerabilityResults(
 			pkg.Licenses = licensesResp[i]
 			allowlist := make(map[string]bool)
 			for _, license := range licensesAllowlist {
-				allowlist[license] = true
+				allowlist[strings.ToLower(license)] = true
 			}
 			for _, license := range pkg.Licenses {
-				if !allowlist[string(license)] {
+				if !allowlist[strings.ToLower(string(license))] {
 					pkg.LicenseViolations = append(pkg.LicenseViolations, license)
 				}
 			}
diff --git a/scripts/generators/generate-cran-versions.R b/scripts/generators/generate-cran-versions.R
new file mode 100755
index 0000000..10013dd
--- /dev/null
+++ b/scripts/generators/generate-cran-versions.R
@@ -0,0 +1,183 @@
+#!/usr/bin/env Rscript
+
+install.packages("jsonlite", repos = 'https://cran.r-project.org')
+
+library(utils)
+library(jsonlite)
+
+# An array of version comparisons that are known to be unsupported and so
+# should be commented out in the generated fixture.
+#
+# Generally this is because the native implementation has a suspected bug
+# that causes the comparison to return incorrect results, and so supporting
+# such comparisons in the detector would in fact be wrong.
+UNSUPPORTED_COMPARISONS <- c()
+
+download_cran_db <- function() {
+  url <- "https://osv-vulnerabilities.storage.googleapis.com/CRAN/all.zip"
+  dest <- "cran-db.zip"
+  download.file(url, dest, method = "auto")
+}
+
+extract_packages_with_versions <- function(osvs) {
+  result <- list()
+
+  for (osv in osvs) {
+    for (affected in osv$affected) {
+      package <- affected$package$name
+
+      if (!(package %in% names(result))) {
+        result[[package]] <- list()
+      }
+
+      for (version in affected$versions) {
+        tryCatch(
+          {
+            as.package_version(version)
+            result[[package]] <- c(result[[package]], version)
+          },
+          error = function(e) {
+            cat(sprintf("skipping invalid version %s for %s\n", version, package))
+          }
+        )
+      }
+    }
+  }
+
+  # deduplicate and sort the versions for each package
+  for (package in names(result)) {
+    result[[package]] <- sort(numeric_version(unique(result[[package]])))
+  }
+
+  return(result)
+}
+
+is_unsupported_comparison <- function(line) {
+  line %in% UNSUPPORTED_COMPARISONS
+}
+
+uncomment <- function(line) {
+  if (startsWith(line, "#")) {
+    return(substr(line, 2, nchar(line)))
+  }
+  if (startsWith(line, "//")) {
+    return(substr(line, 3, nchar(line)))
+  }
+  return(line)
+}
+
+compare <- function(v1, relate, v2) {
+  ops <- list('<' = function(result) result < 0,
+              '=' = function(result) result == 0,
+              '>' = function(result) result > 0)
+
+  return(ops[[relate]](compareVersion(v1, v2)))
+}
+
+compare_versions <- function(lines, select="all") {
+  has_any_failed <- FALSE
+
+  for (line in lines) {
+    line <- trimws(line)
+
+    if (line == "" || grepl("^#", line) || grepl("^//", line)) {
+      maybe_unsupported <- trimws(uncomment(line))
+
+      if (is_unsupported_comparison(maybe_unsupported)) {
+        cat(sprintf("\033[96mS\033[0m: \033[93m%s\033[0m\n", maybe_unsupported))
+      }
+      next
+    }
+
+    parts <- strsplit(trimws(line), " ")[[1]]
+    v1 <- parts[1]
+    op <- parts[2]
+    v2 <- parts[3]
+
+    r <- compare(v1, op, v2)
+
+    if (!r) {
+      has_any_failed <- TRUE
+    }
+
+    if (select == "failures" && r) {
+      next
+    }
+
+    if (select == "successes" && !r) {
+      next
+    }
+
+    color <- ifelse(r, '\033[92m', '\033[91m')
+    rs <- ifelse(r, "T", "F")
+    cat(sprintf("%s%s\033[0m: \033[93m%s\033[0m\n", color, rs, line))
+  }
+  return(has_any_failed)
+}
+
+compare_versions_in_file <- function(filepath, select="all") {
+  lines <- readLines(filepath)
+  return(compare_versions(lines, select))
+}
+
+generate_version_compares <- function(versions) {
+  comparisons <- character()
+
+  for (i in seq_along(versions)) {
+    if (i == 1) {
+      next
+    }
+
+    comparison <- sprintf("%s < %s", versions[i - 1], versions[i])
+
+    if (is_unsupported_comparison(trimws(comparison))) {
+      comparison <- paste("#", comparison)
+    }
+
+    comparisons <- c(comparisons, comparison)
+  }
+
+  return(comparisons)
+}
+
+generate_package_compares <- function(packages) {
+  comparisons <- character()
+
+  for (package in names(packages)) {
+    versions <- packages[[package]]
+    comparisons <- c(comparisons, generate_version_compares(versions))
+  }
+
+  # return unique comparisons
+  return(unique(comparisons))
+}
+
+fetch_packages_versions <- function() {
+  download_cran_db()
+  osvs <- list()
+
+  with_zip <- unzip("cran-db.zip", list = TRUE)
+
+  for (fname in with_zip$Name) {
+    osv <- jsonlite::fromJSON(unzip("cran-db.zip", files = fname, exdir = tempdir()), simplifyDataFrame = FALSE)
+    osvs <- c(osvs, list(osv))
+  }
+
+  return(extract_packages_with_versions(osvs))
+}
+
+outfile <- "internal/semantic/fixtures/cran-versions-generated.txt"
+
+packs <- fetch_packages_versions()
+writeLines(generate_package_compares(packs), outfile, sep = "\n")
+cat("\n")
+
+# set this to either "failures" or "successes" to only have those comparison results
+# printed; setting it to anything else will have all comparison results printed
+show <- Sys.getenv("VERSION_GENERATOR_PRINT", "failures")
+
+did_any_fail <- compare_versions_in_file(outfile, show)
+
+if (did_any_fail) {
+  q(status = 1)
+}
diff --git a/scripts/run_lints.sh b/scripts/run_lints.sh
index 42ee567..f23aa2a 100755
--- a/scripts/run_lints.sh
+++ b/scripts/run_lints.sh
@@ -2,4 +2,4 @@
 
 set -ex
 
-go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.1 run ./... --max-same-issues 0
+go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 run ./... --max-same-issues 0


Test patch:
diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go
index ff124d8..05469c1 100644
--- a/cmd/osv-scanner/main_test.go
+++ b/cmd/osv-scanner/main_test.go
@@ -7,11 +7,13 @@ import (
 	"os"
 	"path/filepath"
 	"regexp"
+	"strconv"
 	"strings"
 	"testing"
 
 	"github.com/go-git/go-git/v5"
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/osv-scanner/internal/testutility"
 	"github.com/google/osv-scanner/internal/version"
 )
 
@@ -58,7 +60,7 @@ func dedent(t *testing.T, str string) string {
 			}
 		}
 
-		re := regexp.MustCompile(`\n[\t ]{` + fmt.Sprint(size) + `}`)
+		re := regexp.MustCompile(`\n[\t ]{` + strconv.Itoa(size) + `}`)
 		str = re.ReplaceAllString(str, "\n")
 	}
 
@@ -105,6 +107,16 @@ func expectAreEqual(t *testing.T, subject, actual, expect string) {
 	}
 }
 
+// Attempts to normalize any file paths in the given `output` so that they can
+// be compared reliably regardless of the file path separator being used.
+//
+// Namely, escaped forward slashes are replaced with backslashes.
+func normalizeFilePaths(t *testing.T, output string) string {
+	t.Helper()
+
+	return strings.ReplaceAll(strings.ReplaceAll(output, "\\\\", "/"), "\\", "/")
+}
+
 // normalizeRootDirectory attempts to replace references to the current working
 // directory with "<rootdir>", in order to reduce the noise of the cmp diff
 func normalizeRootDirectory(t *testing.T, str string) string {
@@ -115,6 +127,11 @@ func normalizeRootDirectory(t *testing.T, str string) string {
 		t.Errorf("could not get cwd (%v) - results and diff might be inaccurate!", err)
 	}
 
+	cwd = normalizeFilePaths(t, cwd)
+
+	// file uris with Windows end up with three slashes, so we normalize that too
+	str = strings.ReplaceAll(str, "file:///"+cwd, "file://<rootdir>")
+
 	return strings.ReplaceAll(str, cwd, "<rootdir>")
 }
 
@@ -127,11 +144,11 @@ func testCli(t *testing.T, tc cliTestCase) {
 	ec := run(tc.args, stdoutBuffer, stderrBuffer)
 	// ec := run(tc.args, os.Stdout, os.Stderr)
 
-	stdout := stdoutBuffer.String()
-	stderr := stderrBuffer.String()
+	stdout := normalizeRootDirectory(t, normalizeFilePaths(t, stdoutBuffer.String()))
+	stderr := normalizeRootDirectory(t, normalizeFilePaths(t, stderrBuffer.String()))
 
-	stdout = normalizeRootDirectory(t, stdout)
-	stderr = normalizeRootDirectory(t, stderr)
+	tc.wantStdout = normalizeFilePaths(t, tc.wantStdout)
+	tc.wantStderr = normalizeFilePaths(t, tc.wantStderr)
 
 	if ec != tc.wantExitCode {
 		t.Errorf("cli exited with code %d, not %d", ec, tc.wantExitCode)
@@ -572,9 +589,10 @@ func TestRun_LockfileWithExplicitParseAs(t *testing.T) {
 			},
 			wantExitCode: 127,
 			wantStdout:   "",
-			wantStderr: `
-				open <rootdir>/path/to/my:file: no such file or directory
-			`,
+			wantStderr: testutility.ValueIfOnWindows(
+				"open <rootdir>/path/to/my:file: The system cannot find the path specified.",
+				"open <rootdir>/path/to/my:file: no such file or directory",
+			),
 		},
 		{
 			name: "",
@@ -585,9 +603,10 @@ func TestRun_LockfileWithExplicitParseAs(t *testing.T) {
 			},
 			wantExitCode: 127,
 			wantStdout:   "",
-			wantStderr: `
-				open <rootdir>/path/to/my:project/package-lock.json: no such file or directory
-			`,
+			wantStderr: testutility.ValueIfOnWindows(
+				"open <rootdir>/path/to/my:project/package-lock.json: The filename, directory name, or volume label syntax is incorrect.",
+				"open <rootdir>/path/to/my:project/package-lock.json: no such file or directory",
+			),
 		},
 		// one lockfile with local path
 		{
@@ -787,7 +806,8 @@ func TestRun_GithubActions(t *testing.T) {
                         "markdown": "An issue in Archive v3.3.7 allows attackers to spoof zip filenames which can lead to inconsistent filename parsing."
                       },
                       "deprecatedIds": [
-                        "CVE-2023-39137"
+                        "CVE-2023-39137",
+                        "GHSA-r285-q736-9v95"
                       ],
                       "help": {
                         "text": "%%",
@@ -805,7 +825,8 @@ func TestRun_GithubActions(t *testing.T) {
                         "markdown": "An issue in Archive v3.3.7 allows attackers to execute a path traversal via extracting a crafted zip file."
                       },
                       "deprecatedIds": [
-                        "CVE-2023-39139"
+                        "CVE-2023-39139",
+                        "GHSA-9v85-q87q-g4vg"
                       ],
                       "help": {
                         "text": "%%",
@@ -830,7 +851,7 @@ func TestRun_GithubActions(t *testing.T) {
                   "ruleIndex": 0,
                   "level": "warning",
                   "message": {
-                    "text": "Package 'https://github.com/brendan-duncan/archive.git@9de7a054' is vulnerable to 'CVE-2023-39137'."
+                    "text": "Package 'https://github.com/brendan-duncan/archive.git@9de7a054' is vulnerable to 'CVE-2023-39137' (also known as 'GHSA-r285-q736-9v95')."
                   },
                   "locations": [
                     {
@@ -847,7 +868,7 @@ func TestRun_GithubActions(t *testing.T) {
                   "ruleIndex": 1,
                   "level": "warning",
                   "message": {
-                    "text": "Package 'https://github.com/brendan-duncan/archive.git@9de7a054' is vulnerable to 'CVE-2023-39139'."
+                    "text": "Package 'https://github.com/brendan-duncan/archive.git@9de7a054' is vulnerable to 'CVE-2023-39139' (also known as 'GHSA-9v85-q87q-g4vg')."
                   },
                   "locations": [
                     {
diff --git a/internal/output/fixtures/test-vuln-results-a-grouped_windows.json b/internal/output/fixtures/test-vuln-results-a-grouped_windows.json
new file mode 100644
index 0000000..44b9fbe
--- /dev/null
+++ b/internal/output/fixtures/test-vuln-results-a-grouped_windows.json
@@ -0,0 +1,495 @@
+{
+  "GHSA-m5pq-gvj9-9vr8": {
+    "DisplayID": "CVE-2022-24713",
+    "PkgSource": [
+      {
+        "Package": {
+          "name": "regex",
+          "version": "1.5.1",
+          "ecosystem": "crates.io",
+          "commit": ""
+        },
+        "Source": {
+          "path": "D:\\path\\to\\sub-rust-project\\Cargo.lock",
+          "type": "lockfile"
+        }
+      }
+    ],
+    "AliasedVulns": {
+      "GHSA-m5pq-gvj9-9vr8": {
+        "modified": "2022-08-11T20:38:52Z",
+        "published": "2022-03-08T20:00:36Z",
+        "schema_version": "1.4.0",
+        "id": "GHSA-m5pq-gvj9-9vr8",
+        "aliases": [
+          "CVE-2022-24713"
+        ],
+        "summary": "Rust's regex crate vulnerable to regular expression denial of service",
+        "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json"
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713"
+          },
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e"
+          },
+          {
+            "type": "PACKAGE",
+            "url": "https://github.com/rust-lang/regex/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-08"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-14"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5113"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5118"
+          }
+        ],
+        "database_specific": {
+          "cwe_ids": [
+            "CWE-400"
+          ],
+          "github_reviewed": true,
+          "github_reviewed_at": "2022-03-08T20:00:36Z",
+          "nvd_published_at": "2022-03-08T19:15:00Z",
+          "severity": "HIGH"
+        }
+      },
+      "RUSTSEC-2022-0013": {
+        "modified": "2023-06-13T13:10:24Z",
+        "published": "2022-03-08T12:00:00Z",
+        "schema_version": "1.4.0",
+        "id": "RUSTSEC-2022-0013",
+        "aliases": [
+          "CVE-2022-24713",
+          "GHSA-m5pq-gvj9-9vr8"
+        ],
+        "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse",
+        "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0.0.0-0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "categories": [
+                "denial-of-service"
+              ],
+              "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+              "informational": null,
+              "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json"
+            },
+            "ecosystem_specific": {
+              "affects": {
+                "arch": [],
+                "functions": [],
+                "os": []
+              }
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "PACKAGE",
+            "url": "https://crates.io/crates/regex"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          }
+        ]
+      }
+    },
+    "AliasedIDList": [
+      "CVE-2022-24713",
+      "RUSTSEC-2022-0013",
+      "GHSA-m5pq-gvj9-9vr8"
+    ]
+  },
+  "GO-2021-0053": {
+    "DisplayID": "CVE-2021-3121",
+    "PkgSource": [
+      {
+        "Package": {
+          "name": "github.com/gogo/protobuf",
+          "version": "1.3.1",
+          "ecosystem": "Go",
+          "commit": ""
+        },
+        "Source": {
+          "path": "D:\\path\\to\\go.mod",
+          "type": "lockfile"
+        }
+      }
+    ],
+    "AliasedVulns": {
+      "GO-2021-0053": {
+        "modified": "2023-06-12T18:45:41Z",
+        "published": "2021-04-14T20:04:52Z",
+        "schema_version": "1.4.0",
+        "id": "GO-2021-0053",
+        "aliases": [
+          "CVE-2021-3121",
+          "GHSA-c3h9-896r-86jm"
+        ],
+        "summary": "Panic due to improper input validation in github.com/gogo/protobuf",
+        "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "Go",
+              "name": "github.com/gogo/protobuf",
+              "purl": "pkg:golang/github.com/gogo/protobuf"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0"
+                  },
+                  {
+                    "fixed": "1.3.2"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "source": "https://vuln.go.dev/ID/GO-2021-0053.json"
+            },
+            "ecosystem_specific": {
+              "imports": [
+                {
+                  "path": "github.com/gogo/protobuf/plugin/unmarshal",
+                  "symbols": [
+                    "unmarshal.Generate",
+                    "unmarshal.field"
+                  ]
+                }
+              ]
+            }
+          }
+        ],
+        "references": [
+          {
+            "type": "FIX",
+            "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc"
+          }
+        ],
+        "database_specific": {
+          "url": "https://pkg.go.dev/vuln/GO-2021-0053"
+        }
+      }
+    },
+    "AliasedIDList": [
+      "CVE-2021-3121",
+      "GO-2021-0053",
+      "GHSA-c3h9-896r-86jm"
+    ]
+  },
+  "RUSTSEC-2022-0013": {
+    "DisplayID": "CVE-2022-24713",
+    "PkgSource": [
+      {
+        "Package": {
+          "name": "regex",
+          "version": "1.5.1",
+          "ecosystem": "crates.io",
+          "commit": ""
+        },
+        "Source": {
+          "path": "D:\\path\\to\\sub-rust-project\\Cargo.lock",
+          "type": "lockfile"
+        }
+      }
+    ],
+    "AliasedVulns": {
+      "GHSA-m5pq-gvj9-9vr8": {
+        "modified": "2022-08-11T20:38:52Z",
+        "published": "2022-03-08T20:00:36Z",
+        "schema_version": "1.4.0",
+        "id": "GHSA-m5pq-gvj9-9vr8",
+        "aliases": [
+          "CVE-2022-24713"
+        ],
+        "summary": "Rust's regex crate vulnerable to regular expression denial of service",
+        "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json"
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713"
+          },
+          {
+            "type": "WEB",
+            "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e"
+          },
+          {
+            "type": "PACKAGE",
+            "url": "https://github.com/rust-lang/regex/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/"
+          },
+          {
+            "type": "WEB",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-08"
+          },
+          {
+            "type": "WEB",
+            "url": "https://security.gentoo.org/glsa/202208-14"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5113"
+          },
+          {
+            "type": "WEB",
+            "url": "https://www.debian.org/security/2022/dsa-5118"
+          }
+        ],
+        "database_specific": {
+          "cwe_ids": [
+            "CWE-400"
+          ],
+          "github_reviewed": true,
+          "github_reviewed_at": "2022-03-08T20:00:36Z",
+          "nvd_published_at": "2022-03-08T19:15:00Z",
+          "severity": "HIGH"
+        }
+      },
+      "RUSTSEC-2022-0013": {
+        "modified": "2023-06-13T13:10:24Z",
+        "published": "2022-03-08T12:00:00Z",
+        "schema_version": "1.4.0",
+        "id": "RUSTSEC-2022-0013",
+        "aliases": [
+          "CVE-2022-24713",
+          "GHSA-m5pq-gvj9-9vr8"
+        ],
+        "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse",
+        "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+        "affected": [
+          {
+            "package": {
+              "ecosystem": "crates.io",
+              "name": "regex",
+              "purl": "pkg:cargo/regex"
+            },
+            "ranges": [
+              {
+                "type": "SEMVER",
+                "events": [
+                  {
+                    "introduced": "0.0.0-0"
+                  },
+                  {
+                    "fixed": "1.5.5"
+                  }
+                ]
+              }
+            ],
+            "database_specific": {
+              "categories": [
+                "denial-of-service"
+              ],
+              "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+              "informational": null,
+              "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json"
+            },
+            "ecosystem_specific": {
+              "affects": {
+                "arch": [],
+                "functions": [],
+                "os": []
+              }
+            }
+          }
+        ],
+        "severity": [
+          {
+            "type": "CVSS_V3",
+            "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+          }
+        ],
+        "references": [
+          {
+            "type": "PACKAGE",
+            "url": "https://crates.io/crates/regex"
+          },
+          {
+            "type": "ADVISORY",
+            "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+          },
+          {
+            "type": "WEB",
+            "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+          }
+        ]
+      }
+    },
+    "AliasedIDList": [
+      "CVE-2022-24713",
+      "RUSTSEC-2022-0013",
+      "GHSA-m5pq-gvj9-9vr8"
+    ]
+  }
+}
diff --git a/internal/output/fixtures/test-vuln-results-a.json b/internal/output/fixtures/test-vuln-results-a.json
index 0749a28..605ee93 100644
--- a/internal/output/fixtures/test-vuln-results-a.json
+++ b/internal/output/fixtures/test-vuln-results-a.json
@@ -289,4 +289,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/internal/output/fixtures/test-vuln-results-a_windows.json b/internal/output/fixtures/test-vuln-results-a_windows.json
new file mode 100644
index 0000000..6840352
--- /dev/null
+++ b/internal/output/fixtures/test-vuln-results-a_windows.json
@@ -0,0 +1,292 @@
+{
+  "results": [
+    {
+      "source": {
+        "path": "D:\\path\\to\\go.mod",
+        "type": "lockfile"
+      },
+      "packages": [
+        {
+          "package": {
+            "name": "github.com/gogo/protobuf",
+            "version": "1.3.1",
+            "ecosystem": "Go"
+          },
+          "vulnerabilities": [
+            {
+              "modified": "2023-06-12T18:45:41Z",
+              "published": "2021-04-14T20:04:52Z",
+              "schema_version": "1.4.0",
+              "id": "GO-2021-0053",
+              "aliases": [
+                "CVE-2021-3121",
+                "GHSA-c3h9-896r-86jm"
+              ],
+              "summary": "Panic due to improper input validation in github.com/gogo/protobuf",
+              "details": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.",
+              "affected": [
+                {
+                  "package": {
+                    "ecosystem": "Go",
+                    "name": "github.com/gogo/protobuf",
+                    "purl": "pkg:golang/github.com/gogo/protobuf"
+                  },
+                  "ranges": [
+                    {
+                      "type": "SEMVER",
+                      "events": [
+                        {
+                          "introduced": "0"
+                        },
+                        {
+                          "fixed": "1.3.2"
+                        }
+                      ]
+                    }
+                  ],
+                  "database_specific": {
+                    "source": "https://vuln.go.dev/ID/GO-2021-0053.json"
+                  },
+                  "ecosystem_specific": {
+                    "imports": [
+                      {
+                        "path": "github.com/gogo/protobuf/plugin/unmarshal",
+                        "symbols": [
+                          "unmarshal.Generate",
+                          "unmarshal.field"
+                        ]
+                      }
+                    ]
+                  }
+                }
+              ],
+              "references": [
+                {
+                  "type": "FIX",
+                  "url": "https://github.com/gogo/protobuf/commit/b03c65ea87cdc3521ede29f62fe3ce239267c1bc"
+                }
+              ],
+              "database_specific": {
+                "url": "https://pkg.go.dev/vuln/GO-2021-0053"
+              }
+            }
+          ],
+          "groups": [
+            {
+              "ids": [
+                "GO-2021-0053"
+              ]
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "source": {
+        "path": "D:\\path\\to\\sub-rust-project\\Cargo.lock",
+        "type": "lockfile"
+      },
+      "packages": [
+        {
+          "package": {
+            "name": "regex",
+            "version": "1.5.1",
+            "ecosystem": "crates.io"
+          },
+          "vulnerabilities": [
+            {
+              "modified": "2022-08-11T20:38:52Z",
+              "published": "2022-03-08T20:00:36Z",
+              "schema_version": "1.4.0",
+              "id": "GHSA-m5pq-gvj9-9vr8",
+              "aliases": [
+                "CVE-2022-24713"
+              ],
+              "summary": "Rust's regex crate vulnerable to regular expression denial of service",
+              "details": "\u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\n[advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\nThe Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.",
+              "affected": [
+                {
+                  "package": {
+                    "ecosystem": "crates.io",
+                    "name": "regex",
+                    "purl": "pkg:cargo/regex"
+                  },
+                  "ranges": [
+                    {
+                      "type": "SEMVER",
+                      "events": [
+                        {
+                          "introduced": "0"
+                        },
+                        {
+                          "fixed": "1.5.5"
+                        }
+                      ]
+                    }
+                  ],
+                  "database_specific": {
+                    "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/03/GHSA-m5pq-gvj9-9vr8/GHSA-m5pq-gvj9-9vr8.json"
+                  }
+                }
+              ],
+              "severity": [
+                {
+                  "type": "CVSS_V3",
+                  "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+                }
+              ],
+              "references": [
+                {
+                  "type": "WEB",
+                  "url": "https://github.com/rust-lang/regex/security/advisories/GHSA-m5pq-gvj9-9vr8"
+                },
+                {
+                  "type": "ADVISORY",
+                  "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-24713"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://github.com/rust-lang/regex/commit/ae70b41d4f46641dbc45c7a4f87954aea356283e"
+                },
+                {
+                  "type": "PACKAGE",
+                  "url": "https://github.com/rust-lang/regex/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00003.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00009.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/JANLZ3JXWJR7FSHE57K66UIZUIJZI67T/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/O3YB7CURSG64CIPCDPNMGPE4UU24AB6H/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/PDOWTHNVGBOP2HN27PUFIGRYNSNDTYRJ/"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://security.gentoo.org/glsa/202208-08"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://security.gentoo.org/glsa/202208-14"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://www.debian.org/security/2022/dsa-5113"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://www.debian.org/security/2022/dsa-5118"
+                }
+              ],
+              "database_specific": {
+                "cwe_ids": [
+                  "CWE-400"
+                ],
+                "github_reviewed": true,
+                "github_reviewed_at": "2022-03-08T20:00:36Z",
+                "nvd_published_at": "2022-03-08T19:15:00Z",
+                "severity": "HIGH"
+              }
+            },
+            {
+              "modified": "2023-06-13T13:10:24Z",
+              "published": "2022-03-08T12:00:00Z",
+              "schema_version": "1.4.0",
+              "id": "RUSTSEC-2022-0013",
+              "aliases": [
+                "CVE-2022-24713",
+                "GHSA-m5pq-gvj9-9vr8"
+              ],
+              "summary": "Regexes with large repetitions on empty sub-expressions take a very long time to parse",
+              "details": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+              "affected": [
+                {
+                  "package": {
+                    "ecosystem": "crates.io",
+                    "name": "regex",
+                    "purl": "pkg:cargo/regex"
+                  },
+                  "ranges": [
+                    {
+                      "type": "SEMVER",
+                      "events": [
+                        {
+                          "introduced": "0.0.0-0"
+                        },
+                        {
+                          "fixed": "1.5.5"
+                        }
+                      ]
+                    }
+                  ],
+                  "database_specific": {
+                    "categories": [
+                      "denial-of-service"
+                    ],
+                    "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
+                    "informational": null,
+                    "source": "https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2022-0013.json"
+                  },
+                  "ecosystem_specific": {
+                    "affects": {
+                      "arch": [],
+                      "functions": [],
+                      "os": []
+                    }
+                  }
+                }
+              ],
+              "severity": [
+                {
+                  "type": "CVSS_V3",
+                  "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
+                }
+              ],
+              "references": [
+                {
+                  "type": "PACKAGE",
+                  "url": "https://crates.io/crates/regex"
+                },
+                {
+                  "type": "ADVISORY",
+                  "url": "https://rustsec.org/advisories/RUSTSEC-2022-0013.html"
+                },
+                {
+                  "type": "WEB",
+                  "url": "https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw"
+                }
+              ]
+            }
+          ],
+          "groups": [
+            {
+              "ids": [
+                "GHSA-m5pq-gvj9-9vr8",
+                "RUSTSEC-2022-0013"
+              ]
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/internal/output/fixtures/test-vuln-results-a_windows.sarif b/internal/output/fixtures/test-vuln-results-a_windows.sarif
new file mode 100644
index 0000000..9182728
--- /dev/null
+++ b/internal/output/fixtures/test-vuln-results-a_windows.sarif
@@ -0,0 +1,124 @@
+{
+  "version": "2.1.0",
+  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
+  "runs": [
+    {
+      "tool": {
+        "driver": {
+          "informationUri": "https://github.com/google/osv-scanner",
+          "name": "osv-scanner",
+          "rules": [
+            {
+              "id": "CVE-2022-24713",
+              "name": "CVE-2022-24713",
+              "shortDescription": {
+                "text": "CVE-2022-24713: Regexes with large repetitions on empty sub-expressions take a very long time to parse"
+              },
+              "fullDescription": {
+                "text": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security",
+                "markdown": "The Rust Security Response WG was notified that the `regex` crate did not\nproperly limit the complexity of the regular expressions (regex) it parses. An\nattacker could use this security issue to perform a denial of service, by\nsending a specially crafted regex to a service accepting untrusted regexes. No\nknown vulnerability is present when parsing untrusted input with trusted\nregexes.\n\nThis issue has been assigned CVE-2022-24713. The severity of this vulnerability\nis \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\nof the `regex` crate are not affected by this vulnerability.\n\n## Overview\n\nThe `regex` crate features built-in mitigations to prevent denial of service\nattacks caused by untrusted regexes, or untrusted input matched by trusted\nregexes. Those (tunable) mitigations already provide sane defaults to prevent\nattacks. This guarantee is documented and it's considered part of the crate's\nAPI.\n\nUnfortunately a bug was discovered in the mitigations designed to prevent\nuntrusted regexes to take an arbitrary amount of time during parsing, and it's\npossible to craft regexes that bypass such mitigations. This makes it possible\nto perform denial of service attacks by sending specially crafted regexes to\nservices accepting user-controlled, untrusted regexes.\n\n## Affected versions\n\nAll versions of the `regex` crate before or equal to 1.5.4 are affected by this\nissue. The fix is include starting from  `regex` 1.5.5.\n\n## Mitigations\n\nWe recommend everyone accepting user-controlled regexes to upgrade immediately\nto the latest version of the `regex` crate.\n\nUnfortunately there is no fixed set of problematic regexes, as there are\npractically infinite regexes that could be crafted to exploit this\nvulnerability. Because of this, we do not recommend denying known problematic\nregexes.\n\n## Acknowledgements\n\nWe want to thank Addison Crump for responsibly disclosing this to us according\nto the [Rust security policy][1], and for helping review the fix.\n\nWe also want to thank Andrew Gallant for developing the fix, and Pietro Albini\nfor coordinating the disclosure and writing this advisory.\n\n[1]: https://www.rust-lang.org/policies/security"
+              },
+              "deprecatedIds": [
+                "CVE-2022-24713",
+                "RUSTSEC-2022-0013",
+                "GHSA-m5pq-gvj9-9vr8"
+              ],
+              "help": {
+                "text": "**Your dependency is vulnerable to [CVE-2022-24713](https://osv.dev/list?q=CVE-2022-24713)**\n(Also published as: [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013), [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8), ).\n\n## [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e The Rust Security Response WG was notified that the `regex` crate did not\n\u003e properly limit the complexity of the regular expressions (regex) it parses. An\n\u003e attacker could use this security issue to perform a denial of service, by\n\u003e sending a specially crafted regex to a service accepting untrusted regexes. No\n\u003e known vulnerability is present when parsing untrusted input with trusted\n\u003e regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability\n\u003e is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\n\u003e of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service\n\u003e attacks caused by untrusted regexes, or untrusted input matched by trusted\n\u003e regexes. Those (tunable) mitigations already provide sane defaults to prevent\n\u003e attacks. This guarantee is documented and it's considered part of the crate's\n\u003e API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent\n\u003e untrusted regexes to take an arbitrary amount of time during parsing, and it's\n\u003e possible to craft regexes that bypass such mitigations. This makes it possible\n\u003e to perform denial of service attacks by sending specially crafted regexes to\n\u003e services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this\n\u003e issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately\n\u003e to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are\n\u003e practically infinite regexes that could be crafted to exploit this\n\u003e vulnerability. Because of this, we do not recommend denying known problematic\n\u003e regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according\n\u003e to the [Rust security policy][1], and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini\n\u003e for coordinating the disclosure and writing this advisory.\n\u003e \n\u003e [1]: https://www.rust-lang.org/policies/security\n\n\u003c/details\u003e\n\n## [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e \u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\u003e \n\u003e [advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\u003e \n\u003e The Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\sub-rust-project\\Cargo.lock | regex | 1.5.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GHSA-m5pq-gvj9-9vr8 | regex | 1.5.5 |\n| RUSTSEC-2022-0013 | regex | 1.5.5 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to\\sub-rust-project/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2022-24713\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n",
+                "markdown": "**Your dependency is vulnerable to [CVE-2022-24713](https://osv.dev/list?q=CVE-2022-24713)**\n(Also published as: [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013), [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8), ).\n\n## [RUSTSEC-2022-0013](https://osv.dev/vulnerability/RUSTSEC-2022-0013)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e The Rust Security Response WG was notified that the `regex` crate did not\n\u003e properly limit the complexity of the regular expressions (regex) it parses. An\n\u003e attacker could use this security issue to perform a denial of service, by\n\u003e sending a specially crafted regex to a service accepting untrusted regexes. No\n\u003e known vulnerability is present when parsing untrusted input with trusted\n\u003e regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability\n\u003e is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses\n\u003e of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service\n\u003e attacks caused by untrusted regexes, or untrusted input matched by trusted\n\u003e regexes. Those (tunable) mitigations already provide sane defaults to prevent\n\u003e attacks. This guarantee is documented and it's considered part of the crate's\n\u003e API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent\n\u003e untrusted regexes to take an arbitrary amount of time during parsing, and it's\n\u003e possible to craft regexes that bypass such mitigations. This makes it possible\n\u003e to perform denial of service attacks by sending specially crafted regexes to\n\u003e services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this\n\u003e issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately\n\u003e to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are\n\u003e practically infinite regexes that could be crafted to exploit this\n\u003e vulnerability. Because of this, we do not recommend denying known problematic\n\u003e regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according\n\u003e to the [Rust security policy][1], and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini\n\u003e for coordinating the disclosure and writing this advisory.\n\u003e \n\u003e [1]: https://www.rust-lang.org/policies/security\n\n\u003c/details\u003e\n\n## [GHSA-m5pq-gvj9-9vr8](https://osv.dev/vulnerability/GHSA-m5pq-gvj9-9vr8)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e \u003e This is a cross-post of [the official security advisory][advisory]. The official advisory contains a signed version with our PGP key, as well.\n\u003e \n\u003e [advisory]: https://groups.google.com/g/rustlang-security-announcements/c/NcNNL1Jq7Yw\n\u003e \n\u003e The Rust Security Response WG was notified that the `regex` crate did not properly limit the complexity of the regular expressions (regex) it parses. An attacker could use this security issue to perform a denial of service, by sending a specially crafted regex to a service accepting untrusted regexes. No known vulnerability is present when parsing untrusted input with trusted regexes.\n\u003e \n\u003e This issue has been assigned CVE-2022-24713. The severity of this vulnerability is \"high\" when the `regex` crate is used to parse untrusted regexes. Other uses of the `regex` crate are not affected by this vulnerability.\n\u003e \n\u003e ## Overview\n\u003e \n\u003e The `regex` crate features built-in mitigations to prevent denial of service attacks caused by untrusted regexes, or untrusted input matched by trusted regexes. Those (tunable) mitigations already provide sane defaults to prevent attacks. This guarantee is documented and it's considered part of the crate's API.\n\u003e \n\u003e Unfortunately a bug was discovered in the mitigations designed to prevent untrusted regexes to take an arbitrary amount of time during parsing, and it's possible to craft regexes that bypass such mitigations. This makes it possible to perform denial of service attacks by sending specially crafted regexes to services accepting user-controlled, untrusted regexes.\n\u003e \n\u003e ## Affected versions\n\u003e \n\u003e All versions of the `regex` crate before or equal to 1.5.4 are affected by this issue. The fix is include starting from  `regex` 1.5.5.\n\u003e \n\u003e ## Mitigations\n\u003e \n\u003e We recommend everyone accepting user-controlled regexes to upgrade immediately to the latest version of the `regex` crate.\n\u003e \n\u003e Unfortunately there is no fixed set of problematic regexes, as there are practically infinite regexes that could be crafted to exploit this vulnerability. Because of this, we do not recommend denying known problematic regexes.\n\u003e \n\u003e ## Acknowledgements\n\u003e \n\u003e We want to thank Addison Crump for responsibly disclosing this to us according to the [Rust security policy](https://www.rust-lang.org/policies/security), and for helping review the fix.\n\u003e \n\u003e We also want to thank Andrew Gallant for developing the fix, and Pietro Albini for coordinating the disclosure and writing this advisory.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\sub-rust-project\\Cargo.lock | regex | 1.5.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GHSA-m5pq-gvj9-9vr8 | regex | 1.5.5 |\n| RUSTSEC-2022-0013 | regex | 1.5.5 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to\\sub-rust-project/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2022-24713\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n"
+              }
+            },
+            {
+              "id": "CVE-2021-3121",
+              "name": "CVE-2021-3121",
+              "shortDescription": {
+                "text": "CVE-2021-3121: Panic due to improper input validation in github.com/gogo/protobuf"
+              },
+              "fullDescription": {
+                "text": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.",
+                "markdown": "Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector."
+              },
+              "deprecatedIds": [
+                "CVE-2021-3121",
+                "GO-2021-0053",
+                "GHSA-c3h9-896r-86jm"
+              ],
+              "help": {
+                "text": "**Your dependency is vulnerable to [CVE-2021-3121](https://osv.dev/list?q=CVE-2021-3121)**.\n\n## [GO-2021-0053](https://osv.dev/vulnerability/GO-2021-0053)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\go.mod | github.com/gogo/protobuf | 1.3.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GO-2021-0053 | github.com/gogo/protobuf | 1.3.2 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2021-3121\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n",
+                "markdown": "**Your dependency is vulnerable to [CVE-2021-3121](https://osv.dev/list?q=CVE-2021-3121)**.\n\n## [GO-2021-0053](https://osv.dev/vulnerability/GO-2021-0053)\n\n\u003cdetails\u003e\n\u003csummary\u003eDetails\u003c/summary\u003e\n\n\u003e Due to improper bounds checking, maliciously crafted input to generated Unmarshal methods can cause an out-of-bounds panic. If parsing messages from untrusted parties, this may be used as a denial of service vector.\n\n\u003c/details\u003e\n\n---\n\n### Affected Packages\n\n| Source | Package Name | Package Version |\n| --- | --- | --- |\n| lockfile:D:\\path\\to\\go.mod | github.com/gogo/protobuf | 1.3.1 |\n\n## Remediation\n\nTo fix these vulnerabilities, update the vulnerabilities past the listed fixed versions below.\n\n### Fixed Versions\n\n| Vulnerability ID | Package Name | Fixed Version |\n| --- | --- | --- |\n| GO-2021-0053 | github.com/gogo/protobuf | 1.3.2 |\n\nIf you believe these vulnerabilities do not affect your code and wish to ignore them, add them to the ignore list in an\n`osv-scanner.toml` file located in the same directory as the lockfile containing the vulnerable dependency.\n\nSee the format and more options in our documentation here: https://google.github.io/osv-scanner/configuration/\n\nAdd or append these values to the following config files to ignore this vulnerability:\n\n`D:\\path\\to/osv-scanner.toml`\n\n```\n[[IgnoredVulns]]\nid = \"CVE-2021-3121\"\nreason = \"Your reason for ignoring this vulnerability\"\n```\n"
+              }
+            }
+          ],
+          "version": "1.4.3"
+        }
+      },
+      "artifacts": [
+        {
+          "location": {
+            "uri": "file:///D:/path/to/sub-rust-project/Cargo.lock"
+          },
+          "length": -1
+        },
+        {
+          "location": {
+            "uri": "file:///D:/path/to/go.mod"
+          },
+          "length": -1
+        }
+      ],
+      "results": [
+        {
+          "ruleId": "CVE-2022-24713",
+          "ruleIndex": 0,
+          "level": "warning",
+          "message": {
+            "text": "Package 'regex@1.5.1' is vulnerable to 'CVE-2022-24713' (also known as 'RUSTSEC-2022-0013', 'GHSA-m5pq-gvj9-9vr8')."
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "file:///D:/path/to/sub-rust-project/Cargo.lock"
+                }
+              }
+            }
+          ]
+        },
+        {
+          "ruleId": "CVE-2021-3121",
+          "ruleIndex": 1,
+          "level": "warning",
+          "message": {
+            "text": "Package 'github.com/gogo/protobuf@1.3.1' is vulnerable to 'CVE-2021-3121' (also known as 'GO-2021-0053', 'GHSA-c3h9-896r-86jm')."
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "file:///D:/path/to/go.mod"
+                }
+              }
+            }
+          ]
+        },
+        {
+          "ruleId": "CVE-2022-24713",
+          "ruleIndex": 0,
+          "level": "warning",
+          "message": {
+            "text": "Package 'regex@1.5.1' is vulnerable to 'CVE-2022-24713' (also known as 'RUSTSEC-2022-0013', 'GHSA-m5pq-gvj9-9vr8')."
+          },
+          "locations": [
+            {
+              "physicalLocation": {
+                "artifactLocation": {
+                  "uri": "file:///D:/path/to/sub-rust-project/Cargo.lock"
+                }
+              }
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/internal/semantic/compare_test.go b/internal/semantic/compare_test.go
index de20024..99ed53b 100644
--- a/internal/semantic/compare_test.go
+++ b/internal/semantic/compare_test.go
@@ -217,6 +217,14 @@ func TestVersion_Compare_Ecosystems(t *testing.T) {
 			name: "Debian",
 			file: "debian-versions-generated.txt",
 		},
+		{
+			name: "CRAN",
+			file: "cran-versions.txt",
+		},
+		{
+			name: "CRAN",
+			file: "cran-versions-generated.txt",
+		},
 	}
 	for _, tt := range tests {
 		tt := tt
diff --git a/internal/semantic/parse_test.go b/internal/semantic/parse_test.go
index 6072aca..8fcea3f 100644
--- a/internal/semantic/parse_test.go
+++ b/internal/semantic/parse_test.go
@@ -13,6 +13,9 @@ func TestParse(t *testing.T) {
 
 	ecosystems := lockfile.KnownEcosystems()
 
+	// todo: remove once CRAN is supported by lockfile
+	ecosystems = append(ecosystems, "CRAN")
+
 	for _, ecosystem := range ecosystems {
 		_, err := semantic.Parse("", ecosystem)
 
@@ -33,6 +36,9 @@ func TestMustParse(t *testing.T) {
 
 	ecosystems := lockfile.KnownEcosystems()
 
+	// todo: remove once CRAN is supported by lockfile
+	ecosystems = append(ecosystems, "CRAN")
+
 	for _, ecosystem := range ecosystems {
 		semantic.MustParse("", ecosystem)
 	}
diff --git a/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json b/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json
index 355e95d..94e7867 100755
--- a/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json
+++ b/internal/sourceanalysis/integration/fixtures-go/snapshots/govulncheckshim_test.json
@@ -18,7 +18,7 @@
                   "column":22,
                   "filename":"<Any value>",
                   "line":13,
-                  "offset":234
+                  "offset":-1
                }
             }
          ]
diff --git a/internal/sourceanalysis/integration_test.go b/internal/sourceanalysis/integration_test.go
index c7b47c4..b30113a 100644
--- a/internal/sourceanalysis/integration_test.go
+++ b/internal/sourceanalysis/integration_test.go
@@ -50,5 +50,6 @@ func Test_RunGoVulnCheck(t *testing.T) {
 	}
 
 	res["GO-2023-1558"][0].Trace[1].Position.Filename = "<Any value>"
+	res["GO-2023-1558"][0].Trace[1].Position.Offset = -1
 	testutility.AssertMatchFixtureJSON(t, filepath.Join(fixturesDir, "snapshots/govulncheckshim_test.json"), res)
 }
diff --git a/internal/sourceanalysis/rust_test.go b/internal/sourceanalysis/rust_test.go
index be97cd0..c5a34b9 100644
--- a/internal/sourceanalysis/rust_test.go
+++ b/internal/sourceanalysis/rust_test.go
@@ -94,7 +94,7 @@ func Test_rustBuildSource(t *testing.T) {
 				},
 			},
 			want: []string{
-				workingDir + "/fixtures-rust/rust-project/target/release/test-project",
+				workingDir + filepath.FromSlash("/fixtures-rust/rust-project/target/release/test-project") + testutility.ValueIfOnWindows(".exe", ""),
 			},
 		},
 	}
diff --git a/internal/testutility/utility.go b/internal/testutility/utility.go
index 1501c5e..361581a 100644
--- a/internal/testutility/utility.go
+++ b/internal/testutility/utility.go
@@ -4,7 +4,10 @@ import (
 	"encoding/json"
 	"fmt"
 	"os"
+	"path/filepath"
 	"reflect"
+	"runtime"
+	"strings"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -13,36 +16,67 @@ import (
 	"github.com/hexops/gotextdiff/span"
 )
 
-// LoadJSONFixture loads a JSON fixture file and returns the decoded version.
-func LoadJSONFixture[V any](t *testing.T, path string) V {
+func determineWindowsFixturePath(t *testing.T, path string) string {
 	t.Helper()
-	file, err := os.Open(path)
-	if err != nil {
-		t.Fatalf("Failed to open fixture: %s", err)
-	}
-	var value V
-	err = json.NewDecoder(file).Decode(&value)
-	if err != nil {
-		t.Fatalf("Failed to parse fixture: %s", err)
-	}
 
-	return value
+	ext := filepath.Ext(path)
+
+	return strings.TrimSuffix(path, ext) + "_windows" + ext
 }
 
-// AssertMatchFixtureJSON matches the JSON at path with the value val, failing if not equal, printing out the difference.
-func AssertMatchFixtureJSON[V any](t *testing.T, path string, val V) {
+func loadFixture(t *testing.T, path string) ([]byte, string) {
 	t.Helper()
-	fileA, err := os.ReadFile(path)
+
+	var file []byte
+	var err error
+
+	// when on Windows, check if there is a version of the fixture whose filename
+	// ends with _windows and if so use that instead
+	if //goland:noinspection GoBoolExpressions
+	runtime.GOOS == "windows" {
+		winFixturePath := determineWindowsFixturePath(t, path)
+
+		file, err = os.ReadFile(winFixturePath)
+
+		if err == nil {
+			return file, winFixturePath
+		}
+		// load the original file if a Windows-specific version does not exist
+		if !os.IsNotExist(err) {
+			t.Fatalf("Failed to open fixture: %s", err)
+		}
+	}
+
+	file, err = os.ReadFile(path)
+
 	if err != nil {
 		t.Fatalf("Failed to open fixture: %s", err)
 	}
 
+	return file, path
+}
+
+// LoadJSONFixture loads a JSON fixture file and returns the decoded version.
+func LoadJSONFixture[V any](t *testing.T, path string) V {
+	t.Helper()
+
+	file, _ := loadFixture(t, path)
+
 	var elem V
-	err = json.Unmarshal(fileA, &elem)
+	err := json.Unmarshal(file, &elem)
 	if err != nil {
 		t.Fatalf("Failed to unmarshal val: %s", err)
 	}
 
+	return elem
+}
+
+// AssertMatchFixtureJSON matches the JSON at path with the value val, failing if not equal, printing out the difference.
+func AssertMatchFixtureJSON[V any](t *testing.T, path string, val V) {
+	t.Helper()
+
+	elem := LoadJSONFixture[V](t, path)
+
 	if !reflect.DeepEqual(val, elem) {
 		t.Errorf("Not equal: \n%s", cmp.Diff(val, elem))
 	}
@@ -80,16 +114,20 @@ func CreateTextFixture(t *testing.T, path string, val string) {
 	}
 }
 
+func normalizeNewlines(content string) string {
+	return strings.ReplaceAll(content, "\r\n", "\n")
+}
+
 // AssertMatchFixtureText matches the Text file at path with actual
 func AssertMatchFixtureText(t *testing.T, path string, actual string) {
 	t.Helper()
-	fileA, err := os.ReadFile(path)
-	if err != nil {
-		t.Fatalf("Failed to open fixture: %s", err)
-	}
 
+	fileA, path := loadFixture(t, path)
+
+	actual = normalizeNewlines(actual)
 	expect := string(fileA)
-	if actual != string(fileA) {
+	expect = normalizeNewlines(expect)
+	if actual != expect {
 		if os.Getenv("TEST_NO_DIFF") == "true" {
 			t.Errorf("\nactual %s does not match expected:\n got:\n%s\n\n want:\n%s", path, actual, expect)
 		} else {
@@ -108,3 +146,12 @@ func AcceptanceTests(t *testing.T, reason string) {
 		t.Skip("Skipping extended test: ", reason)
 	}
 }
+
+func ValueIfOnWindows(win, or string) string {
+	if //goland:noinspection GoBoolExpressions
+	runtime.GOOS == "windows" {
+		return win
+	}
+
+	return or
+}