Skip to content

fix(scanner): resolve Python CVEs against uv.lock/poetry.lock, not pyproject floor#80

Merged
LanNguyenSi merged 2 commits into
masterfrom
fix/cve-scanner-python-lockfile-resolution
Jun 24, 2026
Merged

fix(scanner): resolve Python CVEs against uv.lock/poetry.lock, not pyproject floor#80
LanNguyenSi merged 2 commits into
masterfrom
fix/cve-scanner-python-lockfile-resolution

Conversation

@LanNguyenSi

Copy link
Copy Markdown
Owner

What

Extend depsight's lockfile-resolved CVE matching (shipped for npm in #79) to Python. The scanner now queries OSV with the version resolved in uv.lock / poetry.lock instead of the declared pyproject.toml range floor, and falls back to the floor when no lockfile lists the dependency.

Why

For direct Python deps depsight sent the declared semver floor to OSV (e.g. jinja2>=3.1 queried 3.1), so any advisory above the floor but at or below the resolved version showed up as a false positive. scaffoldkit reported 6 such CVEs (jinja2 x5, pydantic x1) while its uv.lock resolves jinja2 3.1.6 and pydantic 2.13.4, both at or above every applicable fix. This is the Python tail of the npm fix in #79.

How

  • lib/manifests/python.ts: normalizePythonPackageName (PEP 503 canonical form), discoverPythonLockfilePaths (root plus co-located lockfiles), parsePythonLockfileContents (one [[package]] TOML state machine covering both uv.lock and poetry.lock, lowest-version-wins, bounded to the main table so sub-tables cannot corrupt it), and fetchPythonLockfileResolutions.
  • lib/cve/osv.ts: the python branch of collectDeps parallel-fetches the lockfile resolutions with a .catch(() => new Map()) fail-safe (a lockfile error degrades to the floor, never aborts the scan) and uses the resolved version or the floor per dep, keyed on the canonical name.

No new runtime dependency: parsing is regex line-by-line, matching the existing convention (rust.ts). No schema migration.

Tests

  • tests/unit/python-lockfile.test.ts (new): pure-parser unit tests for PEP 503 normalization, lowest-version-wins, partial blocks, plus two negative controls (poetry.lock sub-table keys named name/version do not corrupt the block; uv.lock inline-table dependency arrays do not register as standalone packages).
  • tests/unit/osv-lockfile.test.ts: a python block mirroring the npm scenarios, including a resolved-vulnerable negative control that keeps a genuinely vulnerable resolved version flagged.
  • tsc --noEmit clean, lint clean, 250/250 tests pass.

Scope

Python only. yarn.lock / pnpm-lock.yaml are carved out to follow-up 3d20798a (no repo in the corpus uses them; pnpm would need a YAML parser decision).

Verification after deploy

Rescan scaffoldkit: expect 6 to 0. Negative control: repos whose lockfile resolves a genuinely vulnerable version stay flagged.

Refs: agent-tasks 2e68c0ab

nguyen-si-pp and others added 2 commits June 24, 2026 09:19
…project floor

depsight queried OSV with the declared pyproject range floor for direct Python
deps (e.g. jinja2>=3.1 resolves to the 3.1 floor), producing false positives
when the lockfile actually resolves a safe version. Mirror the npm
lockfile-resolution pattern (PR #79) for Python: parse uv.lock and poetry.lock
[[package]] blocks, query OSV with the resolved version, fall back to the
manifest floor when no lockfile lists the dep. PEP 503 name normalization is
applied on both sides of the lookup so my_package and my-package match.

Clears scaffoldkit's 6 floor false positives (jinja2, pydantic). The negative
control keeps genuinely vulnerable resolved versions flagged.

Refs: agent-tasks 2e68c0ab

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review finding: parsePythonLockfileContents used last-match-wins and stayed in
the block across all sub-tables. poetry.lock writes sub-tables after the main
table, so a [package.dependencies] constraint keyed literally `name` or
`version` (e.g. `version = ">=2.0"`) overwrote the captured block, queried OSV
with a garbage version, and silently hid the package's real CVEs. Switch to
first-match-wins (the main table lists name/version before any sub-table).
uv.lock inline-table arrays were already safe via the `^` anchor.

Adds two negative-control tests: poetry.lock sub-table keys named name/version
must not corrupt the block, and uv.lock inline-table dependency entries must
not register as standalone packages.

Refs: agent-tasks 2e68c0ab
@LanNguyenSi LanNguyenSi merged commit a7776f4 into master Jun 24, 2026
4 checks passed
@LanNguyenSi LanNguyenSi mentioned this pull request Jun 25, 2026
3 tasks
LanNguyenSi added a commit that referenced this pull request Jun 25, 2026
Bump version 0.5.0 -> 0.5.1 and add the CHANGELOG entry for the changes since
v0.5.0: a scanner-correctness pass that matches CVEs against the resolved
lockfile version instead of the manifest floor for npm (#79) and Python
(uv.lock / poetry.lock, #80), plus a dashboard hero screenshot in the README
(#81).

Refs: depsight v0.5.1 scanner-correctness pass

Co-authored-by: Lan Nguyen Si <nguyen-si@publicplan.de>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants