github OWASP/cve-lite-cli v1.40.0
v1.40.0 - pnpm 8 lockfile parsing and a ratchet that can fail CI

latest release: v1
4 hours ago

Upgrade note. Three changes can alter what a scan reports or what it exits with. pnpm lockfiles written by pnpm 8 and earlier were misparsed, so a vulnerable tree could report no vulnerabilities and exit 0; if you are on pnpm 6, 7 or 8, re-scan after upgrading. --ratchet with no committed baseline now applies --fail-on instead of writing a baseline and exiting 0, so a CI job that checks out fresh every run and never committed its baseline could not fail before and can now. And the parent package named for a transitive fix is now resolved from the finding's own dependency path, so some recommended versions change where a name was installed at several versions.

Added

  • The GitHub Action takes a delta input, so a pull request gets a comment listing only the findings that pull request introduced rather than the whole tree. It scans the base and the head with the same finding-affecting flags and upserts a single comment keyed on a marker, so repeated pushes edit one comment instead of piling up. It runs after the main scan and survives a fail-on failure, so the comment still appears on the run where it matters most. Fork pull requests go to the job summary with no API call, since the token is read-only there.

  • Code Scanning alerts now say which tool produced them. Each SARIF rule's help markdown leads with a link to the project, so an adopter triaging an alert can see where the finding came from.

  • A --json payload records the version that produced it, when the scan ran, and which options were in effect. A JSON result on its own previously could not be traced back to either a release or an invocation. It records the effective options rather than the raw command line, so defaults are captured alongside what was typed, and any option whose value is a path or a URL records only that it was set, since these files get shared and the offline database, cache directory and OSV URL options can all carry internal paths and internal registry hostnames. The fields are additive.

Fixed

  • pnpm lockfiles written by pnpm 8 are now parsed, so a pnpm 8 project no longer scans clean while carrying known vulnerable dependencies. Lockfile v6 changed the package key shape, and the pre-v9 parser understood only the older form: every unscoped package was dropped outright and every scoped package was misread, so an advisory matched nothing. The result was "No known vulnerabilities found" and a zero exit code on a vulnerable tree, which is the worst failure this scanner can produce. Two smaller faults in the same parser went with it: on lockfile v5 a package that resolved peers kept the peer suffix inside its version string and was missed too, and a single-project v5 or v6 lockfile keeps its root dependencies at the top level, so no dependency paths were reconstructed and transitive findings had no parent to point at. The v9 parser is untouched.

  • --ratchet with no committed baseline now applies --fail-on exactly as if --ratchet were absent, instead of writing a baseline and exiting 0. A CI job that checks out fresh every run could never fail: the first run wrote a baseline accepting every existing finding, and every run was a first run. --ratchet --fail-on high means "fail on high, except debt I have accepted", and with no baseline there is no accepted debt. The baseline is still written, and one line says that committing it is how you accept the existing debt.

  • A --ratchet run now writes the --json or --sarif report it was asked for. Every ratchet outcome returned before the report was written, so no ratchet run had ever produced either file on any path. Two of those paths exit non-zero, so those are builds that go red with nothing to attach. Accepted findings are reported in a separate field rather than mixed into the findings list, absent rather than empty when nothing was accepted, and SARIF carries them as results with a suppressions entry, which is what that field is for.

  • Remediation resolves the parent package to upgrade from the finding's own dependency path on every route, instead of by name on one of them. The selected parent's version decides which manifest is fetched, and the vulnerable child's fix is validated against that copy's declared range, so taking the first match by name could validate against a parent that is not on the path. Measured on a real tree: a vulnerable brace-expansion@1.1.13 sits under a parent declaring ^1.1.7, the name lookup selected a different installed copy declaring ^2.0.2, and the recommendation came out as 2.1.7 when 1.1.21 was published, within range, and what the advisory itself named. If the path cannot identify the parent we now report no remediation, which is the honest failure mode.

  • Remediation no longer offers one of the project's own workspace packages as the thing to upgrade. In a monorepo the first hop of every dependency path can be a first-party package, so a transitive finding with an available fix was reported as having none, advising the developer to check for a release of code they own. The guard now skips a candidate whose recorded version is a local protocol reference, which is what it means to test, rather than relying on the candidate failing to resolve, which only happens to hold on npm. Verified on a 397-package monorepo: all 14 findings resolve a parent, two by skipping a workspace member first.

  • A within-range refresh names the package that actually moves. That remediation means the parent already allows a safe version of the vulnerable child, so the parent stays put and the child has to move, but the command named the parent, and once the parent is already at its newest allowed version there is no reason to re-resolve its subtree. Measured against a real install: updating the parent left the child where it was and updating the child moved it. The exact-pinned parent case now has a fixture and is handled by the fix runner.

  • Every finding appears in the fix plan exactly once, as a target or as a skipped entry with a reason. The plan keyed and matched targets by package name without the installed version, so two installed majors of one package collided: a finding could vanish entirely, be handed a command that did not apply to it, or be covered and still show nothing. Measured across five example projects and 292 findings: zero in both lists, zero in neither.

  • Package metadata resolves through the registry the project installs from, not always the public npm registry. Fix versions were validated against a registry the adopter may never install from, which is worse than not validating, since the output carries the same confidence either way. The resolver reads what the package manager reads, in the same order, including Yarn Berry scoped registries. Nothing configured means the public registry. The npm advisory bulk endpoint stays public, because a mirror proxies metadata but generally does not implement it, and no credential is read or stored.

  • Fix-version messages name the registry actually queried instead of asserting something about npm. A project pointed at a mirror was told a version "is not published on npm" when npm was never asked, which hid the fact the reader needs, that their own registry is missing the fix. An unconfigured scan still reads "on npm".

  • Advisory records that OSV marks as the same vulnerability are collapsed into one finding, with the absorbed id kept in the survivor's aliases. Two ids describing one vulnerability previously counted twice, which inflated the advisory count on a finding. Measured on a 4,367-package tree: 473 advisory records before and 472 after, with finding and package counts unchanged, so this moves advisory records only and changes no detection.

  • Fix commands in the HTML report no longer embed a path computed against the working directory at render time. A report generated from anywhere other than the project produced a command nobody could run, inside a file whose purpose is to be opened later from somewhere else. A multi-folder panel keeps its folder, relative to the scan root, because there it is load-bearing and still portable.

  • handlebars upgraded to 4.7.10 and ts-jest upgraded, clearing four advisories in this project's own development dependencies.

Changed

  • The two SARIF entry points take an options object for their trailing parameters instead of eight positional ones. Six of the eight were optional and four were optional arrays of findings of the same shape, so transposing two of them was a silent bug no type could catch. Behaviour is unchanged.

  • The CLI no longer prints em dashes. There were 54 across 17 source files, 43 of them inside strings the scanner emits, so they reached the terminal, the JSON payload, SARIF and the HTML report.

  • The three --fail-on gate-class labels come from one shared constant instead of being typed out on each scan path, where the wording could drift between a single-folder and a multi-folder scan with the whole suite still passing. Gate behaviour and exit codes are unchanged.

Docs

  • The parser coverage page no longer claims full pnpm support without saying which releases it is true of. It listed v5, v6 and v9 as full, so a pnpm 8 user would conclude their lockfile was covered while a scan of it reported nothing. The format bullet now names how each version keys a package, so a reader can tell whether their own lockfile is affected.

Validation

  • npm test
  • npm run build

Contributors

Don't miss a new cve-lite-cli release

NewReleases is sending notifications on new releases.