You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
research: evaluate uv.lock (Python) support - separate product or in-scope? #672
Research whether uv.lock support belongs in CVE Lite CLI or warrants a separate product.
Background
uv is rapidly becoming the default Python package manager for modern Python projects. uv.lock is its lockfile format. A DINUM (French government) user running CVE Lite CLI across government digital service repositories requested Python lockfile scanning.
Questions to answer
Does adding Python/uv.lock support fit the CVE Lite CLI product identity (currently JS/TS focused) or does it dilute it?
Would a separate cve-lite-cli-python (or similar) be a better home?
What would the OSV query surface look like for Python packages vs npm packages?
Are there existing tools in the Python ecosystem this would compete with or complement (pip-audit, Safety)?
What is the effort to parse uv.lock vs the JS/TS lockfile formats already supported?
Motivation
Requested by a DINUM (French government) user. No implementation commitment yet - this issue is to scope the decision first.
If you would like to see Python/uv.lock support, please give this issue a 👍 - it helps us prioritize.
changed the title [-]feat: support uv.lock (Python/uv package manager)[/-][+]research: evaluate uv.lock (Python) support - separate product or in-scope?[/+]on Jun 15, 2026
Thanks for posting @sonukapoor . A unified CLI would be great as I'm frequently in the situation where we are dealing with polyglot Node / Python monorepos. Hope a polyglot CVE Lite CLI makes sense to others!
Thanks for the suggestion - a --scanner flag is a natural shape for multi-language support if we go that route.
This issue is currently in research/scoping mode: we're figuring out whether Python/uv.lock fits CVE Lite CLI's JS/TS identity or is better as a separate tool. If we do add Python support, a flag like --scanner=ts / --scanner=python (or auto-detect without a flag, as you suggest) would be worth prototyping. Appreciate the input.
The useful split is detect vs --fix. Those are not the same amount of work.
Detect
Detect is: read a lockfile, get {name, version}, ask OSV if that version is known-bad.
The scanner already does this.
PackageRef already has an ecosystem field
OSV already understands PyPI the same way it understands npm
Parsers already plug in through LockfileHandler in src/parsers/
A uv.lock / pylock.toml parser is another input file for the same machine. That is an addon. Same shape as adding bun.lock.
--fix is not that
Default cve-lite does not rewrite the tree. It prints copy-paste commands.
--fix only auto-applies direct deps that have a validated safe version, via npm install / pnpm add / yarn add, then rescans. Transitive findings stay “bump this parent yourself.”
That apply path is the expensive, JS-shaped part of the product.
Doing the same for Python means a new brain, not a new parser:
versions are PEP 440, not npm semver
you edit pyproject.toml + extras / groups / markers, not package.json
the commands are uv add / uv lock / uv sync
parent-aware upgrades do not map cleanly onto a universal uv.lock
override hygiene, cooldown, and the npm registry bulk API all need Python equivalents
Wrong fix commands are worse than no fix commands. So Python --fix is a second product in the same binary. Detect-only is not.
Product identity
Keep the tool JS/TS-first.
Auto-detect uv.lock / pylock.toml when present
Label it experimental
Do not advertise Python --fix
A --scanner=python flag is fine as an override
Do not use that flag as an excuse to rebuild remediations
A separate cve-lite-python package only makes sense if you commit to that second engine.
Do not scrape uv.lock as if it were stable
Astral treats uv.lock as uv-private and points other tools at uv workspace metadata (JSON).
PEP 751 pylock.toml is the public format, but it is a resolution output, not a full uv workspace graph.
Prefer metadata / pylock.toml. If you parse raw uv.lock, you own every format bump.
OSV
Same batch API, "ecosystem": "PyPI".
IDs will be PYSEC-… / GHSA-…
Offline mode needs the PyPI slice of OSV, not just npm
“In the lockfile” is not “installed on this runner” because of environment markers
Say which marker set you used, or Linux CI will page on Windows-only wheels.
Other tools
This gap got smaller after the issue was opened:
uv audit (June 2026) already does native OSV on a uv project
osv-scanner already reads uv.lock and pylock.toml
pip-audit is a good CI gate but does not read uv.lock (export first)
Dependabot already alerts on uv.lock
So the remaining gap is not “can anyone see PyPI CVEs.”
It is one local CLI over a Node + Python monorepo with one JSON / SARIF / SBOM contract. That is the DINUM request. Full Python remediations are a different bet.
Suggestion
Keep it in this repo.
Ship detect-only.
Order:
pylock.toml and/or uv workspace metadata
OSV PyPI on the existing client
same outputs / --fail-on
raw uv.lock only if you must, and pin the revision you accept
--fix, parent upgrades, override rules: out of scope until detect-only is used for real.
Docs: uv-only repo → uv audit. CVE Lite → polyglot gate, same output schema.
Success for DINUM: CI fails on known PyPI vulns from the committed lockfile. If they later want validated uv add commands, that is the other product.
Happy to sketch a LockfileHandler + fixtures if this scoped approach is ok.
This is the most useful thing anyone has put on this issue, so let me engage with it properly rather than just thanking you.
You are right about the plumbing, and I checked rather than took your word for it.PackageRef.ecosystem really is a free string, osv-advisory-source.ts:22 really does pass it straight through, and I confirmed live that a PyPI querybatch returns PYSEC and GHSA ids. LockfileHandler is a genuine compiler-checked table and a new entry touches about five places. The npm-registry supplemental pass is already correctly ecosystem-gated at scanner.ts:258.
The uv audit catch is the most valuable line in your comment and it changes the competitive picture more than the parser question does.
Where I think the argument breaks is "same shape as adding bun.lock". Bun is still the npm ecosystem, so every downstream assumption survives. PyPI breaks four of them, and the sharpest one lands in detect, not in --fix:
looksLikeVersion (utils/version.ts:1) requires a strict X.Y.Z, and findFirstFixedVersion (scanner.ts:1043) skips any fixed value that fails it. So:
numpy@1.21.0, GHSA-fpfv-jqm9-f5jm, declares fixed: "1.22". Two components. We would report "no fix available" for a vulnerability that numpy 1.22 fixes.
That is your own rule, wrong output is worse than no output, violated in the half you called cheap. It is not an edge case either: four of the thirty most-downloaded PyPI packages currently ship a version that check rejects (python-dateutil@2.9.0.post0, idna@3.20, packaging@26.3, pytz@2026.3.post1).
Three more, all verified against the code:
purl.ts:9 hardcodes pkg:npm/, and it feeds both SPDX and CycloneDX. Python packages would be emitted as pkg:npm/requests@2.19.1. On a project that talks about the Cyber Resilience Act, that is a wrong compliance artifact rather than a cosmetic bug.
pylock.toml, the input you recommend, is flat, and readDirectDependencyNames only reads package.json. So classifyRelationship falls back to path length, every finding comes out "unknown", and fix-commands.ts:237-245 routes every "unknown" finding into skipped. Detect-only via pylock.toml gives a findings list where every row has no command and a confusing reason.
And findRootLockfile picks exactly one lockfile per root, so a polyglot repo root scans one and lists the other as skipped. Your headline use case needs the two ecosystems in separate subfolders.
So the seam is not detect versus fix. It is "does this ecosystem's version algebra match semver." Detect here is four steps, not one: OSV query, version reasoning, relationship classification, output. Only the first is ecosystem-neutral today.
And the strategic question your comment does not ask. osv-scanner already reads uv.lock and pylock.toml and emits SARIF, so "one CLI over a polyglot repo with one SARIF contract" is already available. Our differentiator is remediation quality. Detect-only walks into the segment where we are weakest against an incumbent, and drops the axis where we win. What does it win us that we are not already losing?
Two things came out of reading this that are worth fixing regardless of whether Python ever happens, both latent on main today, and I have filed them: #1192 for the unparseable-fix-version case above, and #1191 for deriveAdvisoryPackageRows storing GIT-range commit hashes as versions, where compareVersions("2.32.4", "74ea7cf...") returns -1 so a patched version can be flagged vulnerable. Your comment found both. Thank you for that.
I am not taking the LockfileHandler sketch, and I want to be straight about why rather than leave it hanging: the handler is the easy five percent, and merging it would imply a scope decision this issue exists to make. If anything here is worth prototyping it is a PEP 440 version module behind the existing compareVersions and looksLikeVersion seam, which is the actual gate and is testable on its own without committing the project to a second ecosystem.
Leaving this open as research. You have moved it forward considerably.
I've had a while to think about this, use the tool on prod and gain context.
Real-world use case
I run a production site with an Astro frontend (package-lock.json) and a Python backend and crawler (pinned requirements.txt, no uv.lock or pylock.toml). The frontend gates CI with cve-lite-cli (fail-on: high, SARIF, a committed baseline). The backend gates with pip-audit. Both run on every PR and weekly.
What that taught me:
Every npm high since the first scan came in transitively (through astro or tailwind). The parent-aware commands (npm update astro, npm update source-map-js) were the right fix every time. That is the feature I'd miss most.
pip-audit finds things, but gives no parent path and no baseline, so the gate can't ratchet on new findings only.
A mixed repo needs two tools, two CI jobs and two reports. One baseline and one report across npm and Python would remove most of that duplication.
Lockfile-only Python support would not cover this repo. A plain pinned requirements.txt matters.
So to the "what does it win us?" question: for polyglot repos, a single gate with ratcheting and fix guidance across both ecosystems. Detect-only parity with osv-scanner is not the win.
--fix should be the goal, not detect-only
I've changed my mind since my last comment. --fix is the best feature of this tool, and Python support should aim for it. The question is order. A safe Python fix needs foundations first:
Versions: a PEP 440 module behind the existing compareVersions / looksLikeVersion seam (epochs, .post/.dev, two-part versions like numpy's 1.22). This is the prototype you invited, and it's needed to pick a correct fix version at all.
purl: build purls per ecosystem (pkg:pypi/ with PEP 503 normalised names) instead of hardcoding pkg:npm/.
Multi-lockfile roots: let one root hold both package-lock.json and a Python manifest.
Direct vs transitive: read pyproject.toml dependencies, so findings aren't all classified unknown and skipped.
Inputs: pinned requirements.txt, pylock.toml / uv export, and later uv.lock (which is uv-private and moves).
Offline DB: sync the PyPI slice of OSV.
--fix for uv:uv lock --upgrade-package / uv add pkg>=X, with parent upgrades where the graph allows.
--fix for pinned requirements.txt: edit the pin, and use a constraint for transitive deps with no parent upgrade.
Docs: mark Python experimental until fix support lands.
Steps 1 to 4 are worth having even if Python stalls, because they make the core ecosystem-neutral. Reachability for Python (import names vs distribution names, e.g. PIL vs pillow) I'd leave out of scope.
Happy to start with the PEP 440 module PR if that works for you.
Yes. Let us do this, experimental, and judge it from the outcome rather than from more analysis on this issue. Enough people have asked for Python now, and you are the first to offer a plan I agree with.
A correction to what I argued in September, because it was my strongest objection and it no longer holds. I said an advisory declaring its fix as 1.22 would be read as no fix at all, and I used numpy to make the point. That got filed and fixed days later, so advisory boundaries written with a missing component are normalised before they are compared. Of the four packages I named then, idna@3.20 and packaging@26.3 parse fine today. The two that still fail are python-dateutil@2.9.0.post0 and pytz@2026.3.post1, and the forms that still fail are post and dev releases, epochs, and a release candidate written without a separator.
So your step 1 is still the gate, just a narrower one than I described, and what is left is PEP 440 and nothing else. That makes it a better first piece of work than it was a month ago.
Your own repo reframes this issue more than the rest of the thread does. It was filed about uv.lock, and you are describing pinned requirement files with no lockfile at all. That is a different input and probably the more common one. If we only ever supported the format in the title we would miss the case you have just described.
What experimental has to mean here
The word does not exist anywhere in the tool today, so I would rather define it now than discover we disagree about it over a PR. Three rules, and underneath they are all one sentence: an experimental ecosystem must never be able to report clean when it means it could not look.
1. Python findings print, but they do not touch the exit code yet. You already know why, because you found the npm side of this yourself: pnpm 8 lockfiles parsed to zero packages, so those projects scanned clean under --fail-on. The general case is still open as #1344, where a scan that finds nothing to parse exits 0 even under the gate. A new ecosystem will meet formats we do not handle, and until that is fixed, wiring Python into the exit code means shipping a silent false negative on purpose.
2. Python stays out of SPDX and CycloneDX until identifiers are built per ecosystem. Right now that builder hardcodes npm and does not accept an ecosystem at all, so a Python package would be published as an npm package in both formats. A wrong identifier inside a compliance artefact is worse than not emitting one, which is your step 2 and is why I want it early.
3. Offline plus Python refuses rather than reporting clean. The local advisory database is ecosystem-aware in its structure, which is the good news, but the sync only downloads the npm slice of OSV.
How it switches on: an explicit flag to begin with, not auto-detection. @pfarikrispy suggested a scanner switch on this issue back in June and that is the right shape for now, because auto-detection would change output for every existing adopter who happens to have a Python file in their tree. Auto-detect is where this should end up, once it has earned it.
Graduation
That is the outcome I want to judge from, so here are the conditions rather than a vague "later": #1344 fixed, identifiers built per ecosystem, and the parsers exercised against real trees rather than fixtures. At that point Python joins the gate and the label comes off. Your repo is the obvious first real tree, and I would rather have your numbers than my predictions.
Scope
Yes to your steps 1 and 2 now, as two separate PRs rather than one. Both are narrow, both have an exact success criterion, and both are worth having even if Python stalls, which is your argument and it is the right one.
Steps 3 and 4 I want to look at after those land, because reading declared dependencies changes how every finding gets classified, not just Python ones.
The fix side, your steps 7 and 8, I am not committing to yet, and I would rather tell you that now than let you build toward a maybe. That is where a wrong recommendation does real damage, and it is the part of the tool I keep in house. If the first half goes well, that conversation gets much easier.
One process note, so nothing goes wrong by accident: this stays an open issue rather than becoming an assignment, because first working PR wins here and I am not going to break that rule for anyone. But say here when you start on the version work and I will watch for collisions.
Two of your other PRs are approved and in the queue. I merge them one at a time rather than in a batch, so they will land shortly.
Steps 1 and 2 are in. #1397 reads and orders PEP 440 versions, #1398 builds package URLs from the ecosystem a package actually belongs to, and #1434 added the manual check against packaging. Thanks @alex-powell, building them as requirements rather than a bolt-on made both easy to review.
So steps 3 and 4 are open now: reading a Python project, starting with pinned requirements.txt since that's the real use case here, behind an explicit flag. The experimental rules from my earlier comment still hold for that work: findings print but don't touch the exit code, Python stays out of SPDX and CycloneDX, and offline mode refuses rather than reporting clean.
Two things sit next to it. #1344 (a scan that finds nothing to parse exits 0 under --fail-on) is open for anyone and is the main thing between experimental and graduating. #1516, checking a PyPI upgrade target against the package's other advisories, is the fix side, and I'm keeping that one in-house.
@sonukapoor fix for step 3,4 is open at #1546 . besides this, on issue #1344 , I could still see an existing pr #1353 still in progress but if u need help with it do let me know so I could raise a pr
Activity
[-]feat: support uv.lock (Python/uv package manager)[/-][+]research: evaluate uv.lock (Python) support - separate product or in-scope?[/+]kaaloo commentedon Jun 15, 2026
Thanks for posting @sonukapoor . A unified CLI would be great as I'm frequently in the situation where we are dealing with polyglot Node / Python monorepos. Hope a polyglot CVE Lite CLI makes sense to others!
pfarikrispy commentedon Jun 22, 2026
Just my 2 cents...
Much like
checkovsupports scanning for several frameworks, perhaps you can "wrap" the scanner type behind a switch:--scanner=python--scanner=tswithout a switch present, probe for the resp. lock files of each language.
sonukapoor commentedon Jun 23, 2026
Thanks for the suggestion - a
--scannerflag is a natural shape for multi-language support if we go that route.This issue is currently in research/scoping mode: we're figuring out whether Python/uv.lock fits CVE Lite CLI's JS/TS identity or is better as a separate tool. If we do add Python support, a flag like
--scanner=ts/--scanner=python(or auto-detect without a flag, as you suggest) would be worth prototyping. Appreciate the input.alex-powell commentedon Sep 20, 2026
Detect is an addon.
--fixis a second product.The useful split is detect vs
--fix. Those are not the same amount of work.Detect
Detect is: read a lockfile, get
{name, version}, ask OSV if that version is known-bad.The scanner already does this.
PackageRefalready has anecosystemfieldPyPIthe same way it understandsnpmLockfileHandlerinsrc/parsers/A
uv.lock/pylock.tomlparser is another input file for the same machine. That is an addon. Same shape as addingbun.lock.--fixis not thatDefault
cve-litedoes not rewrite the tree. It prints copy-paste commands.--fixonly auto-applies direct deps that have a validated safe version, vianpm install/pnpm add/yarn add, then rescans. Transitive findings stay “bump this parent yourself.”That apply path is the expensive, JS-shaped part of the product.
Doing the same for Python means a new brain, not a new parser:
pyproject.toml+ extras / groups / markers, notpackage.jsonuv add/uv lock/uv syncuv.lockWrong fix commands are worse than no fix commands. So Python
--fixis a second product in the same binary. Detect-only is not.Product identity
Keep the tool JS/TS-first.
uv.lock/pylock.tomlwhen present--fix--scanner=pythonflag is fine as an overrideA separate
cve-lite-pythonpackage only makes sense if you commit to that second engine.Do not scrape
uv.lockas if it were stableAstral treats
uv.lockas uv-private and points other tools atuv workspace metadata(JSON).PEP 751
pylock.tomlis the public format, but it is a resolution output, not a full uv workspace graph.Prefer metadata /
pylock.toml. If you parse rawuv.lock, you own every format bump.OSV
Same batch API,
"ecosystem": "PyPI".PYSEC-…/GHSA-…Say which marker set you used, or Linux CI will page on Windows-only wheels.
Other tools
This gap got smaller after the issue was opened:
uv audit(June 2026) already does native OSV on a uv projectuv.lockandpylock.tomluv.lock(export first)uv.lockSo the remaining gap is not “can anyone see PyPI CVEs.”
It is one local CLI over a Node + Python monorepo with one JSON / SARIF / SBOM contract. That is the DINUM request. Full Python remediations are a different bet.
Suggestion
pylock.tomland/oruv workspace metadataPyPIon the existing client--fail-onuv.lockonly if you must, and pin the revision you accept--fix, parent upgrades, override rules: out of scope until detect-only is used for real.uv audit. CVE Lite → polyglot gate, same output schema.uv addcommands, that is the other product.Happy to sketch a
LockfileHandler+ fixtures if this scoped approach is ok.sonukapoor commentedon Sep 20, 2026
This is the most useful thing anyone has put on this issue, so let me engage with it properly rather than just thanking you.
You are right about the plumbing, and I checked rather than took your word for it.
PackageRef.ecosystemreally is a free string,osv-advisory-source.ts:22really does pass it straight through, and I confirmed live that aPyPIquerybatch returns PYSEC and GHSA ids.LockfileHandleris a genuine compiler-checked table and a new entry touches about five places. The npm-registry supplemental pass is already correctly ecosystem-gated atscanner.ts:258.The
uv auditcatch is the most valuable line in your comment and it changes the competitive picture more than the parser question does.Where I think the argument breaks is "same shape as adding
bun.lock". Bun is still the npm ecosystem, so every downstream assumption survives. PyPI breaks four of them, and the sharpest one lands in detect, not in--fix:looksLikeVersion(utils/version.ts:1) requires a strictX.Y.Z, andfindFirstFixedVersion(scanner.ts:1043) skips anyfixedvalue that fails it. So:That is your own rule, wrong output is worse than no output, violated in the half you called cheap. It is not an edge case either: four of the thirty most-downloaded PyPI packages currently ship a version that check rejects (
python-dateutil@2.9.0.post0,idna@3.20,packaging@26.3,pytz@2026.3.post1).Three more, all verified against the code:
purl.ts:9hardcodespkg:npm/, and it feeds both SPDX and CycloneDX. Python packages would be emitted aspkg:npm/requests@2.19.1. On a project that talks about the Cyber Resilience Act, that is a wrong compliance artifact rather than a cosmetic bug.pylock.toml, the input you recommend, is flat, andreadDirectDependencyNamesonly readspackage.json. SoclassifyRelationshipfalls back to path length, every finding comes out"unknown", andfix-commands.ts:237-245routes every"unknown"finding intoskipped. Detect-only via pylock.toml gives a findings list where every row has no command and a confusing reason.And
findRootLockfilepicks exactly one lockfile per root, so a polyglot repo root scans one and lists the other as skipped. Your headline use case needs the two ecosystems in separate subfolders.So the seam is not detect versus fix. It is "does this ecosystem's version algebra match semver." Detect here is four steps, not one: OSV query, version reasoning, relationship classification, output. Only the first is ecosystem-neutral today.
And the strategic question your comment does not ask. osv-scanner already reads uv.lock and pylock.toml and emits SARIF, so "one CLI over a polyglot repo with one SARIF contract" is already available. Our differentiator is remediation quality. Detect-only walks into the segment where we are weakest against an incumbent, and drops the axis where we win. What does it win us that we are not already losing?
Two things came out of reading this that are worth fixing regardless of whether Python ever happens, both latent on
maintoday, and I have filed them: #1192 for the unparseable-fix-version case above, and #1191 forderiveAdvisoryPackageRowsstoring GIT-range commit hashes as versions, wherecompareVersions("2.32.4", "74ea7cf...")returns -1 so a patched version can be flagged vulnerable. Your comment found both. Thank you for that.I am not taking the
LockfileHandlersketch, and I want to be straight about why rather than leave it hanging: the handler is the easy five percent, and merging it would imply a scope decision this issue exists to make. If anything here is worth prototyping it is a PEP 440 version module behind the existingcompareVersionsandlooksLikeVersionseam, which is the actual gate and is testable on its own without committing the project to a second ecosystem.Leaving this open as research. You have moved it forward considerably.
fix(scanner): read advisory fix versions that omit components (#1219)
fix(offline-db): drop GIT ranges instead of storing commit hashes as …
fix(offline-db): drop GIT ranges instead of storing commit hashes as …
alex-powell commentedon Oct 24, 2026
I've had a while to think about this, use the tool on prod and gain context.
Real-world use case
I run a production site with an Astro frontend (
package-lock.json) and a Python backend and crawler (pinnedrequirements.txt, nouv.lockorpylock.toml). The frontend gates CI withcve-lite-cli(fail-on: high, SARIF, a committed baseline). The backend gates with pip-audit. Both run on every PR and weekly.What that taught me:
npm update astro,npm update source-map-js) were the right fix every time. That is the feature I'd miss most.requirements.txtmatters.So to the "what does it win us?" question: for polyglot repos, a single gate with ratcheting and fix guidance across both ecosystems. Detect-only parity with osv-scanner is not the win.
--fixshould be the goal, not detect-onlyI've changed my mind since my last comment.
--fixis the best feature of this tool, and Python support should aim for it. The question is order. A safe Python fix needs foundations first:compareVersions/looksLikeVersionseam (epochs,.post/.dev, two-part versions like numpy's1.22). This is the prototype you invited, and it's needed to pick a correct fix version at all.pkg:pypi/with PEP 503 normalised names) instead of hardcodingpkg:npm/.package-lock.jsonand a Python manifest.pyproject.tomldependencies, so findings aren't all classifiedunknownand skipped.requirements.txt,pylock.toml/uv export, and lateruv.lock(which is uv-private and moves).--fixfor uv:uv lock --upgrade-package/uv add pkg>=X, with parent upgrades where the graph allows.--fixfor pinnedrequirements.txt: edit the pin, and use a constraint for transitive deps with no parent upgrade.Steps 1 to 4 are worth having even if Python stalls, because they make the core ecosystem-neutral. Reachability for Python (import names vs distribution names, e.g.
PILvspillow) I'd leave out of scope.Happy to start with the PEP 440 module PR if that works for you.
sonukapoor commentedon Oct 24, 2026
Yes. Let us do this, experimental, and judge it from the outcome rather than from more analysis on this issue. Enough people have asked for Python now, and you are the first to offer a plan I agree with.
A correction to what I argued in September, because it was my strongest objection and it no longer holds. I said an advisory declaring its fix as
1.22would be read as no fix at all, and I used numpy to make the point. That got filed and fixed days later, so advisory boundaries written with a missing component are normalised before they are compared. Of the four packages I named then,idna@3.20andpackaging@26.3parse fine today. The two that still fail arepython-dateutil@2.9.0.post0andpytz@2026.3.post1, and the forms that still fail are post and dev releases, epochs, and a release candidate written without a separator.So your step 1 is still the gate, just a narrower one than I described, and what is left is PEP 440 and nothing else. That makes it a better first piece of work than it was a month ago.
Your own repo reframes this issue more than the rest of the thread does. It was filed about
uv.lock, and you are describing pinned requirement files with no lockfile at all. That is a different input and probably the more common one. If we only ever supported the format in the title we would miss the case you have just described.What experimental has to mean here
The word does not exist anywhere in the tool today, so I would rather define it now than discover we disagree about it over a PR. Three rules, and underneath they are all one sentence: an experimental ecosystem must never be able to report clean when it means it could not look.
1. Python findings print, but they do not touch the exit code yet. You already know why, because you found the npm side of this yourself: pnpm 8 lockfiles parsed to zero packages, so those projects scanned clean under
--fail-on. The general case is still open as #1344, where a scan that finds nothing to parse exits 0 even under the gate. A new ecosystem will meet formats we do not handle, and until that is fixed, wiring Python into the exit code means shipping a silent false negative on purpose.2. Python stays out of SPDX and CycloneDX until identifiers are built per ecosystem. Right now that builder hardcodes npm and does not accept an ecosystem at all, so a Python package would be published as an npm package in both formats. A wrong identifier inside a compliance artefact is worse than not emitting one, which is your step 2 and is why I want it early.
3. Offline plus Python refuses rather than reporting clean. The local advisory database is ecosystem-aware in its structure, which is the good news, but the sync only downloads the npm slice of OSV.
How it switches on: an explicit flag to begin with, not auto-detection. @pfarikrispy suggested a scanner switch on this issue back in June and that is the right shape for now, because auto-detection would change output for every existing adopter who happens to have a Python file in their tree. Auto-detect is where this should end up, once it has earned it.
Graduation
That is the outcome I want to judge from, so here are the conditions rather than a vague "later": #1344 fixed, identifiers built per ecosystem, and the parsers exercised against real trees rather than fixtures. At that point Python joins the gate and the label comes off. Your repo is the obvious first real tree, and I would rather have your numbers than my predictions.
Scope
Yes to your steps 1 and 2 now, as two separate PRs rather than one. Both are narrow, both have an exact success criterion, and both are worth having even if Python stalls, which is your argument and it is the right one.
Steps 3 and 4 I want to look at after those land, because reading declared dependencies changes how every finding gets classified, not just Python ones.
The fix side, your steps 7 and 8, I am not committing to yet, and I would rather tell you that now than let you build toward a maybe. That is where a wrong recommendation does real damage, and it is the part of the tool I keep in house. If the first half goes well, that conversation gets much easier.
One process note, so nothing goes wrong by accident: this stays an open issue rather than becoming an assignment, because first working PR wins here and I am not going to break that rule for anyone. But say here when you start on the version work and I will watch for collisions.
Two of your other PRs are approved and in the queue. I merge them one at a time rather than in a batch, so they will land shortly.
alex-powell commentedon Oct 24, 2026
Cheers, working on steps 1&2 now. I'll try to make it as a requirement from day 1, rather than some messy bolt-on
sonukapoor commentedon Nov 6, 2026
Steps 1 and 2 are in. #1397 reads and orders PEP 440 versions, #1398 builds package URLs from the ecosystem a package actually belongs to, and #1434 added the manual check against
packaging. Thanks @alex-powell, building them as requirements rather than a bolt-on made both easy to review.So steps 3 and 4 are open now: reading a Python project, starting with pinned
requirements.txtsince that's the real use case here, behind an explicit flag. The experimental rules from my earlier comment still hold for that work: findings print but don't touch the exit code, Python stays out of SPDX and CycloneDX, and offline mode refuses rather than reporting clean.Two things sit next to it. #1344 (a scan that finds nothing to parse exits 0 under
--fail-on) is open for anyone and is the main thing between experimental and graduating. #1516, checking a PyPI upgrade target against the package's other advisories, is the fix side, and I'm keeping that one in-house.bhushan4work commentedon Nov 6, 2026
I'd like to pick the remaining parts , will raise a pr as intended
bhushan4work commentedon Nov 6, 2026
@sonukapoor fix for step 3,4 is open at #1546 . besides this, on issue #1344 , I could still see an existing pr #1353 still in progress but if u need help with it do let me know so I could raise a pr
feat: scan pinned requirements.txt behind --python flag (OWASP#672)
feat: scan pinned requirements.txt behind --python flag (OWASP#672)
feat: scan pinned requirements.txt behind --python flag (refs OWASP#672)
feat: scan pinned requirements.txt behind --python flag (refs OWASP#672)
feat: scan pinned requirements.txt behind --python flag (refs OWASP#672)
feat: scan pinned requirements.txt behind --python flag (refs OWASP#672)
fix: isolate python scan test cache, normalize package names, fix rat…
fix: support pip whitespace and compact included files and prevent np…