Skip to content

feat(scan): judge each CVE against the installed version - #58

Merged
maksimtech merged 1 commit into
mainfrom
feat/a-scan-that-knows-the-installed-version
Oct 17, 2026
Merged

maksimtech merged 1 commit into
mainfrom
feat/a-scan-that-knows-the-installed-version

Conversation

@maksimtech

Copy link
Copy Markdown
Owner

Audit finding PR-01 (critical) of 2026-10-17, first step: the scan judges CVEs against the installed version.

What was wrong

A scan found the CVEs that name a product and could not say which affect what is installed. The README said "tells you which vulnerabilities affect it". For Notepad++ the recorded NVD answer holds 21 CVEs; for 8.5.0, 11 of them concern other versions or another product, and all 21 were listed and ranked as work to do.

affected.py already knew how to answer (since 2026-09-28) and was deliberately not wired in, waiting for the watchlist columns of "the versionradar design, §5". That text was never committed and could not be found anywhere (E:\Dev, WSL, git history, notes).

What changes

  • docs/DESIGN.md: the design the code referred to, rebuilt from what affected.py, inventory.py and test_priority_wiring.py already committed to. §5 covers the columns, the three verdicts, what the scan prints, what is stored and the order of work (01a, 01b, 01c). The three references in the code now point to it.

  • Watchlist: five nullable columns with no default (cpe, vendor, product_id, installed_version, channel), migrated into existing databases.

  • patchradar add <name> --version <v> --cpe <cpe>:

    • updates an existing entry;
    • refuses a version affected.py cannot order;
    • refuses a malformed CPE. notepad++ is refused with NVD's notepad\+\+ shown, because NVD would match nothing and the scan would read as clean.
    • The CPE is never guessed from the name (cpe.py).
  • affected.judge: affected, not affected or not evaluable, from the record's own NVD configurations, which nvd._parse_item now keeps (an empty list while NVD has not analysed the CVE). "Cannot tell" never becomes "not affected". This covers:

    • no configurations;
    • a CVE from MSRC or KEV alone;
    • a version from another scheme.

    Range.comparable separates "outside the range" from "cannot be ordered", and closed_by is factored out of summarise.

  • scan:

    • the table lists affected and not evaluable CVEs, with a Verdict column; --all adds not affected;
    • one line gives the split, one says what an update closes;
    • the NVD caveat is always printed;
    • entries without a version are scanned by name as before, and named at the end;
    • the total keeps its meaning (CVEs found by name).
  • list shows version and CPE.

  • Not in this PR, per DESIGN §5.6:

    • storing the verdict, judging KEV entries via NVD, and the API (PR-01b);
    • the web UI and import (PR-01c).

Tests (strict TDD)

  • New files:
    • test_cpe.py;
    • test_watchlist_versions.py, with a hand-written pre-migration database;
    • test_affected_verdict.py, on the recorded Notepad++ NVD payload. It agrees with summarise: 10 affected and 11 not affected at 8.5.0, five fixes at 8.9.5;
    • test_scan_versioned.py, a full CLI scan against the same recording replayed with respx: MSRC 404, the KEV conftest catalogue. Derived variants (configurations removed, payload reduced to the affected ten) are declared in each test.
  • All seen failing before the code. Guards added after the first green run (unbounded comparable, --all with nothing hidden, caveat on an empty answer, 5 of 5, set nothing) each kill their mutant: 26/26 mutants killed.
  • test_cli_escaping.py: add called as a function now passes its new options. The list test no longer stubs get_watchlist: the hostile row is written straight into the test database, the way an older version left it.
  • Full suite on Windows: 2169 passed. ruff and mypy clean.

🤖 Generated with Claude Code

A scan found the CVEs that name a product and could not say which affect
what is installed: for Notepad++ 8.5.0 the recorded NVD answer holds 21,
11 of them about other versions or another product, and all 21 were
listed and ranked. affected.py could answer and waited for the watchlist
columns of "the versionradar design, section 5", a text never committed.

- docs/DESIGN.md rebuilds that design from what the code committed to.
- Five nullable watchlist columns (cpe, vendor, product_id,
  installed_version, channel), migrated into existing databases.
- `add --version --cpe`: the CPE typed by a person and checked for form
  (notepad++ is refused with NVD's notepad\+\+), a version refused when
  it cannot be ordered.
- affected.judge: affected, not affected or not evaluable, from the NVD
  record's own configurations, which nvd._parse_item now keeps. Cannot
  tell never becomes not affected.
- scan lists affected and not evaluable, --all the rest; it gives the
  split, what an update closes, and always the NVD caveat; entries
  without a version are scanned by name and named.

Written test first and seen failing; the guards added after the first
green run each kill their mutant, 26 of 26.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
if fields:
# Column names from the literal keys above, never from input.
assignments = ", ".join(f"{column} = ?" for column in fields)
await db.execute(f"UPDATE watchlist SET {assignments} WHERE name = ?",
@codspeed

codspeed Bot commented Oct 17, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ 11 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 17 untouched benchmarks


Comparing feat/a-scan-that-knows-the-installed-version (df6f195) with main (a66861a)

Open in CodSpeed

@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Oct 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@maksimtech
maksimtech merged commit 2e2fdc8 into main Oct 17, 2026
25 checks passed
@maksimtech
maksimtech deleted the feat/a-scan-that-knows-the-installed-version branch October 17, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants