feat(overrides): override-hygiene subsystem (cve-lite overrides + --check-overrides)#718
Conversation
… --check-overrides) Contributes the override-hygiene auditor from @hexaxia-labs/override-audit-cli (Aaron Lamb), co-developed with Sonu Kapoor. It audits npm / pnpm.overrides / yarn resolutions for hygiene problems that CVE remediation alone never catches. - `cve-lite overrides [path]` runs eight detectors (OA001-OA008): orphaned target, floating tag, misplaced section, surpassed pin, ineffective nested, parent-binary coupling, registry drift, and on-disk materialized vulnerable copy. - `cve-lite [path] --check-overrides` runs the same audit during a regular scan and threads findings to terminal / JSON / SARIF / HTML output. - `--fix` closes the loop: an applied override fix re-runs OA001 + OA008 against the patched target to confirm it took (new exit code 2 on verify failure). The fix hook never creates new override keys; it removes, repins, or relocates existing ones. - Opt-in project audit log via `--audit-log <path>` / CVE_LITE_AUDIT_LOG (NDJSON). - Docs: docs/rules/OA001-OA008, docs/audit-log.md, docs/api/overrides.md. Additive for existing CVE users: the default scan exit behavior is unchanged, and SARIF adds a sibling toolComponent only when override findings are present. Staged on main HEAD (0c16544). 827 tests / 81 suites green.
sonukapoor
left a comment
There was a problem hiding this comment.
Really solid foundation - the subsystem architecture is clean, the chokepoint guard is the right safety invariant for a tool that auto-patches project files, and the test coverage is thorough. A few things need attention before this merges.
The main one: the fix hook runs on every --fix scan regardless of whether --check-overrides was passed. That contradicts the "additive by design" contract in the PR description - any user running cve-lite . --fix will silently get a full OA audit and potentially have their package.json patched without knowing it. Details in the inline comments.
The audit log also has a double-open on the overrides command path and a missing close() before process.exit(2). The SARIF output is missing the toolComponent.index linkage required by SARIF 2.1.0 §3.27.7. The HTML report renders an "Override hygiene" section on every scan even when override checking was never requested. The rest is minor - details inline.
- Gate the --fix override hygiene hook on --check-overrides so `cve-lite . --fix` never audits or patches package.json overrides unasked (additive contract). README auto-fix section updated to match. - Pass the index-level audit-log handle into runOverrides instead of opening a second handle to the same --audit-log file on the overrides command path. - Emit scan.finished and close the audit log before process.exit(2) on the override-verify failure path (was leaking the FD and dropping scan.finished). - SARIF: add rule.toolComponent.index to override results (SARIF 2.1.0 3.27.7) so GitHub Code Scanning / Multitool link OA001-OA008 to the extension; version the override extension component from getCliVersion() instead of a "1.0.0" literal. - HTML report: render nothing when override hygiene was not requested (undefined), vs the "no findings" panel for an empty result set. - fixer: refuse a move into an already-occupied destination instead of silently overwriting it (the chokepoint guard only catches key-set growth, not clobber). - terminal: "applyable patch" -> "autofix available". Tests: +5 (hook-gating contract, SARIF rule ref + version, HTML omit, move guard); 2 updated. 832 green.
|
@sonukapoor all eight points are addressed in 2ca5b05 — replies inline on each thread, README aligned with the gated |
#1) The installed-tree walker skipped `.pnpm` along with other dotfiles, so in a strict pnpm layout every transitive-only package (the real copies live under .pnpm/<name>@<ver>/node_modules/<name>) was invisible. installedCopies came back empty for those packages, which: - false-positived OA006 (overrideWonOnDisk saw 0 copies -> "override not confirmed on disk") on the canonical next->postcss pattern, and - blinded OA008's materialization check to genuinely below-floor copies in the store (false negative). Descend into the virtual store and dedupe by realpath so a package reachable both via a top-level symlink and via the store is counted once. Closes #1.
#3) --ratchet unconditionally re-saved the current findings as the baseline and exited 0, so a newly introduced vulnerability was silently absorbed into a fresh baseline - a ratcheted CI job could never fail on a regression. Add ratchetOutcome(): with no baseline, save (unchanged); with an existing baseline, gate - report findings not in the baseline and exit 1 without overwriting. Matches the documented "if baseline exists, only fail on new findings." Closes #3.
sonukapoor
left a comment
There was a problem hiding this comment.
All 8 requested changes are correctly addressed - thank you for the thorough fixes. One new issue crept in with the ratchet rewrite: the three --ratchet exit paths don't call auditLogHandle.close(). Same pattern as the process.exit(2) fix you just landed - detail inline. Everything else looks good.
| if (options.checkOverrides) { | ||
| console.log(chalk.gray("Note: override hygiene (--check-overrides) is not part of the ratchet baseline; run `cve-lite overrides` to audit overrides.")); | ||
| } | ||
| process.exit(0); |
There was a problem hiding this comment.
The three --ratchet exit paths here (lines 532, 540, 549) skip auditLogHandle.close() and the scan.finished emit. Same issue as the process.exit(2) path you fixed - the audit log FD is leaked and the log is missing the terminal event. Each of the three exits needs the same treatment:
auditLogHandle.emit({ ts: new Date().toISOString(), type: "scan.finished", schemaVersion: 1, durationMs: Date.now() - scanStartTime, findingsCount: scanState.sorted.length, exitCode: 0 });
auditLogHandle.close();
process.exit(0);(exitCode: 1 for the new-findings branch at line 549.)
There was a problem hiding this comment.
Fixed in 87835ae. All three ratchet exit paths (baseline saved, no new findings, new findings) now emit scan.finished with the correct exitCode (0/0/1) and call auditLogHandle.close() before process.exit, matching the process.exit(2) pattern. Full suite green.
… ratchet exit paths The ratchet gate (#3 fix) added three process.exit() sites that skipped the scan.finished emit and auditLogHandle.close(), leaking the FD and dropping the terminal audit-log event - the same defect class as the process.exit(2) fix. Each ratchet exit (baseline saved, no new findings, new findings) now emits scan.finished with the right exitCode and closes the handle before exiting.
|
@sonukapoor ratchet audit-log leak fixed in 87835ae - all three exit paths now emit scan.finished + close() before exiting (same pattern as the exit(2) fix). Suite green. Ready for another look. |
|
@sonukapoor one structural question before you re-review: the Both are the two tip commits on the branch, so they're cleanly separable. I'm happy to pull them into their own focused PR to keep this one purely the override-hygiene subsystem - or leave them here since you've already reviewed the rewrite. Whichever you prefer for review and merge. I've filed the underlying bug as #721 so it's tracked on its own regardless of which way you want the fix to land. |
sonukapoor
left a comment
There was a problem hiding this comment.
All issues resolved - the ratchet exit paths are correctly fixed across all three sites, consistent with the process.exit(2) pattern from the prior commit. Happy to keep the ratchet gate fix here given it was surfaced during this review and is already tracked under #721. Merging.
|
Merged - thank you @alamb-hex! Outstanding contribution. |
Closes #717
Adds an override-hygiene subsystem that audits npm
overrides/pnpm.overrides/ yarnresolutionsfor problems that CVE remediation alone never catches. Contributed from@hexaxia-labs/override-audit-cli(Aaron Lamb), co-developed with Sonu Kapoor. Opening this PR is MIT-license consent per CONTRIBUTING.md.What it adds
cve-lite overrides [path]runs eight detectors (OA001-OA008): orphaned target, floating tag, misplaced section, surpassed pin, ineffective nested, parent-binary coupling, registry drift, and on-disk materialized vulnerable copy. Each finding links to a rule doc underdocs/rules/.cve-lite [path] --check-overridesruns the same audit during a normal scan and threads findings to terminal /--json/--sarif/--reportoutput.--fixcloses the loop: an applied override fix re-runs OA001 + OA008 against the patched target to confirm it took (new exit code2only on an override-fix verify failure). The fix hook never creates new override keys; it removes, repins, or relocates existing ones.--audit-log <path>/CVE_LITE_AUDIT_LOG(NDJSON change-control stream). No-op when unused.docs/rules/OA001-OA008,docs/audit-log.md,docs/api/overrides.md.Additive by design
No change to the default scan path or its exit behavior. The only surface a non-override user can observe is opt-in: the
overridessubcommand, the--check-overridesflag,--audit-log, exit code2(override--fixverify failure only), and a SARIF siblingtoolComponentthat appears only when override findings exist (the CVE component/results are unchanged otherwise).Verification
main(no rebase needed).npm run buildandnpx tsc --noEmitclean.--offline).Notes