Add fixed-version remediation UX: FIXED IN column and --format fixplan - #4
Merged
Conversation
Parse the fixed ranges vexscan already receives from OSV but never read, and surface them as the report's one actionable field. - Extract release-scoped fixed versions from each OSV record's affected ranges, joined onto findings by source package. - Add a FIXED IN column (earns its place like EPSS/KEV) and a remediation summary line (N unique advisories, N fixable, N with no fix yet). "no fix" is kept distinct from a blank cell. - Add --format fixplan: a remediation-first view that groups affected findings by the upgrade that clears them. Debian/Ubuntu packages collapse to the single newest fix (cumulative point releases) via a new internal/debver dpkg comparator; other ecosystems stay split rather than guess an order. Un-fixable findings are still listed under NO FIX YET. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds remediation-focused reporting by extracting OSV “fixed” version events and surfacing them as actionable upgrade targets in both the standard text report and a new --format fixplan output.
Changes:
- Parse OSV affected ranges to extract release-scoped fixed versions and overlay them onto findings.
- Extend the text report with an optional
FIXED INcolumn plus a remediation summary line. - Introduce
--format fixplan, including Debian/Ubuntu version collapsing via a newinternal/debvercomparator, with tests and README updates.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| report.go | Adds shared empty-report messaging, remediation summary, and optional FIXED IN column/detail output. |
| report_test.go | Adds coverage for FIXED IN column behavior and remediation summary output rules. |
| README.md | Documents FIXED IN semantics and the new --format fixplan output. |
| main.go | Wires --format fixplan into CLI validation and rendering. |
| internal/osv/osv.go | Extracts fixed versions from OSV affected ranges with release scoping. |
| internal/osv/osv_test.go | Tests fixed-version extraction, latest-event selection, and release scoping. |
| internal/ecosystem/ecosystem.go | Extends the finding schema with fixed_version. |
| internal/analyze/analyze.go | Adds resolver plumbing + overlay to populate FixedVersion on findings. |
| internal/analyze/fixed_test.go | Tests overlay join logic (by package/component) and empty-map behavior. |
| internal/debver/debver.go | Implements Debian version ordering (dpkg verrevcmp) for safe collapse-to-newest. |
| internal/debver/debver_test.go | Validates ordering against known dpkg comparison outcomes. |
| fixplan.go | Implements remediation-first “upgrade plan” rendering, grouping, collapsing, and sorting. |
| fixplan_test.go | Tests collapse behavior, no-fix separation, and non-orderable ecosystem behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The dpkg verrevcmp algorithm weights a digit as 0 in the non-digit comparison run, so a digit sorts before any letter and after '~'. The first cut of order() omitted the digit case, letting a digit fall through to c+256 and sort after letters instead. This only triggers when two versions' runs misalign (a digit on one side against a letter or punctuation on the other), which real distro point-release strings avoid, but it inverted those comparisons for a general dpkg comparator and could pick a non-newest fix target. Add the digit case and regression tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The remediation line ("N affected: ...") repeats in the footer like every
other summary line, so the footer is now four summary lines rather than
three and the test's hard-coded last-3-lines window no longer caught the
ecosystem header. Assert the invariant directly instead: the section index
is the final line (nothing prints beneath the footer), and the summary
markers appear in the footer block.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
cwayne18
added a commit
that referenced
this pull request
Aug 5, 2026
A review of the fixed-version work in #4 turned up one behavioural bug and six places where the code and its own documentation had drifted apart. An OSV record routinely carries more than one `fixed` event, because a vendor maintaining several branches patches them all: GO-2022-0623 fixed Vault in 1.5.9, 1.6.5 and 1.7.2, and 60 of the 131 records returned for that module read the same way. The last event won, on the reading that OSV lists them ascending so the last is newest -- true, and irrelevant, because the newest of several maintained branches is not the upgrade target for someone on the oldest of them. A 1.5.4 install was told to move to 1.8.5 to close CVE-2021-43998, when 1.7.6 closes it. Advisory.Fixed becomes a list, Advisory.Ecosystem travels with it, and the overlay picks the lowest fix that is actually an upgrade. Picking needs a version order, so it is scoped to what this tool can order with confidence: Debian and Ubuntu through internal/debver, Go and npm through semver. PyPI is deliberately out, since PEP 440 sorts 1.0rc1 before 1.0 and semver sorts it after; so are the RPM distros, since rpmvercmp is not verrevcmp. Everything else keeps the old last-wins target, which overshoots rather than naming a version that may not contain the fix -- and --details now names the branches it did not choose either way, so the overshoot is visible. The rest, all from the same review: - Two doc comments lost their first line when the new code was spliced in above them. `go doc` was rendering writePriority's as "severity spread above it counts." and aliases's as "It exists for the VEX overlay." - The fix plan borrowed renderText's footer, which repeats the main report's section index -- naming AFFECTED and RULED OUT under a document whose only headings are UPGRADE and NO FIX YET. It gets its own footer: its own summary, and the caveats, which are about the scan and not about the view. - "clears 86 advisories; 154 with no fix yet" mixed units in one sentence. The 86 are advisories and the 154 are findings; both now say so. - The fix plan silently dropped findings a vendor VEX statement had answered and findings that were undetermined. A remediation view that omits rows is the one report whose shortness reads as good news, so they are counted. - writeRemediation's doc comment claimed the fix clause is skipped when nothing is fixable. An autofix commit had already changed that, correctly -- a fully-patched image is the case a reader most wants confirmed. The comment, the README and the test name now say what the code does. - fixed_version had omitempty, which erased the difference between "no patch has shipped" and a scan run before the field existed. The text report goes to the trouble of printing "no fix" rather than a blank for exactly this reason. - The --help example for --format fixplan used debian:12, which is fully patched and renders the degenerate case. Verified against debian:12.0: 138 of 292 fixable, 28 upgrade rows, 86 advisories cleared -- unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
What
Parse the
fixedranges vexscan already receives from OSV but never read, and turn them into the report's one actionable field: what to upgrade to.Two things ship here, sharing the same OSV-parsing work:
1.
FIXED INcolumn + remediation summarysidone), joined onto findings by source package.FIXED INcolumn that earns its place likeEPSS/KEV— it appears only when a section has at least one published fix.N affected: N unique advisories, N fixable, N with no fix yet. Thefixableclause is dropped entirely when nothing is fixable, so it never prints0 fixable.no fixis kept deliberately distinct from a blank cell: one is data (acknowledged, unpatched), the other is missing data.2.
--format fixplanA remediation-first view that reorganizes affected findings by the action that clears them — one row per upgrade instead of one per advisory:
A package with many advisories fixed across different point releases collapses to one row targeting the newest version, since distro point releases are cumulative. That needs version ordering, which the tool otherwise never does (OSV decides affectedness server-side), so it's scoped to ecosystems it can order confidently:
internal/debver— a correct reimplementation of dpkg'sverrevcmp(epochs,~, numeric-vs-lexical runs), unit-tested against known orderings.NO FIX YET, so the plan can't read as complete when it isn't. Rows are sorted worst-first (KEV → severity → most cleared).Verification
internal/debver, the fixed-version overlay, OSV parsing/scoping, theFIXED INcolumn, the remediation summary, and fixplan grouping/collapse/fallback.go vet,gofmtall clean.debian:bookworm-20230919(28 clean upgrades,libc6 → deb12u14 clears 24) and currentdebian:12(0 fixable, everything underNO FIX YET— correct).Files
fixplan.go,internal/debver/(new);internal/osv/osv.go,internal/analyze/analyze.go,internal/ecosystem/ecosystem.go,report.go,main.go,README.md(changed); plus tests.