feat(security): cve-lite 1.28 + override hygiene scanning (0.21.0) - #133
Merged
Conversation
Records the resolved cve-lite version in each cache entry and treats a mismatch as a miss, so a bump invalidates stale reports immediately rather than letting them age out over the 1h TTL. Also closes the ignoreTtl stale-fallback path, which could otherwise resurrect a 1.24 report when a 1.28 scan fails. Adds recorded 1.28 scan + overrides fixtures for parser tests.
1.28 (#899) replaced the 'exact-direct-child' | 'best-effort' confidence values with 'verified' | 'unverified' and now only recommends a parent upgrade it has proven resolves the vulnerable package. The findings badge still keyed on the old strings, so every confidence badge degraded to the gray fallback after the bump. Adds FindingRow.parentVerified so unverified suggestions can be told apart from proven ones instead of being presented as equally trustworthy.
Adds scanCompleteness(), which tells a clean scan apart from one that could not check everything. 1.28 surfaces unresolvedAdvisoryIds per finding and skippedDependencies at the top level; both are aggregated here. skippedDependencies is counted, never destructured — the array was empty in the recorded capture so its element shape is unverified.
SourceResult.warning was declared but never set or rendered. Widens
ScanSource.scan to return { findings, warning } so a source can report that
it succeeded without covering everything, wires it through the runner, and
renders it on the source card.
Without this a degraded scan is indistinguishable from an all-clear.
A scan that could not resolve some advisories or skipped dependencies now surfaces a partial-scan warning on the source card instead of rendering as a green all-clear on the fleet view.
Wraps 'cve-lite <path> overrides --json' (OA001-OA009). Like the main scan, the subcommand writes a timestamped file into cwd rather than stdout, so the runner executes in a temp dir and reads the file back; the file's presence, not the exit code, signals success. Adds a namespaced JSON cache shared with future callers rather than copying cve-lite-cache.ts.
Surfaces cve-lite's override-hygiene audit (OA001-OA009) as config findings on the fleet security view, alongside the existing three-source stack. PD001/PD002 are filtered out so DependencyHealthSource remains the single phantom-dep authority; config findings dedup on title+path, so letting both report would double-count every phantom dep. The jsonPath is folded into the finding path because two findings of the same rule on different override entries carry identical message text and would otherwise collapse into one.
GET /api/security/overrides/:id returns the cached override audit (?force bypasses) as both raw cve-lite findings and HexOps Finding rows.
POST /api/security/overrides/:id/fix runs 'cve-lite overrides --fix', optionally scoped to a single OA rule, behind a new default-off OVERRIDE_HYGIENE_FIX_ENABLED kill switch enforced server-side so a stale tab cannot bypass it (#96/#97). The fix runs an install, so it is wrapped in runWithDevServerGuard (#109) to stop HexOps patching itself out from under its own dev server.
Renders OA rule findings with rule id, severity, package, file > jsonPath location, and the runnable fix command. The Fix all control appears only when OVERRIDE_HYGIENE_FIX_ENABLED is on, and is confirm-gated; the server enforces the switch independently.
Names the advisories that could not be resolved and the dependencies that were skipped, so a partial scan is not mistaken for an all-clear on the project view. Complements the degraded source state on the fleet cards.
…ompleteness Four Important findings from the final branch review, each a real correctness bug that only showed up once tasks were viewed together: - CompletenessBanner was fed the "imported only" FILTERED report. Toggling that filter could drop the one finding carrying an unresolved advisory id, making a provably partial scan render as clean — the exact false all-clear the banner exists to prevent. Now fed the unfiltered report; completeness is a property of the scan, not the view. - GET /api/security/overrides/[id] returned report.findings unfiltered while parseOverrideAuditJson (used for `rows`) excludes PD001/PD002 so DependencyHealthSource stays the single phantom-dep authority. The panel renders `findings` directly, so a real phantom dep showed up twice. EXCLUDED_RULES is now exported and applied to both arrays. - The cve-lite-cache TTL-expiry test wrote an entry with no toolVersion, so the version gate returned null before the TTL comparison ever ran — verified by temporarily deleting the TTL branch and confirming only that test failed (see final-fix-report.md for the exact command output). Fixed by giving the entry a matching toolVersion. - POST /api/security/overrides/[id]/fix ran RULE_RE.test(body.rule) without a typeof guard first; a non-string body.rule like ["OA009"] coerces via toString() and passes the anchored regex, reaching JSON.stringify as an array. Not currently exploitable (the regex confines reachable bytes to inert shell characters) but the safety proof depends entirely on the regex staying anchored/alternation-free with no test to catch a future regression. Added the typeof guard plus a second test file (route.rule-validation.test.ts) that mocks OVERRIDE_HYGIENE_FIX_ENABLED true in an isolated module registry, since the existing test file mocks it false and 409s before validation is ever reached. The shipped default in auto-apply-flag.ts stays false. Full suite: 272 passed (42 files), up from the 265/41 baseline.
Two Minor findings in parseOverrideAuditJson:
- The dedup key path fell back to bare `file` when jsonPath was absent, with
no package component. Two same-rule findings on different packages that
share message text and omit jsonPath would collide on the merger's
`config:<path>|<title>` key and silently lose one. Falls back to
`${file}#${packageName}` instead of bare `file` now, keeping the existing
jsonPath-present path unchanged (the fixture's dedup test still passes
as-is).
- A finding emitted without a ruleId defaulted to 'OA000', which isn't in
EXCLUDED_RULES, so an unlabelled phantom-dep finding could slip past the
PD001/PD002 filter and duplicate DependencyHealthSource. Now dropped
outright when ruleId is missing rather than guessed into an OA-shaped
default — no new rule taxonomy invented.
(F5's devServerGuard fix and F8's reworded 409 message shipped in the prior
commit, since both live in overrides/[id]/fix/route.ts alongside F4.)
Full suite: 272 passed (42 files).
…he a thin wrapper cve-lite-cache.ts and json-cache.ts were near-duplicate implementations instead of the extracted-and-shared module the design called for (§4.1). json-cache.ts is now the single source of truth: it gains an ignoreTtl option, a closed/self-validating namespace registry to make namespace-separator filename collisions structurally impossible, and a fix for corrupt cachedAt values being treated as fresh (NaN age). cve-lite-cache.ts is now a thin typed wrapper that preserves its public API and on-disk filename/format exactly, including reading entries written under the pre-refactor `report` field name.
The pairwise collision check ran on every import over a fixed two-element registry already guarded by TypeScript's closed JsonCacheNamespace union and the runtime assertRegisteredNamespace check. This redundant check converted any helper-function bug into an app-wide import-time crash instead of a caught test failure. Unit tests for _namespacesCollide (in json-cache.test.ts) provide the proper CI-gated safety for any future namespace addition — that is the right enforcement point. Module-load execution removed; the helper and all its tests remain.
Covers the override-hygiene integration and the next/postcss security fixes that ride with it. Also gitignores .playwright-mcp/, which browser-driven verification runs leave in the working tree.
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.
Upgrades
cve-lite-cli1.24.0 → 1.28.0, absorbs two correctness changes in data HexOps already rendered, and integrates cve-lite's override-hygiene audit (OA001–OA009) as a first-class surface.Why this isn't just a version bump
Two things in 1.28 changed data already on screen:
recommendedParentUpgrade.confidencewas renamed fromexact-direct-child/best-efforttoverified/unverified. The badge colour map still keyed on the old strings, so after the bump every confidence badge silently degraded to gray. 1.28 also now only recommends a parent upgrade it has proven resolves the vulnerable package — unverified suggestions are marked, and are now rendered distinctly instead of as equally trustworthy advice.What's new
OverrideHygieneSource— a fifthScanSourcerunningcve-lite <path> overrides --json, surfacing override-configuration defects asconfigfindings on the fleet view.file > jsonPathlocation, runnable fix command.GET /api/security/overrides/[id](cached,?force) andPOST .../fix, the latter gated by a newOVERRIDE_HYGIENE_FIX_ENABLEDflag that ships disabled, is enforced server-side with a 409 before any project lookup, and wraps the install in the dev-server guard (Guard patch apply when the target project's dev server is running (Turbopack node_modules churn) #109).scanCompleteness()drives a warning banner naming what couldn't be checked, plus a degraded source state on the fleet cards.ScanSource.scanwidened to{ findings, warning? }, wiring the previously declared-but-deadSourceResult.warningend to end.Deliberate decisions worth reviewing
DependencyHealthSource(Scanner: detect phantom dependencies (source imports of undeclared/override-only packages) #125). They also false-positive on monorepo roots: onptolemycve-lite reports 11 phantom deps that are declared directly inapps/web/package.json, whileDependencyHealthSourcecorrectly reports 0. Without the filter,/securitywould show 11 fabricated findings on that project alone.jsonPathis folded into the finding path.configfindings dedup onconfig:<path>|<title>, and two OA009 findings on this repo carry byte-identical message text (ip-address,ws) — keying on file + message alone silently merged them into one.json-cache.tsis now the shared cache, withcve-lite-cache.tsa thin wrapper preserving its API and on-disk filenames. Cache entries record the resolved cve-lite version, so a bump invalidates immediately rather than aging out — including on the stale-fallback path, which could otherwise resurrect a pre-bump report during a scan failure.Fleet impact
Audited all 29 lockfile-bearing registered projects. 26 raw findings, 18 of them PD001/PD002 (filtered) → 5 visible findings across 3 projects; 17 projects clean. No flood.
The rules found real problems:
cordero-grouphad a stalepostcssoverride in the wrong package-manager section (OA008 critical + OA006/OA003 high), since fixed;hexaxia-mediahas two dead overrides (OA001 high).Verification
292 tests / 43 files,
tsc --noEmitclean,pnpm buildexit 0. Every task was independently reviewed; a whole-branch review returned four findings living in the seams between tasks — a banner fed a filtered report, the read endpoint returning unfiltered findings, a cache test that passed without exercising its target, and a missing type guard before a regex on a shell-bound value — all fixed and re-reviewed.Known gap: the flag-enabled fix path has no automated coverage. That is a hard gate before
OVERRIDE_HYGIENE_FIX_ENABLEDis ever flipped; read-only use is fully built and tested.