fix(statusline): overlay ruflo's fabricated CVE counter with the real scan - #26
Merged
Merged
Conversation
… scan The statusline told every clean repo it had 3 CVEs. Upstream's getSecurityStatus (@claude-flow/cli funnel/local-signals.js) hardcodes `const totalCves = 3` — ruflo's OWN v3 roadmap items (CVE-1/2/3 in their v3-security-architect.md: an outdated dep + SHA-256 hashing + hardcoded creds in THEIR api/auth-service.ts), not the rendered project's risk — and derives cvesFixed from scans.length, a FILE count. The alarm therefore cleared itself: run the suggested scan, it writes a JSON file, the counter drops. Three files reach CLEAN with nothing scanned, let alone fixed, while a real finding never registers. Reported upstream: ruvnet/ruflo#2694. The overlay reports what the newest scan actually found and never invents a CVE: totalCves/cvesFixed are pinned to 0 so the "N CVEs" branch cannot fire, and real state rides in `status`, which ruflo's renderer prints verbatim — PENDING when never scanned (an honest unknown, not a false green), "N ISSUES" when the scan found things, CLEAN, or STALE past 7 days. Wired by wrapping getStatuslineData rather than patching applyLocalOverlays: the fresh-cache early return (`if (cache.fresh && cache.promoFresh) return overlayMemoPromo(cache.data)`) bypasses applyLocalOverlays entirely, so a patch there is never called during the 60s TTL and the fabricated count renders anyway. Wrapping the one entry point covers all four return paths. The count also reaches the screen a second way — funnel/insights.js computes `pending = totalCves - cvesFixed` CLI-side and ships a finished sentence — so rufloHonestInsight rebuilds that one line from the real scan, matching on text because promo.js discards the insight id. Gated on positively detecting the defect in the installed CLI rather than a pinned version, mirroring improvement-eval's --cli-check stopgap: the patch retires itself on the first sync after upstream fixes this. Covered by a test. Also in this change: - status: detect statusline CONTENT drift, not just marker presence. Drift is now "would a sync change this file?", which fixStatusline's dry run already answers. A marker test reported 'ok' on a stale injected block, and since sync builds its plan from rows carrying a `fix`, re-injection never ran. This bit us live: an updated overlay silently failed to land. It also means any kit upgrade revising the footer would not have re-injected. - statusline: make the aidefence segment alarm-only. It was a permanent green "aidefence on" — the one pure binary badge in the footer (SONA/QE counts move; a constant "on" says nothing after the first glance) — and its shield glyph collided with ruflo's line-2 scan shield, a different concern entirely: `security scan` audits your source, `security defend`/AIMDS screens prompts for injection, jailbreak and PII. Per issue #8's rule, already law for the proof segment, the expected state is now silent and only the failure surfaces, with no shield glyph in the alarm. Kept rather than deleted because it is load-bearing: @claude-flow/aidefence is still not a declared dependency of ruflo or @claude-flow/cli on 3.32.0 while `security defend` imports it (ruvnet/ruflo#2670), so it is present only because healAidefence installs it. A plain `npm i -g ruflo` can silently remove injection defense. The probe is three-state, not boolean: inverting a signal inverts its failure mode, and a probe miss (custom npm prefix, statusline running under a different node) would otherwise fail loud and WRONG. "off" requires positive evidence — a located ruflo install lacking aidefence; anything unverifiable stays "unknown" and silent.
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.
Why
The statusline told this repo — and every clean repo — that it had 3 CVEs:
Meanwhile
pnpm auditreported 0 vulnerabilities across 176 deps andruflo security scan --depth fullreportedCritical: 0 High: 0 Medium: 0 Low: 0. Both auditors disagreed with the statusline.It's an upstream defect, not ours.
getSecurityStatus()in@claude-flow/cli/dist/src/funnel/local-signals.js(3.32.0, latest):totalCves = 3describes ruflo's own v3 roadmap — per theirv3-security-architect.md, CVE-1/2/3 are an outdated@anthropic-ai/claude-codedep, SHA-256 hashing in theirapi/auth-service.ts:580, and hardcoded creds in theirapi/auth-service.ts:602. Not public CVE IDs; nothing to do with the rendered project.cvesFixedcounts.jsonfiles. A scan reporting"findings": []counts the same as one reporting a critical RCE.So the alarm clears itself:
⚠ 3 CVEs→ "run the scan" → the scan writes a file → the counter drops. Verified — twocpcommands take a never-meaningfully-scanned directory toCLEAN:Reported upstream: ruvnet/ruflo#2694. Latest is 3.32.0 and unfixed, so we patch until it lands.
Confirmed our own customizations are not implicated: the defect reproduces on a pristine
statusline.cjswith zero kit fingerprints,fixStatuslinenever touchessecurity/cvesFixed/totalCves, and the kit never writes to ruflo's CLI internals.What
Overlay the counter with the real scan.
totalCves/cvesFixedare pinned to0so the⚠ N CVEsbranch can never fire; real state rides instatus, which ruflo's renderer prints verbatim:🛡 scan pending(honest unknown, not a false green)🛡 ✓🛡 scan stale🛡 n issues(red)Two implementation notes worth a reviewer's attention:
getStatuslineData, notapplyLocalOverlays. The obvious fix — completingapplyLocalOverlays, which already re-derivesadrs/tests/hooksbut skipssecurity— silently does nothing. The fresh-cache early return (if (cache.fresh && cache.promoFresh) return overlayMemoPromo(cache.data)) bypasses it, so during the 60s TTL the patched function is never called. Wrapping the single entry point covers all four return paths.funnel/insights.js:45re-derivespending = totalCves - cvesFixedCLI-side and ships a finished sentence, so line 3 still read⚠ 1 CVE pendingwithsecurityalready corrected.rufloHonestInsightrebuilds that one line from the real scan, matching on text becausepromo.jsdrops the insight id. Non-CVE insights pass through by identity, so the funnel rotation is untouched.Self-retiring. Gated on positively detecting the defect in the installed CLI rather than a pinned version — same approach as
improvement-eval's--cli-check(#2222). When upstream fixesgetSecurityStatus, the nextsyncstrips the stopgap. There's a test for exactly that.Also here
status: detect statusline CONTENT drift, not just marker presence. Drift is now "would a sync change this file?" — whichfixStatusline's dry run already answers. The marker test reportedokon a stale injected block, and sincesyncbuilds its plan from rows carrying afix, re-injection never ran. This bit during development: an updated overlay silently failed to land. Beyond this PR, it means any kit upgrade revising the footer would not have re-injected it.statusline: aidefence segment is now alarm-only. It was a permanent green🛡 aidefence on— the only pure binary badge in the footer (SONA/QE counts move; a constant "on" says nothing after the first glance) — and its shield collided with ruflo's line-2 scan shield. They are different concerns:security scanaudits your source;security defend/AIMDS screens prompts for injection, jailbreak, PII. Per issue #8's rule (already law for the proof segment: "no static green badge"), the healthy state is now silent and only the failure surfaces, with no shield glyph in the alarm.Kept rather than deleted because it's load-bearing:
@claude-flow/aidefenceis still not a declared dependency ofrufloor@claude-flow/clion 3.32.0 whilesecurity defendimports it (ruvnet/ruflo#2670) — it exists only becausehealAidefenceinstalls it. A plainnpm i -g ruflocan silently remove injection defense.The probe is three-state, not boolean. Inverting a signal inverts its failure mode: a probe miss (custom npm prefix, statusline running under a different node) would have rendered "your defense is off" when it isn't — the same fabricated-alarm crime this PR fixes.
"off"requires positive evidence; anything unverifiable is"unknown"and stays silent.Verification
Before → after on this repo (clean scans on disk):
New coverage: overlay states (pending/clean/stale/N-issues), CVE counters pinned at 0 in every state, newest-scan-wins, malformed-JSON tolerance, insight rebuild + non-CVE pass-through, aidefence three-state polarity end-to-end, injection idempotency, and self-retirement on upstream fix.
Nothing in
.claude/is committed — it's generated and gitignored.🤖 Generated with Claude Code