Repository navigation
Lockfiles & transitive dependencies + v1.0.0 polish - #4
Merged
Merged
Conversation
Extend the ParsedProject contract so a lockfile's resolved tree rides alongside the manifest-declared dependencies, leaving the no-lockfile case byte-for-byte unchanged: - model: Dependency gains resolved (the exact version a lockfile pinned; None keeps the declared-constraint behavior) and direct (false for a lockfile-only transitive). ParsedProject gains dependency_edges, the flattened package graph, mirroring import_edges. Add direct/transitive constructors and route the manifest parsers through direct(). - lockfiles.rs: parse package-lock.json (v2/v3 packages + v1 nested tree), yarn.lock (classic v1; Berry skipped gracefully), poetry.lock, Cargo.lock, and the flat Pipfile.lock (versions, no tree). resolve() merges them per ecosystem, stamps each direct with its exact resolved version, appends the lockfile-only packages as transitives, and returns the edges. Malformed lockfiles warn and are skipped, never fatal. - examples/vulnerable-transitive-app: a fixture whose real risk is a reachable transitive lodash (via data-utils) with a critical-but-unused ejs (via an unused dev tool) as contrast. Covered by 9 lockfile unit tests plus a parser integration test.
Consume the parser's resolved versions and dependency graph, and surface the direct/transitive distinction everywhere: - models: Dependency gains resolved/direct and a match_version property; add DependencyEdge and ParsedProject.dependency_edges; Finding gains direct/dependency_chain, surfaced as direct/dependencyChain in the JSON. All new fields default so pre-lockfile JSON still loads. - reachability: DependencyGraph carries reachability from a direct to the transitives it introduces — a transitive is as reachable as the most reachable direct that pulls it in, with the introducing chain recorded and dev inherited when every introducer is dev-only; orphan transitives stay unused (conservative). - osv: match against match_version (a lockfile's exact version beats an approximated range). Treat last_affected as an inclusive upper bound via VersionRange.contains, distinct from the exclusive fixed. - models.fix_for(version): pick the fixed bound of the range the installed version is actually in, so a multi-range advisory never recommends a downgrade; triage remediation uses it and, for a transitive, names the direct parent to upgrade. report/SARIF/issues label direct vs transitive and show the chain. With no lockfile there are no edges, every dependency is direct, and the classification is exactly the previous import-graph result. Adds transitive reachability, end-to-end, last_affected/fix_for, and issue-body tests.
Terminal and HTML render now tag transitive findings and show the introducing chain (express → qs). The new direct/dependencyChain fields are optional, so reports produced before lockfile support still render (treated as direct). Adds terminal + HTML transitive tests and a backward-compatible default test.
- npm: read peerDependencies and optionalDependencies (both runtime-facing, so prod not dev). - Python: parse setup.py (best-effort install_requires string list), setup.cfg ([options] install_requires + [options.extras_require], keyed on indentation so requirement operators aren't mistaken for INI keys), and Pipfile ([packages]/[dev-packages], with "*" as an empty constraint). - requirements.txt: capture the name from a PEP 508 direct reference (name @ url) instead of skipping the whole line. - Cargo: also read [workspace.dependencies] and every [target.<cfg>.dependencies] table (build-deps already handled). - TypeScript: treat .mts/.cts as TypeScript in the parser and add them to the analyzer's _JS_EXTS so their relative imports resolve. Adds unit tests for each new manifest path plus .mts/.cts detection.
- Add coerceReport, which normalizes any parsed value into a well-formed TriageReport (missing/mistyped results, summary, or per-result arrays become sane defaults). Both renderTerminal and renderHtml run it, so a truncated write or old-schema report annotates instead of dereferencing undefined; index.ts rejects a non-object payload with a clean error. - escapeHtml now also escapes the single quote ('), closing an attribute-injection gap in the HTML dashboard. Adds malformed-report and single-quote-escaping tests.
Pin ruff==0.15.22 and mypy==2.3.0 in the analyzer's dev extra (the CI installs .[dev], so the pins flow straight through) — a new lint rule or type check in a patch release can otherwise fail CI on unchanged code. Also add report.json to .gitignore so a local 'vta triage --out report.json' never gets committed.
- README: new 'Transitive dependencies' section explaining the reachability-by-inheritance rule with the fixture's demo; note lockfile and setup.py/cfg/Pipfile support in the capabilities, architecture diagram, and 'what it is NOT'; refresh test counts (Rust 38, Python 71, TypeScript 9). - design-decisions: record why lockfile parsing lives in the Rust parser and how a transitive inherits reachability from its introducer. - Bump all three components to 1.0.0 (Cargo.toml, pyproject.toml, package.json, vta.__version__).
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.
Summary
This is the v1.0.0 release push. The headline is transitive dependency support: until now VTA only saw direct dependencies at their unresolved constraints, so the risk that hides one level down — a vulnerable package you never chose — was invisible. It now reads lockfiles, resolves the transitive tree, and extends the reachability model to it.
A. Lockfiles / transitive dependencies (the big feature)
parser/src/lockfiles.rsreadspackage-lock.json(v2/v3 + v1),yarn.lock(classic v1),poetry.lock,Cargo.lock, and the flatPipfile.lock. It stamps each direct dependency with its exactresolvedversion and emitsdependencyEdges— the flattened parent→child package graph, mirroring the existingimportEdges. Malformed lockfiles warn and are skipped, never fatal.Dependencygainsresolved/direct;ParsedProjectgainsdependencyEdges. With no lockfile there are no edges and every dependency isdirect, so the JSON and every downstream stage behave exactly as before.data-utils → lodash) recorded anddevinherited when every introducer is dev-only. Conservative on purpose: it never hides a transitive of a reachable direct. Orphan transitives (e.g. from the tree-lessPipfile.lock) stay unused.resolvedversion instead of an approximated range.examples/vulnerable-transitive-app: a reachable transitivelodash(critical) outranks a moderate directexpress, while a critical-but-unreachable transitiveejsis noise.B. Polish (audit loose ends)
last_affectedis now treated as an inclusive upper bound (distinct from the exclusivefixed), andVulnerability.fix_for(version)picks the fix for the range the installed version is actually in, so a multi-range advisory never recommends a downgrade.peerDependencies/optionalDependencies; Pythonsetup.py/setup.cfg/Pipfile;requirements.txtname @ urldirect references; Cargo[workspace.dependencies]and[target.*.dependencies];.mts/.ctsTypeScript.coerceReportnormalizes malformed/old-schema reports so rendering never dereferencesundefined;escapeHtmlnow also escapes the single quote.ruff/mypypinned exactly..gitignorenow ignoresreport.json.analyzer/README.md, anddocs/design-decisions.mddocument the feature; all three components bumped to 1.0.0.Verification
All green locally:
cargo fmt --check·cargo clippy --all-targets -- -D warnings·cargo test— 38 testsruff check·mypy src·pytest— 71 testsnpm run typecheck·npm test— 9 testsvta triage examples/vulnerable-transitive-appsurfaces the reachable transitivelodashwith its chain and renders through the viewer to terminal + HTML; the direct-onlyvulnerable-express-appoutput is unchanged.