#190

feat(osv): dependency-CVE adapter — reachable dependency CVEs (Python + Java/Maven)

OpenNehanthwants to merge intofrom

What

Adds an osv-based dependency-CVE step to setup so known-vulnerable dependencies are surfaced, reasoned about for reachability, and turned into located findings — not just listed. Covers Python (pyproject ranges) and Java/Maven (pom.xml).

  1. Threat-model injection — run osv-scanner over dependencies before the threat model is written, persist osv-findings.json, and inject a "Known-vulnerable dependencies" section into the repository-analysis prompt so the agent judges reachability per advisory.
  2. Python floor-resolution — osv-scanner only reads lockfiles; a range-only pyproject.toml reports "no package sources". Resolve lowest+highest allowed versions with uv pip compile and hand osv the resolved lockfiles.
  3. Java/Maven SBOM — osv can't read a bare pom.xml; syft resolves the Maven project into a CycloneDX SBOM that osv then matches (reuses osv, not grype).
  4. CVE-steered matchers — matcher generation receives the threat model so a reachable dependency CVE gets a matcher and becomes a scan candidate.

Security & robustness hardening (per review)

  • uv sandboxed on repo-controlled manifests: --no-build, --no-sources, isolated cwd (no arbitrary code execution).
  • Sanitize package name/version before the prompt (prompt-injection defense).
  • Timeouts on uv, syft, and osv-scanner; probe each tool on PATH (missing tool reported distinctly, not as "no vulns").
  • Skip floor-resolution when a real lockfile exists (no false positives); resolve all manifests in a monorepo; parseOsv dedup.
  • Checkpoints: bump rubricVersion so existing projects adopt it; don't overwrite the CVE-enriched INFO.md; run the dep-CVE matcher pass once (not every resume).

Validation (end-to-end)

  • Python / FastAPI (range-only pyproject): 0 → 22 advisories floor-resolved; produced a located Starlette multipart-DoS finding the baseline never generates.
  • Java / WebGoat (xstream 1.4.5): syft→SBOM→osv surfaced 36 advisories; matcher steering generated xstream-fromxml-deserialization.
  • commons-cli/-collections, snakeyaml, nimbus-jose-jwt, commons-fileupload: SBOMs resolved, 0 CVEs (correct, deps are clean).

Degrades cleanly throughout: missing tool, no vulnerable deps, or no manifest → empty context, setup unchanged.

