- Status: Accepted
- Date: 2026-07-01
- Deciders: Architecture
The npm/PyPI commands (ADR 0016, ADR 0017) accepted only a single package name,
while the NuGet scan already took a whole .csproj. Real CI usage scans the
project's manifest, not one package at a time. Two further debts sat next to that
gap: a version argument that wasn't an exact version was silently ignored
(depradar npm express "^4" scanned latest 5.x), and the three OSV vulnerability
sources were near-duplicates with GetFixedVersionAsync stubbed to null for
npm/PyPI even though OSV serves the patched version for every ecosystem.
- Manifests are first-class scan targets. Two pure Application parsers —
NpmManifest(package.jsondependencies) andRequirementsFile(pip requirements syntax: comments,\continuations, option lines, PEP 508 lines via the existingPyPiRequirement) — expand a file into direct dependencies. The CLI scans each root, unions the graphs (GraphMerge, extracted fromscan), and gates the merged result once. - Range-aware root resolution. The scanners now resolve a non-exact version
argument the way the package manager would: npm ranges via
NpmRange.BestMatch, PEP 440 specifiers viaPyPiSpecifier.BestMatch, against the registry's published versions. An unsatisfiable spec is a miss, never a silent fallback to latest. - One OSV protocol.
OsvProtocolcentralizes the/v1/queryand/v1/vulns/{id}wire logic; the NuGet/npm/PyPI sources are thin adapters differing only in ecosystem name and version grammar (SemVer.TryParsevsPyPiVersion.TryParse).GetFixedVersionAsyncnow works for every ecosystem. - One ecosystem command.
EcosystemCommandcarries the shared CLI engine (args, manifest expansion, merge, policy, report,--sbom/--sarif);NpmCommand/PyPiCommandshrink to declarative specs. SBOM/SARIF export thereby reaches npm/PyPI with zero new writer code.
devDependenciesare deliberately excluded (whatnpm install --omit=devdeploys); nested requirement files (-r), editable installs and git/file/workspace specifiers are not registry packages — they surface in the report'sunresolvedlist instead of failing the scan.- A manifest scan takes no version argument; mixing both is a usage error.
- Fixed-version hints unblock a future
depradar fixfor npm/PyPI manifests. - Verified live:
npm ./package.jsonresolved 71 packages with^4.18.0→ express 4.22.2 (not latest 5.x) and reported the git dependency as unresolved;pypi ./requirements.txthonored>=1.21.1,<1.24→ urllib3 1.23, merged multiple roots, and wrote CycloneDX + SARIF artifacts.