fix(scanner): match CVEs against resolved lockfile version, not manifest floor#79
Merged
Merged
Conversation
…est floor Eliminates false positives in the OSV CVE scanner that occurred when a repo's package-lock.json had already resolved a dependency to a patched version that the manifest's declared range floor still fell inside. Previously, collectDeps stripped the range operator from the declared spec (e.g. ^10.3.0 → 10.3.0) and sent that floor to OSV; now it fetches and parses the co-located and root package-lock.json files, extracts exact resolved versions (lockfileVersion 1/2/3), and sends those to OSV instead. Falls back to the floor-strip when no lockfile is present or the package is not listed, so repos without lockfiles regress nothing. New pure exports in lib/manifest-discovery.ts: - discoverLockfilePaths(manifestPaths): string[] — computes which package-lock.json paths to probe (root + co-located siblings) - parseNpmLockfileContentsList(contents): Map<string,string> — parses raw lockfile JSON (v1/v2/v3), extracts packageName → resolvedVersion, keeps the lowest version across entries (security-conservative) fetchNpmLockfileResolutions is the thin async wrapper that fetches via fetchManifestContents and delegates to parseNpmLockfileContentsList. The Dependabot path (github-advisories.ts) is unaffected. Co-Authored-By: Lan Nguyen Si <raylsreturn@googlemail.com>
…se, residual docs) - collectDeps wraps fetchNpmLockfileResolutions in .catch(() => Map) so a resolver failure degrades to the manifest floor instead of aborting the whole npm scan and hiding every advisory (mutation-verified test). - parseNpmLockfileContentsList takes the name after the LAST node_modules segment (split, not greedy regex) so deeply-nested/scoped keys resolve; skips workspace self-entries. - Document the cross-workspace partial-coverage residual (full per-workspace provenance is task cac1b6fb). - Add regression tests: deep-nested key, workspace self-entry, fail-safe degrade. Co-Authored-By: Lan Nguyen Si <raylsreturn@googlemail.com>
LanNguyenSi
added a commit
that referenced
this pull request
Jun 24, 2026
…project floor (#80) * fix(scanner): resolve Python CVEs against uv.lock/poetry.lock, not pyproject 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> * fix(scanner): bound Python lockfile parse to the main [[package]] table 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 --------- Co-authored-by: Lan Nguyen Si <nguyen-si@publicplan.de> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes the root cause of depsight's CVE false-positive flood: the OSV path sent each npm dependency's declared-range FLOOR (e.g.
^3.27.0becomes3.27.0) to OSV instead of the RESOLVED lockfile version, so already-patched repos were flagged as vulnerable.Why it matters
An org-wide sweep on 2026-06-21 had depsight reporting 273 CVEs (incl. 1 "CRITICAL") across 23 non-archived repos; cross-checking the resolved lockfiles with
npm auditshowed nearly all already fixed. Concrete: agent-dx declaressimple-git ^3.27.0and was flagged CRITICAL, but the lockfile pins 3.36.0 (safe). The Dependabot path was already correct (GitHub matches lockfile-side); only the OSV path used the floor.The fix
lib/manifest-discovery.ts: newdiscoverLockfilePaths,parseNpmLockfileContentsList,fetchNpmLockfileResolutions. They fetch the co-located and rootpackage-lock.json(lockfileVersion 1/2/3) and mappackageNameto its resolved version, keeping the lowest across entries (security-conservative).lib/cve/osv.tscollectDeps: query OSV with the resolved version; fall back to the manifest floor when no lockfile or the package is absent. Resolver failures are caught and degrade to the floor, never aborting the scan.Review findings addressed
split, not a greedy regex).Verification
Display follow-up (extractVulnRangeInfo shows ranges[0]) is task 6db0ae34.
Refs: CVE sweep 2026-06-21; task a7a4ab3f