(The Java work was briefly opened as a stacked PR #191; folded into this PR instead — one combined adapter.)

🤖 Generated with Claude Code

Activity

29
vercel[bot]23d ago

@Nehanth is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

vercel-security-reviewer[bot]23d ago
auto-maintain[bot]23d ago

🤖 auto-maintain review

Automated, advisory triage for @Nehanth's PR. Facts below are read from the GitHub API.

CheckResult
Author's merged PRs (this repo)0 — ⚠️ first-time contributor
Account established✅ (age 3914d · 37 followers · 25 public repos)
Commits signed/verified⚠️ 0/13

Review panel: 🔴 high highest severity

deepsec maintainer code review: 🟡 medium

The enrichment pipeline can persist stale verdicts, silently miss advisories, and fail to recover after missing tooling; these correctness issues should be fixed before merge.

  • packages/deepsec/src/setup/coordinator.ts:592 — A successful clean OSV refresh preserves the previous reachability block because empty findings and scanner failure both produce no fresh block. After a dependency is patched, stale HIGH verdicts remain in INFO.md and can be promoted as false findings.
  • packages/deepsec/src/setup/repository-analysis.ts:128 — The required per-dependency reachability block is never validated. A syntactically valid analysis that omits or partially emits it is accepted, leaving matcher generation without the advisory details needed to produce candidates.
  • packages/deepsec/src/setup/coordinator.ts:553 — OSV failures still allow the info checkpoint to complete, while its digest records only the enabled flag. Installing a previously missing scanner/resolver and resuming unchanged source therefore skips enrichment permanently instead of retrying it.
  • packages/deepsec/src/setup/osv-enrich.ts:366 — Maven projects silently skip SBOM generation when syft is absent. OSV can then persist an empty result as if the project were clean, contrary to the promised distinct missing-tool reporting.

General code review: 🟡 medium

Dependency-CVE enrichment can retain obsolete reachability verdicts and silently discard current advisory context.

  • packages/deepsec/src/setup/coordinator.ts:592 — A successful clean OSV rescan produces no fresh reachability block, so this branch preserves the previous block. After a dependency is patched or removed, stale HIGH verdicts can continue forcing obsolete CVE findings; clean scans should remove the old block, while scanner failures need a distinct status.
  • packages/deepsec/src/setup/repository-analysis.ts:128 — Validation accepts analysis output that omits the required Dependency reachability subsection. Because the raw OSV context is not subsequently passed to matcher generation, one noncompliant agent response silently loses all advisory details despite persisted hits and leaves the CVE matcher pass unable to target them.

Scanner and matcher quality: 🔴 high

The Maven scan path is broken, and checkpoint/preservation behavior can both miss new advisories and retain patched ones as findings.

  • packages/deepsec/src/setup/osv-enrich.ts:442 — The generated CycloneDX file is appended as a positional source path rather than supplied through osv-scanner's SBOM input, so Maven components are not scanned and some versions reject the invocation entirely.
  • packages/deepsec/src/setup/coordinator.ts:592 — A successful clean OSV refresh produces no reachability block, but this branch preserves the previous block. After upgrading a vulnerable dependency, stale HIGH verdicts and generated candidates can still be promoted as false vulnerability findings.
  • packages/deepsec/src/setup/coordinator.ts:560 — OSV enrichment only runs when the repository-analysis checkpoint is stale. Rerunning setup with unchanged source never queries the updated advisory database, so newly disclosed CVEs remain undetected indefinitely.

Sandbox and credential boundaries: 🟡 medium

The opt-in osv step is well-bounded overall (off by default, timeouts, tool probes, sanitized prompt fields), but the uv SSRF guard misses manifest-controlled index configuration, the new subprocesses bypass the repo's established minimal-env credential boundary, and the resolved-version caveat is lost before the promote step — fix or consciously accept before merge.

  • packages/deepsec/src/setup/osv-enrich.ts:279 — The SSRF guard for `uv pip compile` is incomplete. `--no-sources` only neutralizes `[tool.uv.sources]`, and `DIRECT_REF` only matches PEP 508 `name @ <url>` forms — neither covers index/link configuration that uv reads from the same repository-controlled `pyproject.toml` (`[[tool.uv.index]]`, `[tool.uv.pip] index-url` / `extra-index-url` / `find-links`). Since no `--index-url`/`--no-index` is passed to pin the resolver, a malicious manifest can still steer the resolution fetch to an attacker-chosen or internal host, which is exactly the outbound-request class `manifestHasDirectRefs` was added to block.
  • packages/deepsec/src/setup/osv-enrich.ts:284 — `uv`, `syft`, `osv-scanner` and `which` are spawned with no `env` option, so they inherit the orchestrator's full `process.env` (GITHUB_TOKEN, AWS_, VERCEL_TOKEN, API_KEY). This contradicts the credential boundary the repo already enforces for untrusted-repo-facing subprocesses via `buildCodexEnv` (packages/processor/src/agents/codex-sdk.ts:268), and these are network-reaching tools whose endpoint can be influenced by the repository-controlled manifest and by inherited `UV`/`HTTP_PROXY` settings. uv also echoes any configured index URL (credentials included) into the compiled requirements file that is then copied to `data/<id>/setup/deps-.txt`.
  • packages/deepsec/src/setup/osv-enrich.ts:573 — The required reachability line format drops the `(resolved range version — not necessarily installed)` marker that `buildContext` emits one block earlier, so the hypothetical/confirmed distinction is lost once the verdict is written into INFO.md. The new processor prompt then tells the agent that a HIGH verdict means "reachability is already established upstream" and must be promoted without re-litigation (packages/processor/src/prompt/core.ts:76), so an advisory against a `lowest-direct`/`highest` version the project does not actually install is reported as a real finding.

Durability and distributed state: 🟡 medium

Two resumability issues can lose CVE matcher work or prevent bounded cancellation during enrichment.

  • packages/deepsec/src/setup/coordinator.ts:754 — The CVE pass digest is committed before its proposed matchers are persisted. A crash after this write but before writeGeneratedMatchers makes a resume skip the pass when coverage already passes, permanently losing the proposal.
  • packages/deepsec/src/setup/osv-enrich.ts:426 — OSV enrichment uses synchronous subprocesses without the workflow AbortSignal. These calls block the event loop, so cancellation and --max-duration cannot take effect until potentially several minutes of uv/syft/osv timeouts finish.

Posted by auto-maintain. This automated code review is advisory; a human maintainer makes the call.

vercel[bot]reviewed23d ago
vercel[bot]reviewed23d ago
vercel[bot]reviewed23d ago
vercel[bot]reviewed23d ago
vercel[bot]reviewed23d ago
vercel[bot]reviewed22d ago
vercel[bot]reviewed22d ago
vercel[bot]reviewed22d ago