DWG TrueView and Navisworks Freedom were blind spots — viewers, installed far
more widely than the authoring tools and just as exposed, since both parse
untrusted CAD files (CVE-2025-1658/-1659/-1660 are DWFX parsing bugs).
cvelistV5 is not optional for Autodesk: the three CVEs from ADSK-SA-2026-0009
(CVE-2026-16463, -16465, -17550) carry no NVD data at all, so the CPE path
cannot see them at any point. Both paths are wired up for all four products.
Two version notations occur and both compare correctly as numbers: older
records say "2026 .. <2026.1", newer ones "2027.0.0 .. <2027.1.0". AutoCAD and
AutoCAD LT are listed separately in the records but carry IDENTICAL ranges,
verified across CVE-2025-5046, -8894 and CVE-2026-17550; kept apart in case
they ever diverge.
Language packs are the trap here. They ship as separate inventory entries with
the SAME version as the application — "AutoCAD LT 2022 Language Pack" at
24.1.51.0, and Navisworks Freedom 2025 has twelve of them across Deutsch,
Italiano, Français, Português, Español, Korean and more. Each would have
collected the full CVE set of its product, exactly like Adobe's dictionary
pack. Excluded, along with Material Library and Single Sign On Component.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A complete blind spot, and the reason is a units mismatch rather than a
missing entry. Autodesk versions its products by release YEAR and NVD lists
them that way too (autodesk:autocad:2018), but the inventory reports the
internal build — AutoCAD LT 2026 installs as 25.1.60.0. Comparing a build
number against a year matches nothing, so adding the product alone would have
changed nothing.
The year is therefore read from the NAME ("AutoCAD LT 2026 - Deutsch
(German)"), the same idea as name_ver for .NET and Python, with a year pattern
instead of a dotted version. Where the name carries no year — "AutoCAD LT
Private" — nothing is scanned: guessing one is worse than missing the product.
LT and full AutoCAD are separate CPE products and stay apart. The companions
are excluded by anchoring, each having its own unrelated version: Autodesk
Access, CER, Genuine Service, Identity Manager, and "AutoCAD Open in Desktop".
cvelistV5 pairs are deliberately not added yet — NVD's AutoCAD coverage looks
complete on inspection, and an unverified second path is a false-positive risk
rather than a gain. Worth revisiting if a CVE turns up that NVD has not
enriched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Illustrator, Photoshop, InDesign and Bridge were blind spots — nothing looked
at them at all, on software that ships dozens of code-execution CVEs per
patch day (APSB26-42, -51, -40, -32, -39, -89).
Both sources carry them well, so both paths are wired up. cvelistV5 names two
of the four with a "Desktop" suffix ("Photoshop Desktop", "InDesign Desktop")
and two without ("Illustrator", "Bridge"), which is not a pattern — each name
is listed. Verified against CVE-2026-34661, -27289, -27283 and -34630.
The inventory names carry a YEAR the CVE records never mention ("Adobe
Illustrator 2026" at version 30.1), so the year is ignored rather than parsed
— the version field is what gets compared. Two InDesign generations can sit on
one host and both resolve to the same product, each judged on its own version.
Anchored, and the helper components that ship alongside are excluded:
AdobeNotificationClient, Adobe Refresh Manager and AdobeAcrobatDCCoreApp are
not the products and carry unrelated version numbers — the same trap the
dictionary pack sprang last commit.
Index key bumped to v16.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
VMware Tools is the clearest case for running both sources. NVD enriched
CVE-2025-41244 with a CPE and marked CVE-2025-41246, -41239, -22247 and
-22230 "NOT SCHEDULED" — the CPE path can never see those four, on software
that sits on every virtualised Windows endpoint. cvelistV5 has all of them,
under three spellings: VMware/Tools, VMware/VMware tools, and n/a/VMware
Tools. All three are mapped; both paths are wired up.
Adding the product alone would not have worked, because VMware writes its
affected versions as placeholders — "13.x.x.x", and "12.x.x, 11.x.x" naming
TWO release lines in a single field. Parsed as digits the second becomes
(12, 11), which is not a version: a 12.4 host compares BELOW that floor and
drops out of its own range, and so does an 11.3 host. Both would have been
reported unaffected while the record says the opposite.
_wildcard_floor now reads the concrete part in front of each `x` and takes the
lowest — the floor of the whole statement. Versions without a wildcard are
untouched, so nothing else changes. This is general, not VMware-specific;
any CNA writing "9.x" gets read correctly now.
Index key bumped to v15.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Adobe entries added in 2dc60be and 3e6c15c matched the product name
anywhere in the string, so they also matched every add-on that mentions it:
"Asian Language And Spelling Dictionaries Support For Adobe Acrobat Reader",
language packs, font packs. Those ship their own version numbers that look
exactly like old Reader builds — the dictionary pack is 23.008.20421 — so each
one collected the full set of Reader CVEs. The tester saw CVE-2026-48373
listed eight times on one host, and not once for the Reader itself.
The name must now START with the product, which drops the "Support For …"
add-ons, plus an explicit exclusion for the packs that do lead with it
(language pack, dictionaries, spelling, font pack). Same pattern as the
Exchange language packs and the Teams add-in — a vendor putting its product
name inside an accessory's name is the recurring shape here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The three inventories do not see the same moment in time. Wazuh syscollector
is close to live, Intune depends on how aggressively the tenant pushes an
inventory refresh, and Defender TVM's software list trails by days. Any source
could reopen a patched finding immediately, so the normal sequence after
patching a host was: the fast source stops reporting the CVE and the finding
closes — then the slow source runs, still holding its old picture, and reopens
it. Next night the same again. The finding flaps and its change history
describes our polling rather than the host.
A source may now only reopen a finding once its own lag has had time to pass:
three days for Defender, one for the Graph-based ones. Sources that read a
live inventory themselves — wazuh_sync, app_scan, msrc, nessus — keep
reopening immediately, because when they say it is back, it is back.
The grace is a window, not a veto: past its lag the slow source is believed
again, so a host that genuinely regressed still reopens.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both scores multiply the CVSS in, so a CVE without one came out at zero:
priority 0, CPR "—". A Critical Chrome CVE therefore sorted below a medium
that happened to carry a score, in every priority-ordered list and in the
digest mail. Google states "Chromium security severity: Critical" in prose and
NVD frequently never scores those records, so this is not a rare corner.
The severity band's FLOOR now stands in when no score exists — critical 9.0,
high 7.0, medium 4.0, low 0.1. The floor, not the middle: the estimate can
only ever understate a real score, never inflate one. An unscored critical
lands at CPR 74 against 78.8 for a real 9.8, and above a scored medium, which
is the ordering that was wrong. A real CVSS always wins over the estimate.
Also silences the Settings page for read-only users: every
/api/v1/settings/<key> is admin-only and the page requests a dozen on open,
one 403 each. Same central gate as /auth/users.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A complete blind spot: Wazuh does not detect it (wazuh/wazuh#36200), so an
Exchange SE host reported nothing at all. Both of our sources carry the data —
cvelistV5 states "15.02.0.0 .. lessThan 15.02.2562.043" for CVE-2026-42897, and
NVD has the matching CPE — so this only needed a registry entry on each path.
The leading zeros that make this awkward elsewhere are irrelevant here: Wazuh
reports 15.2.2562.27 and the record says 15.02.2562.043, and both sides are
parsed as numbers rather than compared as strings. That difference is exactly
what the Wazuh issue describes as the cause on their side.
The name regex is ANCHORED and exact, which is the whole risk of this change.
One Exchange host lists five kinds of row — the product itself, "Microsoft
Exchange Server", a dozen language packs (all still on the RTM build), and
"Hotfix Update for Exchange Server Subscription Edition (KB5066373)" whose
version field is the literal "1". Only the product entry carries the real
build; the KB row would compare below every fix ever published and flag the
host permanently.
Index key bumped to v13. tests/test_exchange_se.py pins all five row types
plus the version boundary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A record stating one affected version and no bound at all was dropped
entirely, so the CVE never entered the index and nothing but Defender could
report it. CVE-2026-14266 is exactly that shape: "7-Zip 20.01, status
affected", nothing else.
They were dropped to avoid over-matching, which is a real risk — an unbounded
floor swallows every version above it. A single exact version has no such
problem when it is read as the closed range [v, v]: it matches that one
version and nothing else, which is precisely what the record claims. 20.01
hits, 20.01.0 hits (same version, written differently), 19.00 and 26.01 do
not.
Index key bumped to v12; the cached index has none of these entries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reading ESTABLISHED sockets as evidence (943569f) exposed a dedup key that
was too narrow: it included the protocol, which was harmless while only
listeners counted, but a busy host has many established sockets on the same
service port, and tcp vs tcp6 already made a single listener look like two
services.
The tester's domain controller listed "RDP :3389" four times and LDAP four
times. Since each additional entry adds 40% of its weight, the exposure score
inflated to 100 for what is one RDP and one LDAP service. Deduplicating by
port alone fixes both: one service, one entry, one weight.
Also feeds Recent Critical from four narrow server-side queries (critical,
high, KEV, EUVD) instead of filtering the 300 newest rows in the browser. A
batch of low-severity CVEs — Chrome publishes dozens at once — fills that
window completely and empties the widget, which no amount of extra depth
fixes; only filtering before the limit does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two unrelated defects, both found on the same test estate.
MSRC states the Teams fix as 25060212043 — one eleven-digit stamp — while
Teams itself ships 26183.1003.4002.4460. Python compares those tuples element
by element, so 26183 < 25060212043 came out True and a current Teams was
reported vulnerable (CVE-2025-49731). Builds with different segment counts are
not the same numbering scheme and no ordering between them means anything, so
they are no longer compared at all. The product name filters were NOT at
fault here: "Microsoft Teams Meeting Add-in for Microsoft Office" and
"Microsoft Teams VDI Dim-plugin" both resolve to nothing, as intended.
Separately, both port-based scorers required a LISTENING socket, and Wazuh's
syscollector does not always report one. On a Windows Server 2025 domain
controller netstat showed 3389 ESTABLISHED while syscollector returned no
listening entry at all, so RDP scored zero exposure and the DC role went
undetected on a live domain controller. An ESTABLISHED socket whose LOCAL
port is a known service port is an inbound connection, which proves the
service is running just as well. Outbound connections carry an ephemeral
local port, so they cannot be mistaken for one — and only ports that are
actually asked about qualify, never an arbitrary high port.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every export queried the vulnerabilities table raw, so all four disagreed
with the dashboard they were exported from, in the same three ways:
- Findings on decommissioned and inactive assets were counted. Those hosts
are deliberately hidden on screen, and the gap grows with every machine
retired.
- EOL- and NESSUS-PLUGIN- pseudo-CVEs were counted as CVEs. They are real
work items but they are not vulnerabilities, and they have their own
surface in the UI.
- The executive summary counted severities across ALL statuses, so a
remediated estate still reported hundreds of "Critical Severity" — the
patched ones. A reader takes those as outstanding work. They are now
explicitly labelled and scoped to open findings.
"Top Priority Risks" was the worst of it: a LIMIT 5 with no ORDER BY, so the
database returned any five critical rows it liked under a heading promising
the five that matter most. It now ranks by priority score, then CVSS.
Two exports could also take the server down on a large estate. Patching
Progress put every patched finding of the last 30 days in the table — one
reconcile here closed 14703 at once — and the ISO report loaded every
non-compliant finding into memory just to call len() on it, while printing
20. Both now count in the database and list a bounded, ordered page. The CSV
streams in batches instead of materialising the whole file first.
The scope rule lives in app/services/report_scope.py so the reports cannot
drift apart again, and so it can be tested without the web stack —
tests/test_report_scope.py asserts it against the emitted SQL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Checkmk agent was invisible to every path, and the reason was one
character: it numbers patches inside the version string (2.4.0p12), which the
dotted-numeric rule in _clean_version rejected outright, so the version was
discarded before any source was consulted. Wazuh does not detect it either
(wazuh/wazuh#35646), which left it a complete blind spot.
Nothing else had to change — _vtuple already reads 2.4.0p12 as (2,4,0,12), so
the comparisons were correct all along. Both sources state their bounds in the
same notation (lessThan "2.4.0p13"), and both are now wired up: the cvelistV5
pairs for the branch-bounded ranges, the NVD CPE registry entry as the second
route.
The exclusion that made the rule strict stays intact: Linux distro versions
(1:3.2, 4.6.5-3.el8, 2.43.0.windows.1) are still rejected — they belong to
Wazuh and pushing them at NVD produces noise. tests/test_checkmk_version.py
covers both halves, including that an older release branch answers only for
itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chrome CVE records carry no metrics block whatsoever — Google puts the
severity in the description instead: "… (Chromium security severity:
Critical)". Every fresh Chrome finding therefore landed on the neutral
'medium' placeholder, so a Critical sandbox escape sorted level with a Low UI
glitch, both in the queue and in the daily digest mail. For a team that
triages by severity that is worse than no data.
The severity word is now read from the description when no metrics block
exists. No score is invented — only what the vendor stated, and a real CVSS
always wins over the prose. Once NVD publishes a score, the existing
CVSS-sync takes over as before.
Existing rows heal on the next scan: the upsert already lifts a finding off
the medium placeholder when a source reports a better severity.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Citrix published-app delivery leaves a registry stub for software that is NOT
installed on the box: a self-chosen product name and a placeholder version
("SAP Business Client 1.0", vendor "Delivered by Citrix"). A 1.0 reads as
ancient against any CVE data, so whatever matches it reports decade-old CVEs.
The filter existed, but each scan path applied it for itself — so every path
added later started out unfiltered. Three had it, four did not, including the
SAP path added yesterday, which is how a published-app stub turned into a
finding again.
Filtering the fetched inventory once, before any path sees it, is the only
version of this that stays true as paths are added. The EOL check fetches its
own inventory and now filters it the same way; a placeholder 1.0 reads as
end-of-life there too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NVD enumerates one CPE per SAP patch level, which can only ever describe the
levels that existed when the record was written — a host on a newer level
falls through. cvelistV5 states the bound instead ("7.70 PL0" ..
"7.70 PL11"), so it keeps answering as SAP ships more levels.
The records contradict themselves, and the resolution is the whole point of
this change: CVE-2023-32113 carries BOTH "<= 7.70" (the entire release) and
"7.70 PL0".."7.70 PL11". A host on 7.70 PL26 is affected by the first
statement and patched by the second. The patch level is the precise one, so
it decides for its own release, and the release-wide bound only answers for
releases that have no patch-level entry at all. Entries are therefore weighed
per CVE, not row by row.
Both vendor spellings are matched — records days apart say "SAP SE" and
"SAP_SE".
Index key bumped to v10 (new sap bucket). tests/test_sap_cvelistv5.py pins
the contradiction, the fallback, and the unreadable-level case against the
verbatim version objects.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With the real syscollector rows in hand the level-only match turns out to be
a false-positive generator. Every SAP release carries the same patch level
NUMBERS — NVD lists gui_for_windows 7.70:patch_level17 right next to
8.0:patch_level1 — so the tester's host (SAP GUI for Windows 8.00 64bit,
Patch 17) matched every 7.70 CVE that happens to have a patch_level17 entry.
The release is now compared as well, and numerically: Wazuh reports "8.00"
where NVD writes "8.0", so a string compare would have rejected the host's
own release.
The parser is confirmed against the verbatim inventory strings, which differ
per product — Business Client states the level in the version field
("8.00 PL26") while SAP GUI states a compilation there and puts the level in
the NAME ("SAP GUI for Windows 8.00 64bit (Patch 17)"). Both are pinned in
the test, along with "Compilation" not reading as a level despite containing
the letters p-l.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It was never in scope — it came in from the Wazuh issue's product list — and
it does not fit the patch-level model the other two SAP products use: AfO
ships 2.8.x builds and states no patch level anywhere, so the match could
never fire for it.
Worse is what NVD says about it. CVE-2021-38175 lists
`cpe:2.3:a:sap:analysis_for_microsoft_office:2.8:*` with a wildcard update
field and no range whatsoever, so any 2.8 build would count as affected
forever, no matter how many patches it has. cvelistV5 carries the honest
bound ("< 2.8") but the cvelistV5 path has no SAP pairs yet, so there is no
correct source to scan this product with today.
A test now pins that a wildcard update field never matches, for any patch
level — the guard that keeps this class of CPE from becoming a false positive
if another SAP product is added later.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SAP GUI, Business Client and Analysis for Office were a complete blind spot:
cvelistV5 carries no CPE for them, and Wazuh misses them too
(wazuh/wazuh#30334), so nothing in the pipeline ever looked at them.
NVD has the data, but not as a range — SAP enumerates ONE CPE per patch
level in the update field (cpe:2.3:a:sap:gui_for_windows:7.70:patch_level4),
verified against CVE-2023-32113 with 15 entries and CVE-2021-38150 with 68.
So "affected" is set membership, not a comparison, and the existing range
check cannot express it: it compares the base release, which is identical for
a fully patched host and an unpatched one.
The level is read from either inventory string ("7.70 PL 12" in the version
field, "Patch 4" in the product name — both spellings appear in the Wazuh
issue). When no level can be read the product is SKIPPED, not scanned: the
base release alone matches every CVE ever filed against it, so one unreadable
string would otherwise produce a page of false positives. The patch level is
part of the cache key, so PL 12 can't be served PL 3's answer.
tests/test_sap_patch_level.py pins both halves against the real criteria
strings, the unknown-level case included.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Apple OS findings could only ever come from NVD, and NVD's Apple enrichment
lags: CVE-2026-28911 sat at "UNDERGOING ENRICHMENT" with no configuration at
all, so the CPE path had nothing to match and the cvelistV5 path refused
every non-Windows asset outright. Both iPhones and Macs therefore showed
these CVEs only where Defender happened to report them — with no package or
installed version, since Defender carries neither.
cvelistV5 has the data on day one (Apple / macOS / lessThan 14.8.8), so the
OS scan now reads it there too.
Apple states each parallel release train as its own ZERO-floor range on the
same CVE — CVE-2026-64721 carries lessThan 14.8.8, 15.7.8 AND 26.6 — so a
Sonoma 14.7 host matches all three at face value and the finding would claim
"fixed in 26.6", an upgrade that host will never receive. The major version
picks the train, the same role _win_family plays for Windows builds.
tests/test_apple_os_train.py pins that behaviour.
Index key bumped to v9; the cached index has no apple-os bucket.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>