Repository: google/osv-scanner

Issue:
Regression in requirements.txt since 2.1.0
[requirements.txt](https://github.com/user-attachments/files/21669489/requirements.txt)

2.0.3
```
>"\Users\egon\Downloads\osv-scanner_windows_amd64 (2).exe" scan source -L requirements.txt
Scanned C:\m\1\tests\requirements.txt file and found 17 packages
╭─────────────────────────────────────┬──────┬───────────┬──────────────┬─────────┬──────────────────╮
│ OSV URL                             │ CVSS │ ECOSYSTEM │ PACKAGE      │ VERSION │ SOURCE           │
├─────────────────────────────────────┼──────┼───────────┼──────────────┼─────────┼──────────────────┤
│ https://osv.dev/PYSEC-2023-254      │ 7.5  │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-jfhm-5ghh-2f97 │      │           │              │         │                  │
│ https://osv.dev/GHSA-3ww4-gg4f-jr7f │ 8.7  │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-5cpq-8wj7-hf2v │      │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-9v9h-cgj8-h64p │ 5.5  │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-jm77-qphf-c4w8 │      │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-v8gr-m533-ghj9 │      │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-w7pp-m8wf-vj6r │ 6.9  │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-x4qr-2fvf-3mr5 │ 7.4  │ PyPI      │ cryptography │ 3.3.2   │ requirements.txt │
│ https://osv.dev/GHSA-3rq5-2g8h-59hc │ 5.9  │ PyPI      │ dnspython    │ 1.14.0  │ requirements.txt │
│ https://osv.dev/PYSEC-2018-12       │ 6.1  │ PyPI      │ lxml         │ 3.6.0   │ requirements.txt │
│ https://osv.dev/GHSA-xp26-p53h-6h2p │      │           │              │         │                  │
│ https://osv.dev/PYSEC-2020-62       │ 6.1  │ PyPI      │ lxml         │ 3.6.0   │ requirements.txt │
│ https://osv.dev/GHSA-pgww-xf46-h92r │      │           │              │         │                  │
│ https://osv.dev/PYSEC-2021-19       │ 6.1  │ PyPI      │ lxml         │ 3.6.0   │ requirements.txt │
│ https://osv.dev/GHSA-jq4v-f5q6-mjqq │      │           │              │         │                  │
│ https://osv.dev/PYSEC-2022-230      │ 6.9  │ PyPI      │ lxml         │ 3.6.0   │ requirements.txt │
│ https://osv.dev/GHSA-wrxv-2j5q-m38w │      │           │              │         │                  │
│ https://osv.dev/GHSA-55x5-fj6c-h6m8 │ 8.2  │ PyPI      │ lxml         │ 3.6.0   │ requirements.txt │
│ https://osv.dev/PYSEC-2022-166      │      │ PyPI      │ paramiko     │ 2.7.1   │ requirements.txt │
│ https://osv.dev/GHSA-45x7-px36-x8w8 │ 5.9  │ PyPI      │ paramiko     │ 2.7.1   │ requirements.txt │
│ https://osv.dev/PYSEC-2018-28       │ 7.5  │ PyPI      │ requests     │ 2.9.1   │ requirements.txt │
│ https://osv.dev/GHSA-x84v-xcm2-53pg │      │           │              │         │                  │
│ https://osv.dev/PYSEC-2023-74       │ 6.1  │ PyPI      │ requests     │ 2.9.1   │ requirements.txt │
│ https://osv.dev/GHSA-j8r2-6x86-q33q │      │           │              │         │                  │
│ https://osv.dev/GHSA-9hjg-9r4m-mvj7 │ 5.3  │ PyPI      │ requests     │ 2.9.1   │ requirements.txt │
│ https://osv.dev/GHSA-9wx4-h78v-vm56 │ 5.6  │ PyPI      │ requests     │ 2.9.1   │ requirements.txt │
╰─────────────────────────────────────┴──────┴───────────┴──────────────┴─────────┴──────────────────╯
```

2.2.0 (and similar 2.1.0)
```
>"\Users\egon\Downloads\osv-scanner_windows_amd64.exe" scan source -L requirements.txt
unsupported file extension for requirements: .zip
(extracting as python/requirementsenhanceable) open m\1\tests\requirements.txt: invalid argument
```

Note the broken path with missing `C:\`. `-L` on Windows can be also a bit tricky, `C:\path\path` might be confused with `extractor:path`, the last time I checked I had to always pass extractor name on Windows with absolute paths.


Hints from issue comments:
@another-rex: Quick question, what shell are you using? Currently running in powershell I cannot seem to replicate this issue. I suspect maybe your shell is expanding requirements.txt path to an absolute path?

Passing absolute paths and having it intepreted as a extractor is something we should fix either way though. 

For now, can you try running it like this:

```
osv-scanner.exe scan source -L :requirements.txt 
# OR
osv-scanner.exe scan source -L :.\requirements.txt 
```

note the prepended `:`@egonk: @another-rex 2.2.2 works for me with absolute paths:
```
C:\Users\ekocj>downloads\osv-scanner_windows_amd64.exe -L c:\Users\ekocj\Downloads\go.mod
Scanned c:\Users\ekocj\Downloads\go.mod file and found 186 packages
No issues found
```

2.2.1 did not work
```
C:\Users\ekocj>"downloads\osv-scanner_windows_amd64 (1).exe" -L c:\Users\ekocj\Downloads\go.mod
could not determine extractor, requested c
```

That was fix #2188@egonk: @another-rex The problem with this specific requirements.txt is still there in 2.2.2 however, I used cmd.exe but the output is the same in powershell.
```
C:\Users\ekocj\Downloads>osv-scanner_windows_amd64.exe -L requirements.txt
unsupported file extension for requirements: .zip
(extracting as python/requirementsenhanceable) open Users\ekocj\Downloads\requirements.txt: invalid argument

C:\Users\ekocj\Downloads>osv-scanner_windows_amd64.exe -L :requirements.txt
unsupported file extension for requirements: .zip
(extracting as python/requirementsenhanceable) open Users\ekocj\Downloads\requirements.txt: invalid argument

C:\Users\ekocj\Downloads>osv-scanner_windows_amd64.exe -L :.\requirements.txt
unsupported file extension for requirements: .zip
(extracting as python/requirementsenhanceable) open Users\ekocj\Downloads\requirements.txt: invalid argument

C:\Users\ekocj\Downloads>osv-scanner_windows_amd64.exe -L c:\Users\ekocj\Downloads\requirements.txt
unsupported file extension for requirements: .zip
(extracting as python/requirementsenhanceable) open Users\ekocj\Downloads\requirements.txt: invalid argument
```@another-rex: Ah I see, I think it's something to do with transitive dependencies. I'll look into this further.@another-rex: Made the fix PR, in the mean time before the next fix is released, you can fix this by passing `--no-resolve` flag. 

The `.zip` error is it trying to resolve a transitive dependency and failing (dnspython@1.14.0), and it is falling back to the non resolving extractor, but that fallback logic is failing.

Solution patch:
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 2a6f3021..b2a5f069 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -38,7 +38,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - run: scripts/report_uncleaned_snapshots.py
@@ -49,7 +49,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - name: Run format action
@@ -61,7 +61,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - name: Set up Go
@@ -77,7 +77,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - name: Set up Go
@@ -93,7 +93,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - run: scripts/build_test_images.sh
@@ -115,10 +115,10 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
-      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+      - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
         with:
           name: image-testdata-${{ github.run_number }}-${{ github.run_attempt }}
           path: cmd/osv-scanner/scan/image/testdata/
@@ -140,7 +140,7 @@ jobs:
       DOCKER_CLI_EXPERIMENTAL: "enabled"
     steps:
       - name: Checkout
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           fetch-depth: 0
       - name: Set up Go
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a7a7ef47..cb2dfb61 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -40,7 +40,7 @@ jobs:
 
     steps:
       - name: Checkout repository
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
       # Update go to the latest version to support minor go versions is go.mod file
       - name: Install Go
         uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
diff --git a/.github/workflows/goreleaser-nightly.yml b/.github/workflows/goreleaser-nightly.yml
index 33318000..464ec2d3 100644
--- a/.github/workflows/goreleaser-nightly.yml
+++ b/.github/workflows/goreleaser-nightly.yml
@@ -23,7 +23,7 @@ jobs:
       DOCKER_CLI_EXPERIMENTAL: "enabled"
     steps:
       - name: Checkout
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           fetch-depth: 0
       - name: Set up Go
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 8aebbe02..43b1fb1a 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -22,7 +22,7 @@ jobs:
       DOCKER_CLI_EXPERIMENTAL: "enabled"
     steps:
       - name: Checkout
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           fetch-depth: 0
       - name: Set up Go
diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml
index 4ce708b1..5581a20d 100644
--- a/.github/workflows/links.yml
+++ b/.github/workflows/links.yml
@@ -20,7 +20,7 @@ jobs:
       contents: read # to fetch code (actions/checkout)
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
       - uses: tcort/github-action-markdown-link-check@a800ad5f1c35bf61987946fd31c15726a1c9f2ba # v1.1.0
         with:
           use-quiet-mode: "yes"
diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index 4e01978b..9a93ebff 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -30,6 +30,6 @@ jobs:
     name: Validate PR title
     runs-on: ubuntu-latest
     steps:
-      - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
+      - uses: amannn/action-semantic-pull-request@fdd4d3ddf614fbcd8c29e4b106d3bbe0cb2c605d # v6.0.1
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/prerelease-check.yml b/.github/workflows/prerelease-check.yml
index ce854fa2..df672668 100644
--- a/.github/workflows/prerelease-check.yml
+++ b/.github/workflows/prerelease-check.yml
@@ -34,7 +34,7 @@ jobs:
       contents: read # to fetch code (actions/checkout)
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
       - uses: tcort/github-action-markdown-link-check@a800ad5f1c35bf61987946fd31c15726a1c9f2ba # v1.1.0
         with:
           use-quiet-mode: "yes"
@@ -46,7 +46,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - name: Run lint action
@@ -58,7 +58,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - name: Set up Go
@@ -74,7 +74,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
           ref: ${{ inputs.commit }}
@@ -91,7 +91,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - run: scripts/build_test_images.sh
@@ -113,11 +113,11 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
           ref: ${{ inputs.commit }}
-      - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+      - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
         with:
           name: image-testdata-${{ github.run_number }}-${{ github.run_attempt }}
           path: cmd/osv-scanner/scan/image/testdata/
@@ -137,7 +137,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
           ref: ${{ inputs.commit }}
diff --git a/.github/workflows/renovate-validator.yml b/.github/workflows/renovate-validator.yml
index 83ea707d..780478a9 100644
--- a/.github/workflows/renovate-validator.yml
+++ b/.github/workflows/renovate-validator.yml
@@ -21,7 +21,7 @@ jobs:
       contents: read # to fetch code (actions/checkout)
     steps:
       - name: Checkout code
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 
       - name: Set up Nodes.js
         uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 1afb34bf..c846eed2 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -33,7 +33,7 @@ jobs:
 
     steps:
       - name: "Checkout code"
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
 
diff --git a/.github/workflows/snapshots.yml b/.github/workflows/snapshots.yml
index 36066e4b..6cb6166f 100644
--- a/.github/workflows/snapshots.yml
+++ b/.github/workflows/snapshots.yml
@@ -21,7 +21,7 @@ jobs:
       pull-requests: write # Create pull requests
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+      - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
         with:
           persist-credentials: false
       - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
diff --git a/cmd/osv-scanner/scan/image/__snapshots__/command_test.snap b/cmd/osv-scanner/scan/image/__snapshots__/command_test.snap
index 3a54212a..23a02532 100755
--- a/cmd/osv-scanner/scan/image/__snapshots__/command_test.snap
+++ b/cmd/osv-scanner/scan/image/__snapshots__/command_test.snap
@@ -273,7 +273,7 @@ You can also view the full vulnerability list in your terminal with: `osv-scanne
 Scanning local image tarball "./testdata/test-ubuntu.tar"
 
 Container Scanning Result (Ubuntu 22.04.5 LTS):
-Total 19 packages affected by 36 known vulnerabilities (2 Critical, 9 High, 20 Medium, 4 Low, 1 Unknown) from 1 ecosystem.
+Total 19 packages affected by 37 known vulnerabilities (2 Critical, 10 High, 20 Medium, 4 Low, 1 Unknown) from 1 ecosystem.
 15 vulnerabilities can be fixed.
 
 
@@ -296,7 +296,7 @@ Ubuntu:22.04
 | libzstd        | 1.4.8+dfsg-3build1           | No fix available        |          1 | libzstd1                | # 4 Layer        | ubuntu        |
 | ncurses        | 6.3-2ubuntu0.1               | No fix available        |          2 | libncurses6... (5)      | # 4 Layer        | ubuntu        |
 | openssl        | 3.0.2-0ubuntu1.18            | Partial fixes Available |          3 | libssl3                 | # 4 Layer        | ubuntu        |
-| pam            | 1.4.0-11ubuntu2.5            | Partial fixes Available |          2 | libpam-modules... (4)   | # 4 Layer        | ubuntu        |
+| pam            | 1.4.0-11ubuntu2.5            | Partial fixes Available |          3 | libpam-modules... (4)   | # 4 Layer        | ubuntu        |
 | pcre2          | 10.39-3ubuntu0.1             | No fix available        |          1 | libpcre2-8-0            | # 4 Layer        | ubuntu        |
 | perl           | 5.34.0-3ubuntu1.3            | Partial fixes Available |          3 | perl-base               | # 4 Layer        | ubuntu        |
 | shadow         | 1:4.8.1-2ubuntu2.2           | No fix available        |          2 | login, passwd           | # 4 Layer        | ubuntu        |
@@ -318,7 +318,7 @@ You can also view the full vulnerability list in your terminal with: `osv-scanne
 Scanning local image tarball "./testdata/test-ubuntu.tar"
 
 Container Scanning Result (Ubuntu 22.04.5 LTS):
-Total 19 packages affected by 36 known vulnerabilities (2 Critical, 9 High, 20 Medium, 4 Low, 1 Unknown) from 1 ecosystem.
+Total 19 packages affected by 37 known vulnerabilities (2 Critical, 10 High, 20 Medium, 4 Low, 1 Unknown) from 1 ecosystem.
 15 vulnerabilities can be fixed.
 
 
@@ -341,7 +341,7 @@ Ubuntu:22.04
 | libzstd        | 1.4.8+dfsg-3build1           | No fix available        |          1 | libzstd1                | # 4 Layer        | ubuntu        |
 | ncurses        | 6.3-2ubuntu0.1               | No fix available        |          2 | libncurses6... (5)      | # 4 Layer        | ubuntu        |
 | openssl        | 3.0.2-0ubuntu1.18            | Partial fixes Available |          3 | libssl3                 | # 4 Layer        | ubuntu        |
-| pam            | 1.4.0-11ubuntu2.5            | Partial fixes Available |          2 | libpam-modules... (4)   | # 4 Layer        | ubuntu        |
+| pam            | 1.4.0-11ubuntu2.5            | Partial fixes Available |          3 | libpam-modules... (4)   | # 4 Layer        | ubuntu        |
 | pcre2          | 10.39-3ubuntu0.1             | No fix available        |          1 | libpcre2-8-0            | # 4 Layer        | ubuntu        |
 | perl           | 5.34.0-3ubuntu1.3            | Partial fixes Available |          3 | perl-base               | # 4 Layer        | ubuntu        |
 | shadow         | 1:4.8.1-2ubuntu2.2           | No fix available        |          2 | login, passwd           | # 4 Layer        | ubuntu        |
@@ -2811,9 +2811,10 @@ Scanning local image tarball "./testdata/test-node_modules-npm-full.tar"
           "vulnerabilities": [
             "USN-7580-1",
             "UBUNTU-CVE-2024-10041",
-            "UBUNTU-CVE-2025-6020"
+            "UBUNTU-CVE-2025-6020",
+            "UBUNTU-CVE-2025-8941"
           ],
-          "groups": 2
+          "groups": 3
         },
         {
           "package": {
@@ -2828,9 +2829,10 @@ Scanning local image tarball "./testdata/test-node_modules-npm-full.tar"
           "vulnerabilities": [
             "USN-7580-1",
             "UBUNTU-CVE-2024-10041",
-            "UBUNTU-CVE-2025-6020"
+            "UBUNTU-CVE-2025-6020",
+            "UBUNTU-CVE-2025-8941"
           ],
-          "groups": 2
+          "groups": 3
         },
         {
           "package": {
@@ -2845,9 +2847,10 @@ Scanning local image tarball "./testdata/test-node_modules-npm-full.tar"
           "vulnerabilities": [
             "USN-7580-1",
             "UBUNTU-CVE-2024-10041",
-            "UBUNTU-CVE-2025-6020"
+            "UBUNTU-CVE-2025-6020",
+            "UBUNTU-CVE-2025-8941"
           ],
-          "groups": 2
+          "groups": 3
         },
         {
           "package": {
@@ -2862,9 +2865,10 @@ Scanning local image tarball "./testdata/test-node_modules-npm-full.tar"
           "vulnerabilities": [
             "USN-7580-1",
             "UBUNTU-CVE-2024-10041",
-            "UBUNTU-CVE-2025-6020"
+            "UBUNTU-CVE-2025-6020",
+            "UBUNTU-CVE-2025-8941"
           ],
-          "groups": 2
+          "groups": 3
         },
         {
           "package": {
diff --git a/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap b/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap
index 2523649c..5f720608 100755
--- a/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap
+++ b/cmd/osv-scanner/scan/source/__snapshots__/command_test.snap
@@ -3517,6 +3517,37 @@ No issues found
 
 ---
 
+[TestCommand_LockfileWithExplicitParseAs/absolute_paths_are_automatically_escaped_on_windows - 1]
+Scanned <rootdir>/fixtures/locks-many/yarn.lock file and found 1 package
+Loaded filter from: <rootdir>/fixtures/locks-many/osv-scanner.toml
+No issues found
+
+---
+
+[TestCommand_LockfileWithExplicitParseAs/absolute_paths_are_automatically_escaped_on_windows - 2]
+
+---
+
+[TestCommand_LockfileWithExplicitParseAs/absolute_paths_can_have_explicit_parse_as - 1]
+
+---
+
+[TestCommand_LockfileWithExplicitParseAs/absolute_paths_can_have_explicit_parse_as - 2]
+(extracting as javascript/packagelockjson) could not extract: invalid character '#' looking for beginning of value
+
+---
+
+[TestCommand_LockfileWithExplicitParseAs/absolute_paths_work_with_explicit_escaping - 1]
+Scanned <rootdir>/fixtures/locks-many/yarn.lock file and found 1 package
+Loaded filter from: <rootdir>/fixtures/locks-many/osv-scanner.toml
+No issues found
+
+---
+
+[TestCommand_LockfileWithExplicitParseAs/absolute_paths_work_with_explicit_escaping - 2]
+
+---
+
 [TestCommand_LockfileWithExplicitParseAs/empty_is_default - 1]
 Scanned <rootdir>/fixtures/locks-many/composer.lock file and found 1 package
 Loaded filter from: <rootdir>/fixtures/locks-many/osv-scanner.toml
diff --git a/cmd/osv-scanner/scan/source/fixtures/locks-gitignore/subdir/.gitignore b/cmd/osv-scanner/scan/source/fixtures/locks-gitignore/subdir/test.gitignore
similarity index 100%
rename from cmd/osv-scanner/scan/source/fixtures/locks-gitignore/subdir/.gitignore
rename to cmd/osv-scanner/scan/source/fixtures/locks-gitignore/subdir/test.gitignore
diff --git a/cmd/osv-scanner/scan/source/fixtures/locks-gitignore/.gitignore b/cmd/osv-scanner/scan/source/fixtures/locks-gitignore/test.gitignore
similarity index 100%
rename from cmd/osv-scanner/scan/source/fixtures/locks-gitignore/.gitignore
rename to cmd/osv-scanner/scan/source/fixtures/locks-gitignore/test.gitignore
diff --git a/pkg/osvscanner/internal/scanners/lockfile.go b/pkg/osvscanner/internal/scanners/lockfile.go
index 3bdaf187..927d4e16 100644
--- a/pkg/osvscanner/internal/scanners/lockfile.go
+++ b/pkg/osvscanner/internal/scanners/lockfile.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"fmt"
 	"path/filepath"
+	"runtime"
 	"slices"
 	"strings"
 
@@ -162,7 +163,7 @@ func ScanSingleFileWithMapping(scanPath string, pluginsToUse []plugin.Plugin) ([
 }
 
 func parseLockfilePath(scanArg string) (string, string) {
-	if !strings.Contains(scanArg, ":") {
+	if (runtime.GOOS == "windows" && filepath.IsAbs(scanArg)) || !strings.Contains(scanArg, ":") {
 		scanArg = ":" + scanArg
 	}
 



Test patch:
diff --git a/cmd/osv-scanner/internal/testcmd/copy.go b/cmd/osv-scanner/internal/testcmd/copy.go
index f9da28fd..68f850e8 100644
--- a/cmd/osv-scanner/internal/testcmd/copy.go
+++ b/cmd/osv-scanner/internal/testcmd/copy.go
@@ -1,25 +1,24 @@
 package testcmd
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
 	"testing"
 )
 
-func CopyFileTo(t *testing.T, file, dir string) string {
-	t.Helper()
-	b, err := os.ReadFile(file)
+func copyFile(from, to string) (string, error) {
+	b, err := os.ReadFile(from)
 	if err != nil {
-		t.Fatalf("could not read test file: %v", err)
+		return "", fmt.Errorf("could not read test file: %w", err)
 	}
 
-	dst := filepath.Join(dir, filepath.Base(file))
-	if err := os.WriteFile(dst, b, 0600); err != nil {
-		t.Fatalf("could not copy test file: %v", err)
+	if err := os.WriteFile(to, b, 0600); err != nil {
+		return "", fmt.Errorf("could not copy test file: %w", err)
 	}
 
-	return dst
+	return to, nil
 }
 
 // CopyFileFlagTo creates a copy of the file pointed to by the given flag (if present
@@ -38,7 +37,11 @@ func CopyFileFlagTo(t *testing.T, tc Case, flagName string, dir string) string {
 		return ""
 	}
 
-	newPath := CopyFileTo(t, flagValue, dir)
+	newPath, err := copyFile(flagValue, filepath.Join(dir, filepath.Base(flagValue)))
+
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
 
 	for i := range tc.Args {
 		tc.Args[i] = strings.ReplaceAll(tc.Args[i], flagValue, newPath)
diff --git a/cmd/osv-scanner/internal/testcmd/git.go b/cmd/osv-scanner/internal/testcmd/git.go
new file mode 100644
index 00000000..c2daee59
--- /dev/null
+++ b/cmd/osv-scanner/internal/testcmd/git.go
@@ -0,0 +1,56 @@
+package testcmd
+
+import (
+	"io/fs"
+	"os"
+	"path/filepath"
+
+	"github.com/go-git/go-git/v5"
+)
+
+func SetupGitFixtures() (func(), error) {
+	// ensure a git repository doesn't already exist in the fixtures directory,
+	// in case we didn't get a chance to clean-up properly in the last run
+	os.RemoveAll("./fixtures/.git")
+
+	toRemove := []string{"./fixtures/.git"}
+
+	cleaner := func() {
+		for _, p := range toRemove {
+			os.RemoveAll(p)
+		}
+	}
+
+	// temporarily make the fixtures folder a git repository to prevent gitignore files messing with tests
+	_, err := git.PlainInit("./fixtures", false)
+	if err != nil {
+		return cleaner, err
+	}
+
+	var gitIgnoreFiles []string
+
+	// walk the fixtures to find all test .gitignore files that should be copied before tests run
+	err = filepath.Walk("./fixtures", func(path string, info fs.FileInfo, err error) error {
+		if err == nil && !info.IsDir() && filepath.Base(path) == "test.gitignore" {
+			gitIgnoreFiles = append(gitIgnoreFiles, path)
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		return cleaner, err
+	}
+
+	for _, f := range gitIgnoreFiles {
+		gitignoreFile, err := copyFile(f, filepath.Join(filepath.Dir(f), ".gitignore"))
+
+		if err != nil {
+			return cleaner, err
+		}
+
+		toRemove = append(toRemove, gitignoreFile)
+	}
+
+	return cleaner, nil
+}
diff --git a/cmd/osv-scanner/scan/source/command_test.go b/cmd/osv-scanner/scan/source/command_test.go
index 8f79ee07..388699a8 100644
--- a/cmd/osv-scanner/scan/source/command_test.go
+++ b/cmd/osv-scanner/scan/source/command_test.go
@@ -514,6 +514,8 @@ func TestCommand_CallAnalysis(t *testing.T) {
 func TestCommand_LockfileWithExplicitParseAs(t *testing.T) {
 	t.Parallel()
 
+	cwd := testutility.GetCurrentWorkingDirectory(t)
+
 	tests := []testcmd.Case{
 		{
 			Name: "unsupported parse-as",
@@ -633,6 +635,38 @@ func TestCommand_LockfileWithExplicitParseAs(t *testing.T) {
 			},
 			Exit: 0,
 		},
+		{
+			// if this isn't true, the test would fail along the lines of
+			// "could not determine extractor, requested D"
+			Name: "absolute_paths_are_automatically_escaped_on_windows",
+			Args: []string{
+				"",
+				"source",
+				"-L",
+				filepath.FromSlash(filepath.Join(cwd, "./fixtures/locks-many/yarn.lock")),
+			},
+			Exit: 0,
+		},
+		{
+			Name: "absolute_paths_work_with_explicit_escaping",
+			Args: []string{
+				"",
+				"source",
+				"-L",
+				":" + filepath.FromSlash(filepath.Join(cwd, "./fixtures/locks-many/yarn.lock")),
+			},
+			Exit: 0,
+		},
+		{
+			Name: "absolute_paths_can_have_explicit_parse_as",
+			Args: []string{
+				"",
+				"source",
+				"-L",
+				"package-lock.json:" + filepath.FromSlash(filepath.Join(cwd, "./fixtures/locks-many/yarn.lock")),
+			},
+			Exit: 127,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.Name, func(t *testing.T) {
diff --git a/cmd/osv-scanner/scan/source/testmain_test.go b/cmd/osv-scanner/scan/source/testmain_test.go
index 75f67e18..566606d3 100644
--- a/cmd/osv-scanner/scan/source/testmain_test.go
+++ b/cmd/osv-scanner/scan/source/testmain_test.go
@@ -2,10 +2,8 @@ package source_test
 
 import (
 	"log/slog"
-	"os"
 	"testing"
 
-	"github.com/go-git/go-git/v5"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/cmd"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/testcmd"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/scan/source"
@@ -14,13 +12,11 @@ import (
 )
 
 func TestMain(m *testing.M) {
-	// ensure a git repository doesn't already exist in the fixtures directory,
-	// in case we didn't get a chance to clean-up properly in the last run
-	os.RemoveAll("./fixtures/.git")
+	cleanupGitFixtures, err := testcmd.SetupGitFixtures()
 
-	// Temporarily make the fixtures folder a git repository to prevent gitignore files messing with tests.
-	_, err := git.PlainInit("./fixtures", false)
 	if err != nil {
+		cleanupGitFixtures()
+
 		panic(err)
 	}
 
@@ -28,7 +24,7 @@ func TestMain(m *testing.M) {
 	testcmd.CommandsUnderTest = []cmd.CommandBuilder{source.Command}
 	m.Run()
 
-	os.RemoveAll("./fixtures/.git")
+	cleanupGitFixtures()
 
 	testutility.CleanSnapshots(m)
 }
diff --git a/cmd/osv-scanner/scan/testmain_test.go b/cmd/osv-scanner/scan/testmain_test.go
index 9fdd9f42..9919dbe2 100644
--- a/cmd/osv-scanner/scan/testmain_test.go
+++ b/cmd/osv-scanner/scan/testmain_test.go
@@ -2,10 +2,8 @@ package scan_test
 
 import (
 	"log/slog"
-	"os"
 	"testing"
 
-	"github.com/go-git/go-git/v5"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/cmd"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/testcmd"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/scan"
@@ -14,13 +12,11 @@ import (
 )
 
 func TestMain(m *testing.M) {
-	// ensure a git repository doesn't already exist in the fixtures directory,
-	// in case we didn't get a chance to clean-up properly in the last run
-	os.RemoveAll("./fixtures/.git")
+	cleanupGitFixtures, err := testcmd.SetupGitFixtures()
 
-	// Temporarily make the fixtures folder a git repository to prevent gitignore files messing with tests.
-	_, err := git.PlainInit("./fixtures", false)
 	if err != nil {
+		cleanupGitFixtures()
+
 		panic(err)
 	}
 
@@ -28,7 +24,7 @@ func TestMain(m *testing.M) {
 	testcmd.CommandsUnderTest = []cmd.CommandBuilder{scan.Command}
 	m.Run()
 
-	os.RemoveAll("./fixtures/.git")
+	cleanupGitFixtures()
 
 	testutility.CleanSnapshots(m)
 }
diff --git a/cmd/osv-scanner/testmain_test.go b/cmd/osv-scanner/testmain_test.go
index ae29fc78..12b64645 100644
--- a/cmd/osv-scanner/testmain_test.go
+++ b/cmd/osv-scanner/testmain_test.go
@@ -2,10 +2,8 @@ package main
 
 import (
 	"log/slog"
-	"os"
 	"testing"
 
-	"github.com/go-git/go-git/v5"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/fix"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/cmd"
 	"github.com/google/osv-scanner/v2/cmd/osv-scanner/internal/testcmd"
@@ -16,13 +14,11 @@ import (
 )
 
 func TestMain(m *testing.M) {
-	// ensure a git repository doesn't already exist in the fixtures directory,
-	// in case we didn't get a chance to clean-up properly in the last run
-	os.RemoveAll("./fixtures/.git")
+	cleanupGitFixtures, err := testcmd.SetupGitFixtures()
 
-	// Temporarily make the fixtures folder a git repository to prevent gitignore files messing with tests.
-	_, err := git.PlainInit("./fixtures", false)
 	if err != nil {
+		cleanupGitFixtures()
+
 		panic(err)
 	}
 
@@ -34,7 +30,7 @@ func TestMain(m *testing.M) {
 	}
 	m.Run()
 
-	os.RemoveAll("./fixtures/.git")
+	cleanupGitFixtures()
 
 	testutility.CleanSnapshots(m)
 }