Commit Graph
693 Commits
Author SHA1 Message Date
vulncheck d78baeaf93 fix(aruba): map "(AOS-10 & AOS-8)" to aruba-os, mark the Instant AP record reviewed
The 17.10 Netdisco run ended "1 ERR": the spelling guard from 89800c5c
found three HPE names no Aruba key knew. Two causes:

"HPE Aruba Networking Wireless Operating System (AOS-10 & AOS-8)"
(CVE-2026-23601, -23808, -23809) is the controller line, the same
product as CVE-2026-23812's "(AOS-8 & AOS-10)" with the generations in
the other order. The pattern only knew that order, so no controller
got these three CVEs. The aruba-os product_re now takes both.

"HPE/Hpe Aruba Networking InstantOS and Aruba Access Points running
ArubaOS 10" (CVE-2024-42393..42396) is Instant AP firmware, bounded at
8.10/8.12, which are controller build numbers too; HPE says controllers
are not affected. Mapping it would put findings on the wrong box, so it
goes into _ARUBA_KNOWN_UNMATCHED (lowercase covers both spellings).

The test builds these two records in an index of their own: their
ranges overlap CVE-2026-44857's builds that the shared fixture asserts on.
2026-10-17 11:59:15 +02:00
vulncheck 697360a093 fix(scheduler): run EOL, M365, app scan and MSRC as one chain after the Intune sync
The four post-inventory jobs were separate crons (03:00 EOL, 03:10 M365,
03:20 app-CVE scan, 03:50 MSRC), each waiting on the Intune sync's lock.
On a night the sync overran (16.10.2026: 07:38-12:17) all four started the
moment it ended, in no order. The MSRC job's remediation catch-up then ran
before the app scan whose Edge findings it exists for, so those stayed
without a KB until the next night, and app scan and MSRC wrote Windows
findings on the same assets at once (different job slots, no mutual
exclusion).

Cause: every waiter polled the same lock and none waited for its
predecessor; the clock order only held when the sync finished before 03:00.

post_inventory_scans_nightly (03:00) now waits once, then runs EOL -> M365
-> app scan -> MSRC in turn. Each step keeps its own job slot and Intune
wait (a no-op unless a sync starts mid-chain); a step that raises costs
only itself.

The schedule comments in scheduler.py and the job tables in ARCHITECTURE.md
and README.DEV.md carried times from before several reschedules and called
them UTC; the cron times are the container's local time (TZ from TIMEZONE).
Both are corrected.

Test: tests/test_nightly_post_inventory_chain.py fails on the old code (four
separate crons, no chain) and passes now; the schedule asserts in
test_nightly_index_order and test_msrc_edge_chromium_cve point at the chain.
The full suite fails on the same 13 order-dependent tests with and without
this change.
2026-10-17 11:30:28 +02:00
vulncheck 5f5e31a399 fix(scheduler): run overlapping jobs late instead of dropping them, one run per kind
APScheduler ran with its defaults: a 1 s misfire_grace_time, checked again
in the worker thread, on the event loop's default pool (min(32, cpus+4)
threads, shared with asyncio.to_thread). A job that queued for a free
thread, or came due while the loop was blocked for a second, was skipped
("Run time of job ... was missed by") and never caught up. On a night when
the Intune sync ran past noon and four jobs waited for it, the pool was
full and every job due after that was lost.

The scheduler now has its own 64-thread pool, with misfire_grace_time=None,
coalesce=True and max_instances=1. A late job runs late, and a backlog runs
once.

The nightly scans and the Data-jobs and sync buttons also ran the same code
with no lock between them, so two writers could hit the same findings
(deadlock, or a uq_vuln_cve_asset race aborting a run).
database.job_slot(name) is one advisory lock per kind, taken before the job
opens its Session. A second run waits for the first to finish and then
runs; after 6 h it starts anyway. It covers EOL, M365, app-CVE scan, MSRC
scan and refresh, exploit intel, Vulnrichment, the vuln-index rebuild, the
Wazuh vulnerability sync and Nessus. Different kinds still run in parallel.

Test: tests/test_job_concurrency.py fails on the old code (all 41
overlapping jobs dropped, 22 entry points without a lock) and passes now.
The full suite fails on the same 21 tests as before this change.
2026-10-17 10:12:19 +02:00
vulncheck 1b631af11e fix(scheduler): hold the post-inventory nightly jobs until the Intune sync ends
The 02:10 Intune sync (with Defender TVM) can outlive the 03:00 EOL,
03:10 M365, 03:20 app-CVE and 03:50 MSRC slots on a large estate (field
report 16.10.2026: 07:38-12:17 for 1639 assets). APScheduler ran them on
its thread pool alongside the sync: two writers on the same assets and
findings in different orders, and a scan of an inventory still being
refreshed.

Each of those jobs now waits until no sync holds the Intune advisory lock,
polling every 60 s for at most 2 h, then runs anyway with a warning so the
estate does not lose a night. The probe reads pg_locks instead of taking
the lock, so a sync starting at that moment is never turned away as
"already running". A GUI app scan does not wait; it logs a warning and
reports intune_sync_running in its stats.
2026-10-16 16:49:56 +02:00
vulncheck a4b5312f85 fix(defender): read TVM CVEs tenant-wide instead of one call per machine
The sync asked GET /machines/{id}/vulnerabilities once per machine. That
API allows 50 calls a minute and 1500 an hour, so a tenant with more
machines than that ran into 429 for every machine past the hourly budget.
The client gave up after its retries and those machines' CVEs were
neither written nor resolved, while the run took hours (field report:
1639 assets, Intune + Defender 07:38-12:17).

The (machine, CVE, product) pairs now come from the tenant-wide
/vulnerabilities/machinesVulnerabilities list, paged by $top/$skip so a
full page without a nextLink is not taken as the end. CVSS and
description, which that list lacks, are copied from existing findings of
the same CVE and looked up in the TVM catalogue only for CVEs new to the
estate (capped per run, fill-only on later runs). Every MDE call is paced
at one per 2.4 s, under both limits. The upsert reads an asset's findings
once per machine instead of once per CVE.

Simulated against the documented limits for 1639 machines: before, 62 min
of API time, 73 429s and 10 machines dropped; after, 4 API calls in steady
state, no 429, none dropped.
2026-10-16 16:33:09 +02:00
vulncheck 89800c5cb6 fix(aruba): read the Mobility prose records and flag unknown HPE spellings
CVE-2024-26304, -33511 and -33512 reached no controller. HPE files the
2022-2024 Mobility records under "Aruba Mobility Conductor (formerly
Mobility Master); Aruba Mobility Controllers; WLAN Gateways and SD-WAN
Gateways managed by Aruba Central" (and "ArubaOS Wi-Fi Controllers and
Campus/Remote Access Points") and states each branch as prose, "ArubaOS
10.5.x.x: 10.5.1.0 and below". Neither the name nor the shape was read,
and NVD has not analysed those three. Both names now map to aruba-os, and
blocks with prose go through aruba_switch_entries (branch as floor). The
shared extractor took the sentence as a version and made it its own
bound, which the scan backstop dropped. A lookbehind keeps the SD-WAN
compound build ("8.7.0.0-2.3.0.8 and below") from becoming a 2.3 branch.

The guard: every index build collects HPE product names that look like
Aruba firmware but match no key, logs them, and the Netdisco sync puts
them on its run errors (Sync Health amber, no mail). The five names
reviewed and left out on purpose (four Instant/AOS-10 AP lines, the
9200/9000 series record NVD covers) are in _ARUBA_KNOWN_UNMATCHED, so it
is silent on today's corpus. The list lives in memory and is empty after
a restart until the next build.
2026-10-16 16:12:33 +02:00
vulncheck c815d0e817 fix(app-scan): retract a dead Wazuh agent's own findings once Intune owns the host
Wazuh's vulnerability sync only asks active agents, so on a dual-enrolled
host whose agent died (field case asset #276, keepalive 2025-04-03) the
"wazuh" source was never retracted and those findings stayed open forever.
Since f528374e the app scan reads Intune for such a host; after it has read
Intune's whole list it now drops the "wazuh" source as well. A finding closes
only when no source is left, so CVEs that app-scan, MSRC, Defender or Nessus
still report stay open. Doing it in the same pass avoids closing findings
Intune still shows and then being blocked by Intune's 7-day reopen grace.
2026-10-16 13:21:49 +02:00
vulncheck 18744d9376 fix(aruba): match HPE's current product spellings and "<=" bounds
CVE-2026-76742 (AOS-S web-UI auth bypass, HPESBNW05156) was not detected
on ArubaOS-Switch 16.11.0016/0027/0029. HPE now names the product
"AOS-Switch (AOS-S)", which the anchored aruba-switch pattern rejected,
so the record never entered the cvelistV5 index; NVD still has it as
"Awaiting Analysis" with no CPE, so no source saw it. The same applied
to CVE-2026-76741..76749 and to other spellings: "ArubaOS (AOS)",
"HPE Aruba Networking AOS", "... Operating Systems (AOS-8 & AOS-10)",
"ArubaOS-Switch Devices" and a typo'd "HPE Aruba Networkign AOS-CX".

A second shape was dropped on purpose until now: lessThanOrEqual
"<=10.6.0.2" with the floor as prose in `version` (CVE-2024-42505,
CVE-2025-25042, -27080 and others, none with an NVD CPE).
aruba_affected() normalises it for the Aruba keys only; the shared
_clean_bound is unchanged, and the branch guard still confines an
entry whose floor cannot be read.

The NVD half now also queries arubanetworks:aos-cx and
aos-cx_firmware, under which NVD filed the 2021/2022 AOS-CX CVEs.

Checked against all 634 HPE cvelistV5 records: no non-switch or
non-controller product enters an Aruba key, and families never cross.
2026-10-16 13:05:08 +02:00
vulncheck f528374ef5 fix(app-scan): take Intune's software list when the Wazuh agent is dead
Field report 2026-10-16: asset #276 is still registered in Wazuh with a
last keepalive of 2025-04-03 while Intune reports it daily. Wazuh won the
inventory precedence whenever wazuh_agent_id was set, so the app-CVE scan
and the software view kept deciding from syscollector data frozen 18
months ago, and Intune's current detectedApps was never read. Findings
from that data could not close.

Each inventory now keeps its own stamp (assets.wazuh_last_seen,
intune_last_seen, migration 063). asset_inventory() hands a dual-enrolled
host to Intune when the keepalive is outside the grace window and Intune
checked in after it. A live agent keeps the precedence, so it does not
follow the order the syncs ran in. Rows without both stamps keep Wazuh.
run_app_cve_scan, the software view and the Intune sync's own retraction
use the same decision; a dead agent's browser extensions are not read.
2026-10-16 11:50:42 +02:00
vulncheck ee01abab12 fix(assets): keep a host Intune still sees active when its Wazuh agent died
Field report 2026-10-16: asset #276, whose Wazuh agent last kept alive on
2025-04-03 but which Intune reports daily, switched between ACTIVE and
INACTIVE with every sync. The Wazuh agent sync overwrote Intune's fresh
check-in in last_seen with the old keepalive and judged the status on it,
the nightly reconcile reads last_seen for Wazuh hosts and deactivated it
too, and the next Intune sync revived it.

last_seen now only moves forward (advance_last_seen), in the Wazuh agent
sync and in the Intune sync, so the newest proof of life from any source
decides. A Wazuh-only host past the grace window still ages out. The
Wazuh audit reason names the keepalive and who stamped last_seen.
2026-10-16 11:30:12 +02:00
vulncheck c90023121f feat(app-scan): detect Ghostscript CVEs on the Windows installer
"GPL Ghostscript" 10.05.1 (Artifex Software Inc.) got no CVE-2026-39919:
Ghostscript had no row on any path, and NVD has the record without a
configuration. cvelistV5 now pairs Artifex's three vendor spellings
(0 .. < 10.08.0), and NVD is asked for artifex:ghostscript.

Only the installer name is matched, so the distro package, the fonts
package, GhostPCL and GhostXPS stay out. Red Hat's records that write the
fixed release or prose into the version field are not read, and
gpl_ghostscript is not asked because its looser 2018 bounds would flag
9.24/9.25. On the full cvelistV5 ZIP the index gains 9 entries and every
other key is unchanged.
2026-10-16 11:01:38 +02:00
vulncheck 56afed055a fix(jev): commit each answer so the triage no longer deadlocks
The nightly Jev triage committed every 50 CVEs, so its row locks on
`vulnerabilities` (all rows of each answered CVE, across hosts) stayed held
across up to 50 HTTP calls. A job writing the same rows in another order
closed a lock cycle; Postgres killed the triage (2026-10-16 06:05), which
rolled back answers already paid for and skipped the audit row.

Each answer is now committed before the next call. A database error stops
the run and is recorded in the audit row as "stopped: database: ...".
2026-10-16 10:40:09 +02:00
vulncheck c9c955fc26 fix(zoom): read an MSI build as its release from Zoom's installer URLs
Field report 16.10.2026: Zoom Workplace (64-bit) 7.1.43453 got no finding
for CVE-2026-108184 (ZSB-26021, fixed in 7.1.9) or CVE-2026-108185
(ZSB-26022). The MSI writes major.minor.BUILD, and the release 7.1.5 sits
only in the ARP "Comments" ("7.1.5 (43453)"), which neither Wazuh nor
Intune reports. A build inside the fix's own minor cannot be placed, so
zoom_undecided gave it no verdict on purpose (the known MSI-build limit).

Zoom's download URLs name both: zoom.us/client/7.1.5.43453/,
zoom.us/download/vdi/7.0.13.27170/ (VDI Client and plugins share that
line). winget-pkgs keeps a manifest with that URL for every installer.
zoom_bulletin_service.build_builds reads them on the nightly cvelistV5
build into a stored build→release table (about 150 client and 40 VDI
builds), fetching only builds it does not know yet. Only a URL of the
manifest's own build counts: 2020's prod/5.2.42619.0804/ is build.date
and maps to unknown. zoom_release rewrites a Windows host's MSI version to
"7.1.5 (43453)", the EXE's spelling, before the NVD and cvelistV5 paths
compare, so the finding shows the release like the ARP comment.

No new false positives: a build the table does not know stays undecided
as before, and the table matches the field report's comments (7.1.43453 =
7.1.5, VDI 7.0.27050 = 7.0.11, 7.0.27170 = 7.0.13), rising with the build
in every minor. The field host's Universal Plugin 7.0.11 stays clear of
ZSB-26021 (the line names the VDI Client), and its VDI Client 7.0.13 is
the fix.
2026-10-16 10:34:40 +02:00
vulncheck 9c245e2984 fix(zoom): link the security bulletin a finding came from
A Zoom finding decided by a bulletin said "Source: Zoom Security Bulletin
ZSB-26020" in its description, but External References had no link to
the bulletin (field report, CVE-2026-53420). Cause: the cvelistV5 scan
built the finding without refs, though the bulletin entry carries its ZSB
id. It now passes the bulletin URL, which _upsert merges into references
like TeamViewer's and IGEL's, so existing findings get it on the next
scan. Record-only entries get no link, and matching is unchanged. The
detail page labels the link "Zoom Security Bulletin".

The description stays fill-only: a finding that NVD or the CVE record
created first keeps that text without the "Source:" suffix.
2026-10-16 10:12:26 +02:00
vulncheck 8df6524d06 feat(msrc): detect SQL Server 2017-2025 CVEs from the GDR/CU fixed builds
MSRC states each SQL Server fix per release and servicing path, e.g.
CVE-2026-70189: 16.0.1210.3 on "SQL Server 2022 (GDR)", 16.0.4305.5 on
"(CU27)". The app scan's MSRC package pass now reads the build from
"Microsoft SQL Server 20xx Setup (...)", raised by the GDR/Hotfix (KB...)
entries, and compares it only against fixes on its own path (GDR and CU
are told apart by the build's third part). Per CVE the oldest fix on the
path decides: MSRC adds newer builds to a revised CVE (CVE-2026-32176)
without dropping the first ones.

No false positives by construction: Browser, RsFx and VSS Writer stay at
RTM and are never read, a Setup entry without the instance entry
"Microsoft SQL Server 20xx (64-bit)" is a leftover and skipped, and a
build that fits no path gets no verdict. The reconcile compares a fix on
the host's own path only, and now also retracts MSRC from rows another
scanner created first. Index key bumped to msrc_product_index_v3.
2026-10-15 13:37:46 +02:00
vulncheck fb7bfad479 fix(euvd): never cache a catalog cut short by a failed page
A failed exploited-search page (or critical-list request) left a partial
map that was stored and stamped for 24h. Enrichment clears euvd_listed on
every CVE missing from the catalog, so CVEs ENISA still lists were
un-listed. The paginated fetch now reports whether it reached the last
page; an incomplete fetch keeps the last complete cache and backs off.
2026-10-15 13:17:18 +02:00
vulncheck 04774c8d19 fix(feeds): stop repeating downloads the app already made
Follow-up to the per-device vulnrichment fix: an audit of the backend found
more feeds pulled again by every caller.

cvelistV5 (~557 MB) had three downloaders: the index build, the CVSS
cascade's stage 3 and the date backfill. Callers arriving together each
pulled the whole ZIP, and after a failed download every later caller
retried at once. _ensure_zip is now the only one: a lock with a freshness
re-check after acquiring it, a zipfile check before the swap, and a 15 min
backoff after a failure (the on-disk ZIP still serves meanwhile).

Per-CVE lookups that keep missing are remembered for 6h: the M365 title
fetch on a cvelistV5 404 (MSRC publishes days before CVE.org), and NVD /
GHSA for CVEs they answered without a score. Rate limits and network
errors are not answers and are retried.

A failed or partial index build stores nothing, which keeps the cache
stale, so every per-asset scan and every per-sync enrichment rebuilt it
during an outage. The MFSA, repo-advisory and TeamViewer indexes and the
KEV / EUVD / VulnCheck refreshes now serve the stale copy for 1h after a
failure; the nightly and forced paths still rebuild. The app scan loads
the repo-advisory and TeamViewer indexes once per run, like the IGEL ISNs.
The repo-advisory index no longer falls back to {} when a build raises:
the reconcile would read every untouched finding as patched.

MSRC CVRF documents were downloaded by the 01:30 index build and again by
the 03:50 / Sunday ingest. Both now go through fetch_cvrf_doc, a disk
cache keyed by the revision date /updates lists (max 4h old), so a
revised document is still fetched again.
2026-10-15 13:12:21 +02:00
vulncheck 7a835fc797 fix(m365): stop re-downloading the vulnrichment ZIP once per device
Field report 14.10.2026: every Intune device with Microsoft 365 Apps logged
"vulnrichment: downloading ZIP snapshot for 131 CVEs", minutes apart, each
a full 334 MB pull, followed by the same NVD and CVE.org per-CVE lookups.
Two causes. _load_via_zip_snapshot had no cache: a fresh tempdir per call.
And run_m365_for_packages (Intune and Defender, once per device) re-ran the
whole CVSS cascade and the title fetch for every CVE the device has, not
just for its new findings; the upsert also overwrote the real CVE title
with the placeholder, so the text had to be fetched again on every run.

The vulnrichment ZIP is now disk-cached for 6h, swapped in only once it is
a valid ZIP, and a failed refresh falls back to the stale snapshot. A new
M365 finding inherits CVSS, severity, SSVC and the real CVE text from a
sibling on another host, as the msrc, nessus and container scanners
already do. Only what that leaves open goes to the feeds. Existing findings
keep their real text; the nightly Correct-CVSS run still re-checks them.

Also: _download_cvelistv5_zip wrote to a shared .part file, so concurrent
downloads could interleave; it now uses a per-writer name like the others.
2026-10-15 12:57:37 +02:00
vulncheck 5c55a4e9f3 fix(zoom): read "see references" CVEs from Zoom's security bulletins
Field report 14.10.2026: CVE-2026-108184 (ZSB-26021) and CVE-2026-108185
(ZSB-26022) were not found. Zoom's records on cvelistV5 state
"version": "see references" and link the bulletin; NVD had both at
"Awaiting Analysis" with no configuration (so were CVE-2026-53413, -53415
and -53420). Neither source had a bound, so no host could be flagged.

zoom_bulletin_service reads the overview (newest 20) and each bulletin's
"Affected Products" lines for Zoom Workplace and its VDI Client / VDI
Plugins. "starting on version 7.0.0 and before version 7.2.0 and 7.1.9 in
their respective branches" becomes [7.0.0, 7.1.9): each lower fix closes
its own x.y branch. Several branches without a start floor at the lowest
one's x.y.0; a single "before X" is everything below. Rooms, the SDKs,
Contact Center and the mobile apps are other version lines and skipped; a
line with no bound leaves its key to the record and NVD.

The nightly cvelistV5 build merges it under the zoom / zoom-vdi keys. The
bulletin decides each CVE it bounds, also over a record's own range
(CVE-2026-53416: record "< 7.0.11", ZSB-26018 adds the 6.6.15 branch fix).
Its lines name the platform and client or plugin, so these entries also
reach macOS and Linux and skip the CVE-text heuristic. The MSI-build limit
is unchanged: 7.1.43453 gets no verdict on a 7.1.9 fix.

A bulletin that is not freshly parsed (unreachable, parsed empty, rolled
off the overview) keeps its last parse, so its findings do not close as
patched while NVD still has no bound. Index v48.
2026-10-14 08:58:57 +02:00
vulncheck 6754b6b700 feat(app-scan): detect GnuPG CVEs via NVD
Field case: the inventory lists "GNU Privacy Guard" 2.4.8 (The GnuPG
Project); it got no CVE-2025-68972 (GnuPG through 2.4.8). GnuPG had no row
on any path.

New NVD row gnupg:gnupg. NVD has the record analysed and bounds each
branch; for 2.4.8 it returns CVE-2025-68972 and CVE-2025-68973.

No cvelistV5 row, although that was the first choice: MITRE's GnuPG
records write one range over all branches. CVE-2025-30258 is "0 .. < 2.5.5"
there (NVD: < 2.4.8 and 2.5.0 .. < 2.5.5) and the 2.5-only CVE-2026-24882
is "0 .. < 2.5.17" (NVD: 2.5.13 .. < 2.5.17); both would land on a 2.4.8
that is fixed or never affected. The name is anchored, so Gpg4win, which
carries its own version line, is not matched. Distro packages fail the
version cleaner and stay Wazuh's.
2026-10-13 14:10:27 +02:00
vulncheck fbc7575603 feat(app-scan): detect Blender CVEs via cvelistV5
Field case: Intune lists "Blender" 5.0.0 and Wazuh "Blender" 5.0.1
(Blender Foundation); neither got CVE-2026-60103 (3.0.0 through 5.1.2).
Blender had no row on any path.

NVD has the record "Deferred" with no configuration, and its
blender:blender rows stop at 2022, so cvelistV5 is the only structured
source: blender / blender, "3.0.0" .. lessThanOrEqual "5.1.2". The fix is
shown as ">5.1.2". The record's "unaffected" entry is a git commit and is
ignored.

Only the vendor's spelling is paired; the 2022 records name the vendor
"n/a" and write prose into the version field. The name is anchored
("Blender", "Blender 4.2 LTS"), so launchers and add-ons stay out.
Windows and macOS hosts only: a distro package ("4.3.2+dfsg-2") cleans to
4.3.2 and says nothing about backports. Index key bumped to v47.
2026-10-13 14:10:21 +02:00
vulncheck 3520ca6c45 fix(zoom): Workplace and VDI CVEs missed under the new CPE names
Field report 13.10.2026: CVE-2026-53412 (ZSB-26014) was not found on hosts
listing "Zoom Workplace (64-bit) 7.1.43453" and "Zoom VDI Universal
Plugin(64bit) 6.6.26830".

Cause 1, one CPE name. Every name containing "zoom" was asked as zoom:zoom,
the desktop client's old name. NVD has since filed it as zoom:meetings and
zoom:workplace_desktop, and the VDI client as vdi_windows_meeting_client(s),
virtual_desktop_infrastructure and workplace_virtual_desktop_infrastructure.
CVE-2026-53412 names only the new ones. Each row now asks every name. The
names are anchored, so Zoom Rooms, the Outlook plugin and the SDKs no longer
draw the client's CVEs.

Cause 2, the version hides the patch. Zoom's MSI writes major.minor.BUILD
(the plugin's 6.4.26620 is release 6.4.12); NVD's server-side match and
ours both read the build as a patch, so "6.6.0 .. < 6.6.15" never fired and
a floor like "> 5.14.14" passed every 5.14 build. NVD is now asked for x.y.0,
and a bound inside the install's own minor gets no verdict: no new finding,
but the CVE counts as touched so an open finding is not closed on no
evidence. Bounds in other minors still decide (6.6.22255 < 7.0.0). The EXE
form "6.0.11 (39959)" is unchanged.

Cause 3, client vs plugin. NVD files the VDI client and the VDI plugins
under one CPE. The CVE text decides: a plugin takes only CVEs that name a
VDI plugin, the client all but plugin-only ones.

cvelistV5: the old pairs used vendor "zoom", which no record does. Paired
now are the spellings with real bounds (CVE-2026-53416, "VDI Client and
Plugins" < 7.0.11, is still Awaiting Analysis at NVD); "see references",
"unspecified" floors and "before X" prose stay unpaired. Windows only.
2026-10-13 13:53:20 +02:00
vulncheck e73f277201 fix(wazuh): deleted agent on an Intune device kept Wazuh as inventory
The Wazuh sync only flipped the asset inactive; the stale wazuh_agent_id
kept the software view and the app-CVE scan asking Wazuh for an agent that
no longer exists, never Intune. A dual-registered asset whose agent is gone
from the agent list now loses the Wazuh link, so both fall through to
Intune; a returning agent is re-linked by hostname. Wazuh-only assets and
an empty agent list are left alone.
2026-10-12 15:42:39 +02:00
vulncheck 6e7a041f08 fix(wazuh): Intune "PC-LAB01" and agent "pc-lab01" became two assets
Field report 12.10.2026: a device known from Intune with an uppercase
name got a second asset as soon as its Wazuh agent reported the same name
in lowercase. Intune, Defender and Wazuh findings of one machine ended up
on two rows.

Cause: the Wazuh asset sync was the only source that compared hostnames
case-sensitively (Asset.hostname == name). Intune, Nessus, vCenter, IGEL,
Netdisco and Defender all match with ILIKE, which is why the other
direction (Wazuh first, Intune later) always merged.

The agent name stage and the cluster-node name stage now use the same
literal, case-insensitive compare (like_literal + ILIKE): every character
must match, only case is ignored. The cluster-node stage also goes through
pick_one, so several case variants are settled by the address or not at
all. Within Wazuh nothing changes: two agents that are both in the run
stay two assets, even when their names differ only in case. An agent
without a name skips the name stage.

Existing forks are not merged automatically. The lowercase row holds the
agent id; README.DEV describes how to heal it and how to find candidates.
2026-10-12 11:38:01 +02:00
vulncheck 99d0c51440 fix(cisco): NVD envelope range put CVE-2025-20363 on IOS XE 17.15.6
Field report 11.10.2026: a Netdisco router (C1121X-8PLTEP, IOS XE 17.15.6)
carried CVE-2025-20363. Cisco's Software Checker and Cisco's own cvelistV5
record agree that 17.15.6 is not affected; the record's 17.15 entries stop
at 17.15.3.

Cause: the hit came from the NVD half alone. NVD writes this CVE's whole
IOS XE configuration as one range, 3.2.0sg .. 17.17.1 inclusive, which is
just Cisco's first and last listed release with every train in between.
17.15.6 sits inside it. The cvelistV5 half never matched, but its reconcile
folds the NVD half's hits in, so the finding stayed open.

Dropping NVD ranges for Cisco is not the fix. Measured against all 560 IOS XE
CVEs at NVD: 89 carry ranges, and on most of them Cisco's record enumerates
no IOS XE releases at all, so the range is the only data. Where Cisco does
enumerate, the range only adds releases Cisco did not list (CVE-2025-20196:
102, also CVE-2023-20235, CVE-2023-20198/20273).

scan_asset_os now skips an NVD hit when cisco_unlisted says Cisco's record
enumerates that family for the CVE and leaves the installed release out. The
nightly app scan and the Netdisco sync pass the cvelistV5 index they already
load; without one the NVD half decides alone, as before. The open finding is
closed by scan_asset_cisco's existing reconcile on the next pass.
2026-10-12 11:28:23 +02:00
vulncheck f553e41c1a fix(msrc): Edge CVEs flapped open/patched; newest Edge build counts
Field reports 09./10.10.2026: on a Wazuh-only Server 2016 at Edge
152.0.4191.62 (fix 154.0.4258.37), 15 MSRC-only Edge CVEs went open ->
patched in the same second ("MSRC scan: ... is now at or past the fixed
build") and back to open on the next scan. Two Intune-only clients showed
the same flapping, plus a finding on a stale Edge build.

Cause 1: build_product_index skipped a CVRF document that failed to load
and stored the rest. The package reconcile reads "CVE not in the index" as
"host is past the fix", so it closed every CVE of the missing month; the
next complete build reopened them. A failed document now raises, which
keeps the cached index (ensure_index and the 01:30 refresh already handle
that). The reconcile also takes the inventory now and keeps a finding while
the product is still below the fixed build MSRC recorded on it, so a CVE
missing from the index for any other reason cannot close it either. An
update past the fix or an uninstall still closes it.

Cause 2: Intune lists Edge several times ("Microsoft Edge" 154.0.4258.62 +
152.0.4191.53, or 153.0.4234.32 next to the MSIX
"Microsoft.MicrosoftEdge.Stable" 154.0.4258.37). Edge Stable is one
machine-wide install and the extra entries are per-user registrations that
lag behind it, but each entry was scanned on its own and the stale one
flagged. Edge is now scanned once, at its newest build; the MSIX can raise
that build but never flags on its own.
2026-10-10 09:40:05 +02:00
vulncheck 25f706948a fix(nessus): an expired licence is a failed sync, not COMPLETED · 8 ERR
Field case: the Nessus licence expired and every scan answered
"Nessus 403 on /scans/157: {"error":"Expired license."}". Sync Health showed
an amber "8 ERR" card, the run was COMPLETED, and nobody got a mail, while
not one scan had been imported. The Wazuh API returning 500 goes red and
mails.

Cause: the Nessus sync never called fail_if_source_broken(); neither the
scheduler nor POST /nessus/sync asked whether the source itself was down.
Each scan error was caught and listed, and the run closed as completed. It
could not have caught it anyway: scans_processed was counted before
get_scan(), so it read 8 with 0 scans read, and "nothing synced + errors"
never matched.

scans_processed now counts scans actually read, and both callers pass it
to fail_if_source_broken(). Every scan failing is a failed run with the
usual mail (one per source per 24h); one unreadable scan among readable
ones stays completed with errors. The button returns 502 instead of
"Nessus sync completed"; HTTPException passes the catch-all so it is not
rewrapped as 500. The failure text says "scans" for Nessus.
2026-10-09 11:04:27 +02:00
vulncheck 81bafda04a fix(teams): skip the 1.0.0.0 placeholder, keep Teams Panel/Phone off Teams
Field case: an Intune host lists "MSTeams 1.0.0.0" (CN=Microsoft
Corporation). It drew CVE-2023-4863, -29328, -29330, CVE-2022-21965 and
CVE-2025-53783, plus the Teams classic EOL.

1.0.0.0 names no build. It is the new client's MSIX package (Intune's
"MSTeams", Wazuh's WindowsApps\MSTeams_1.0.0.0_...) with no version read;
no desktop Teams shipped as 1.0.0.0 and classic was never MSIX. Four parts
under 100, so it passed the scheme check and sat below every classic and
Android bound. teams_placeholder_version (1.0.0.0, 1.0, 1, 0.0.0.0) is now
skipped by the cvelistV5 and NVD scans and by _teams_classic. 0a73eafb had
read it as classic; "MSTeams" now counts as the new client for EOL, like
"Microsoft Teams". Real classic builds (1.5.00.31168) keep both.

CVE-2025-53783 came through teams_panels: the NVD filter matched the
vendor:product token as a substring, so microsoft:teams also took
teams_panels and teams_phones (1.0.9x device builds), and Teams for
Android (1.0.0.2025...) got their CVE and the Panel fix 1.0.97.2025102203.
Teams now takes its CPE exactly. Other products keep the prefix match;
Adobe relies on it (acrobat_dc). NVD cache prefix v5.
2026-10-08 13:15:00 +02:00
vulncheck 6ce578fcda fix(defender): a Chrome CVE on a patched client was labelled "Microsoft Windows 11"
Field report CVE-2026-106269 / CVE-2026-106211: first seen by Defender TVM,
the list showed package "Microsoft Windows 11" while the detail page showed
Google Chrome via app-scan.

Cause: d708a04 kept the OS fallback for machines with no row in the
SoftwareVulnerabilitiesByMachine export, meant for devices the export never
covers (iPhones). A fully patched Windows client has no rows either: nothing
was vulnerable at the last snapshot. When Chrome 155 shipped, the
per-machine call already listed its CVEs, the export did not, and the row got
the asset's OS. App-scan then cross-confirmed it, recorded Chrome as a
package row and left the parent name alone (fill-only).

The export covers every desktop OS, so a Windows, macOS or Linux asset never
falls back to its OS label; the package stays empty until the export names
it. iOS and Android keep the fallback.

Migration 062 repairs rows already written, with 057's rules. It also covers
old bare "Microsoft Windows" placeholders: since 2f6334e the asset reads
"Microsoft Windows 11"/"10", so neither 057 nor the sync's heal matched them.
2026-10-08 12:02:24 +02:00
vulncheck 2f6334edb9 fix(intune): scan Intune-only Windows clients for OS CVEs
Field case: clients moved to Intune without a Wazuh agent got no Windows
OS CVEs at all. Graph reports operatingSystem "Windows" and the build in
osVersion, never 10/11 or the edition. The sync stored a bare "Microsoft
Windows", and the cvelistV5 Windows pass skips a Windows it cannot place
in a family (_win_family), so these hosts were never scanned. The MSRC
OS pass covers Windows Server only.

_os_string now places the family from the build: 10.0.22000 and up is
Windows 11, 10.0.x below is Windows 10. The existing family-matched,
release-bounded pass does the rest and closes a finding once the build
reaches the fix. Edition does not matter for CVEs; Microsoft's records
are per release and architecture.

Only for devices whose managementAgent contains "mdm". Intune cannot
MDM-enrol Windows Server, but servers under Defender security settings
management are listed too, and 10.0.26100 is Server 2025 as well as
Windows 11 24H2. Those keep the bare label. managementAgent is now part
of the managedDevices $select.

Side effect: on hosts with both Wazuh and Intune, the 02:10 Intune sync
used to overwrite Wazuh's "Microsoft Windows 11 Pro" with the bare label,
which took the host out of the Windows OS pass until the next Wazuh sync.
It now writes "Microsoft Windows 11" and the host stays scannable.
2026-10-07 13:32:15 +02:00
vulncheck fa6e6af415 fix(app-scan): match classic Teams 1.x on cvelistV5 and NVD
Field case: an Intune host with "Microsoft Teams" 1.5.00.31168 and "Teams
Machine-Wide Installer" 1.5.0.31168 did not get CVE-2023-29328 / -29330
(Teams for Desktop, fixed 1.6.00.18681) from cvelistV5.

cvelistV5: teams is a strict-scheme key, so a bound in another build shape
is not compared. The check also ran over the floor, and Microsoft writes
the desktop floor as "1.0.0": three parts against 1.5.00.31168's four,
so every classic range was refused. It checks the upper bounds only now,
which also applies to adobe-acrobat (its HEAD tests still pass). The key
is skipped on a Mac: "Teams for Mac" is not paired, so it held only the
desktop's ranges.

NVD found both, but showed the macOS fix (1.6.00.17554, listed first).
_query_nvd_cpe now keeps the fix per target_sw, and scan_asset_packages
takes the host's own, else a platform-neutral entry's. That applies to
every NVD product. The Teams entries get the same upper-bound scheme check:
CVE-2023-24881 is "< 2.10.1" on NVD, Microsoft's record says 2.0.0 ..
< 2.10.1 and MSRC's only fix is 2.10.1, so it no longer lands on classic
1.x, where it could never close.

Names: Intune's "MSTeams" and a bare "Teams" match on both paths. The
Machine-Wide Installer stays out: it stages the client and keeps its own
version while the client updates itself; it keeps its EOL flag.

NVD cache prefix v4 (diagnose_scan follows) so the cached decisions are
redone on the next run.
2026-10-07 12:49:45 +02:00
vulncheck 3f2a577680 feat(app-scan): detect Oracle JRE and JDK CVEs on every record shape
Field case: an Intune host with "Java(TM) 6 Update 13" 6.0.130 and
"Java(TM) SE Development Kit 6 Update 13" 1.6.0.130 got nothing from the
app scan. Defender reports CVE-2013-1493 on the JDK. Not JDK-specific: the
JRE missed the same CVEs.

cvelistV5: the oracle-java key paired only "Oracle Java SE". Oracle filed
2018-2020 as "Java" ("Java SE: 6u201, 7u191, 8u181", lists split across
entries) and 2021-2024 as "Java SE JDK and JRE" ("Oracle Java SE:17.0.9"
next to GraalVM), so those years reached no Java install. Both are paired
now; _java_se_versions keeps the Java SE pieces (a label holds until the
next) and drops Embedded, JRockit and GraalVM. 9+ installs ("Development
Kit 17.0.9") and versions ("11.0.26", "24") now parse, for names that
start with "Java " only, so Temurin, Corretto, Microsoft's OpenJDK and
java-17-openjdk stay out. One finding per CVE (8u441 and 8u441-perf).

NVD: new rows for Java 6-8 (oracle:jdk + jre, and their sun: twins for
the Sun-era records). cvelistV5 has CVE-2013-1493 and CVE-2018-13785 as
"n/a". NVD writes the update in the CPE update field
(oracle:jdk:1.6.0:update13), so the release is the query and
_java_affected decides; "*:update41 up to 1.6.0" is 6u41 and below, an
entry without an update is the GA build. 9+ has no row: NVD names only the
newest affected update, which an exact query for an older one never
returns.

Both paths read Oracle's "newest affected update" as that update and every
earlier one of the same release, the rule the cvelistV5 path already used
(checked against Defender). This is the deliberate exception to "everything
below X is never a match": it stays within one release.

The NVD query now follows startIndex over every page. It read only the
first 200 records for every product; oracle:jdk:1.6.0 has 459.
2026-10-07 12:39:48 +02:00
vulncheck 09273d5640 feat(app-scan): detect NanaZip and NanaZip Preview CVEs
Field case: Wazuh lists "NanaZip" 6.0.1632.0 and "NanaZip Preview"
6.5.1742.0 (both MSIX, Kenji Mouri) on one host. Neither had a row.

Source: M2Team/NanaZip's own repo advisories (21, all with a CVE) on the
GitHub repo-advisory path. Only that feed states the fix for both lines.
NVD has the stable bound alone and no configuration from CVE-2026-54616
on; cvelistV5 drops the Preview line on 11 records. No NVD or cvelistV5
row: every NanaZip CVE there is also in this feed.

The version decides the line, not the package name: each fix names a build
on a major.minor line, and the build part only grows within it. The
maintainer writes prose ("5.0 Update 1 (5.0.1252.0) or later",
"Stable: >= 6.0.1698.0 / Preview: >= 6.5.1742.0"). _norm_ops now keeps the
bracketed build and reads "or later" as >=, and patched_floor splits on "/"
as well. 6.0.1632 gets 18 CVEs, Preview 6.5.1742 the four fixed in
6.5.1749, and 6.5.1750 and 7.0 get none.

This applies to every repo: the fixed version shown is now patched_floor's
fix for the install's own line, not only a bare build. Wazuh's ">= 4.14.4"
used to leave it empty. With no fix on the install's line, patched_floor
takes the nearest one above it instead of the highest.
2026-10-07 12:19:44 +02:00
vulncheck 3ad84f278b feat(app-scan): detect Lenovo System Update CVEs via cvelistV5
Field case: an Intune-only host on "Lenovo System Update" 5.08.03.59
(Lenovo) did not get CVE-2026-6387 (fixed 5.08.04.85). System Update had no
row on any path.

NVD has the record at "Awaiting Analysis" with no configuration, so
cvelistV5 is the only structured source: Lenovo / System Update,
"0" .. lessThan "5.08.04.85". New row on that pair. The zero-padded parts
compare as numbers.

The row matches that structured spelling only. Lenovo's records under
"Lenovo System Update" (CVE-2022-4568, CVE-2023-4632) write prose into the
version field ("Versions prior to 5.08.02.25"). The exact-version fallback
reads that as the fixed build itself, which would flag a patched host. The
name is anchored, so Vantage, System Interface Foundation and Update
Retriever are not matched. Index key bumped to v45.
2026-10-07 11:12:43 +02:00
vulncheck 113ec2aaf8 fix(app-scan): match Adobe's yearly-line Illustrator and Photoshop records
Since 2026 Adobe files Illustrator and Photoshop CVEs under one product
per yearly line: "Illustrator Desktop 2026" / "Illustrator Desktop 2025",
"Photoshop 2026" / "Photoshop 2025". The cvelistV5 rows only knew
"Illustrator" and "Photoshop Desktop", so those records (23 for
Illustrator, among them CVE-2026-71441) resolved to nothing. A pattern
entry with "20\d\d" now catches them, so next year's line needs no edit.

Each line is stated as "0 .. <= last broken build", the shape Media
Encoder uses. Read literally, the 2026 range covers a patched 2025
install (Illustrator 29.8.10 below 30.6), so both rows set major_floor.
That also floors the older one-line records ("Photoshop Desktop",
0 .. <= 26.1), the way NVD reads them (25.0 .. < 25.12.1, 26.0 .. < 26.2).
Earlier majors are left to NVD.

The Media Encoder test now also covers CVE-2026-34639, which states both
lines inside one affected block. Index key bumped to v44.
2026-10-07 10:52:50 +02:00
vulncheck 9c84804693 feat(app-scan): detect Adobe Media Encoder CVEs
Field case: an Intune-only host on "Adobe Media Encoder 2026" 26.2.2
(Adobe Inc.) got none of CVE-2026-48370, -48366, -47979, -47976 and
-47971. Media Encoder had no row on either path.

NVD: new row adobe:media_encoder. NVD has the batch analysed, "<= 25.6.5"
and "26.0 .. <= 26.2.2".

cvelistV5: new row on Adobe's "Adobe Media Encoder". Adobe states each
supported line as "0 .. lessThanOrEqual <last broken build>" and names
the patched build only as "unaffected" (26.3, 25.6.6). Read literally,
the 26 range also covers a 25.6.6 that is already patched. The new
registry flag major_floor gives such a range its bound's major as the
floor, so each range stays on its own line (Media Encoder 2025 = 25.x).
It applies to inclusive bounds only: a "< 26.0" floored at 26 would be
an empty range. Older lines get no finding from cvelistV5; NVD covers
them. Index key bumped to v43.
2026-10-07 10:29:04 +02:00
vulncheck b031e140f2 fix(wazuh): drop superseded MSIX registrations from the package list
A Citrix host listed 27 "Microsoft Teams" builds, every one under
WindowsApps\MSTeams_<ver>_x64__8wekyb3d8bbwe, while only the newest folder
existed on disk. Syscollector reads MSIX packages per user hive, and each
profile keeps the registration of the version it last had after Windows
removed the folder. The old builds drew Teams CVEs (CVE-2025-53783 on
25094.310.3616.953) that no update could ever close.

get_packages now keeps only the newest version per package family (name,
architecture, publisher id from the folder name), so the app scan, EOL,
M365 and the software tab all see the same cleaned list. Entries outside
WindowsApps are untouched, so classic Teams 1.x keeps its EOL finding.
2026-10-06 14:04:03 +02:00
vulncheck 3cb03b0955 fix(eol): flag Intune's "MSTeams" 1.x as classic Teams
0a73eaf pinned the 1.x classic-Teams rule to the exact normalised name
"teams", i.e. what Wazuh syscollector reports. Intune detectedApps lists
the same WindowsApps package by its AppX name "MSTeams", which normalises
to "msteams", so an Intune-only host with MSTeams 1.0.0.0 got no EOL
finding. The Intune path itself was fine: detectedApps feeds the same
run_eol_for_packages -> resolve_ms_lifecycle_eol chain.

Accept both names; the version still decides (1.x classic, 2xxxx new
client), so a current MSTeams stays clean.
2026-10-06 13:31:13 +02:00
vulncheck 161f242382 feat(app-scan): detect Nextcloud Desktop CVEs via NVD and GHSA
The sync client had no row on any path, so CVE-2025-66549 (fixed 3.16.5)
and CVE-2025-47792 (fixed 3.15) never reached a host. Intune and Wazuh
both list it as plain "Nextcloud" (3.15.3.20250107, 3.14.2.20241018).

NVD: new row nextcloud:desktop. NVD has both CVEs analysed, with ranges.
The name is anchored, so Talk, Files and the Office connectors (other
version lines) are not matched. The fourth version part is the MSI build
date; ver_parts=3 drops it, otherwise an exact-version CVE such as
CVE-2023-22472 (3.6.1 only) never equals 3.6.1.20221208.

GHSA: nextcloud/security-advisories joins the repo-advisory path.
Nextcloud publishes there before NVD analyses the record. The repo
covers the server, its apps and the mobile clients too, and Android's
3.x line overlaps the desktop's, so _PACKAGES keeps only the Desktop
package blocks (19 of 278 advisories in the live feed). The range
parser now reads "3.0.3 up to 3.2.4" (CVE-2021-37617) as a range rather
than as "anything below the fix".

No cvelistV5 row: those records name the product "security-advisories"
for every Nextcloud product, with prose bounds ("< 3.16.5"), so the
pair cannot tell the desktop from the server or Android.

Checked against the live feeds: 3.15.3.20250107 gets CVE-2025-66549
only; 3.14.2.20241018 gets that and CVE-2025-47792, and not
CVE-2024-52510, whose fix is 3.14.2. Both paths agree.

Known gap, documented in README.DEV: three old single-OS advisories
(fixed by 3.13.4 at the latest) are not platform-filtered on either
path.
2026-10-06 10:42:00 +02:00
vulncheck 9a8cceaeec feat(app-scan): detect LibreOffice CVEs via cvelistV5
Field case: an Intune host on "LibreOffice 26.2.4.2" (The Document
Foundation) got none of the 26.2.5 batch (CVE-2026-63266..63270, -63277
and siblings, "Addressed in LibreOffice 26.2.5/26.8.0").

Cause: LibreOffice had a row on the NVD-CPE path only, and NVD still has
these records at "Received" with no configuration. The cvelistV5 path had
no LibreOffice row, so resolve() returned None. It now has one, matched
on TDF's own vendor/product pair. Each record bounds one series
("26.2" .. lessThan "< 26.2.5"), so 25.8 and 26.8 stay out.

TDF writes the operator inside the bound. The compare reads only the
digits and survived that, but "< 26.2.5" would have become the finding's
fixed version for good. _clean_bound now strips a "<" inside a lessThan.
"<=" inside a lessThanOrEqual stays as it is: that is HPE's Aruba shape,
which scan_asset_aruba skips while its prose version field is unparsed.
Across the current cvelistV5 ZIP the change touches LibreOffice records
only.

The fourth digit is a build number and needs nothing special: the bounds
are three-part, so 26.2.4.2 sits below 26.2.5 and 26.2.5.1 does not.

Both rows are anchored and skip the Help Pack, Language Pack and SDK.
The Help Pack carries the suite's version and lags behind after an
update, which would keep the finding open. Index key bumped to v42.
2026-10-06 09:06:02 +02:00
vulncheck f439cc027b fix(intune): a 500 on detectedApps dropped the device from the run
Question from the field: when one Intune device answers detectedApps with
500 Internal Server Error, is it fetched again until it succeeds? It was
not, and the gap was silent.

Cause: GraphClient._get retried only timeouts and 429, so a 5xx raised at
once, and get_detected_apps caught that error and returned []. Every
caller (app scan, Intune sync, the asset's software view) read that as a
device with no software: nothing was detected on it that run, and nothing
was recorded. Nothing got closed either, since an empty inventory never
reconciles, but a device that kept failing stayed unscanned for good
without anyone seeing it.

500/502/503/504 are now retried with a short backoff. A failure that
persists raises out of get_detected_apps instead of returning []: the app
scan counts it as inventory_fetch_failed (the nightly log drops `errors`),
the Intune sync records it in its errors instead of a debug line, and the
software view returns a 502 instead of an empty list.

DefenderClient._get gets the same 5xx retry. A single 500 on the
tenant-wide SoftwareInventoryByMachine export emptied it for every machine.
2026-10-05 15:40:59 +02:00
vulncheck ad014268b3 fix(eol): a Citrix published-app stub gets no EOL finding
"Microsoft Visio 2016 / 1.0 / Delivered by Citrix" is a launcher for an app
that runs on the Citrix server, not on the host. The CVE scanners drop these
rows (filter_inventory); the EOL sweep kept them on purpose (f14eb1b) and
dated them by name through the MS-lifecycle listing, which put
EOL-MS-LIFECYCLE-MicrosoftVisio2016 on a host where Visio is not installed.

Cause: the stub guard in run_eol_for_packages only blocked the
version-based endoflife.date path; the name-only MS-lifecycle path still ran.
Stubs are now skipped in the loop for every source. They are skipped rather
than filtered up front so an all-stub inventory still counts as an inventory,
and _resolve_stale_ms_lifecycle closes the findings the old behaviour left
open on the next run.
2026-10-05 14:39:48 +02:00
vulncheck 605e271f49 feat(app-scan): the Data-jobs scan rebuilds the CVE indexes first
To get a CVE published today, the operator had to rebuild the cvelistV5
index on the host (docker compose exec ... build_product_index(force_fresh=
True)) before pressing the scan button, or wait for the 01:30 refresh. The
"App CVE Scan" button under Data jobs now does that itself: it calls
/app-cve-scan/start?rebuild_index=true, and the job runs the full nightly
refresh (fresh cvelistV5 ZIP + MFSA, GitHub advisories, TeamViewer, Citrix,
IGEL ISNs, MSRC) before it scans. The 01:30 job body moved into
app_cve_scanner_service.refresh_indexes so both run the same code.

Opt-in: the per-asset rescan and API clients keep reading the stored index,
no 557 MB download per click. The button's dialog says what it costs.

A failed rebuild keeps the cached index as before, but the button now says
so ("index rebuilds failed — scanned on the cached index"). That includes a
ZIP download that fell back to the old file on disk; otherwise "completed"
reads as "today's CVEs are in".

The button can now overlap the 01:30 job and the threat-intel cascade, and
all three streamed the ZIP into the same .part file. Each writer now uses
its own temp name; os.replace keeps the swap atomic.
2026-10-05 14:08:50 +02:00
vulncheck 93e748a70b feat(app-scan): detect .NET SDK and ASP.NET Core CVEs
.NET SDK: SDK-only CVEs (CVE-2026-58649, dotnet watch) carry every feature
band's fix in one field, lessThan "10.0.111, 10.0.400". Instead of being
dropped, those bounds now go to new dotnet-sdk-8/9/10 keys, one range per
band: [10.0.0, 10.0.111) and [10.0.200, 10.0.400). A band without its own
fix is out of support and stays vulnerable up to the next fix. Toolset,
Templates and the packs are parts of the SDK and are not matched.

NVD: runtime and SDK share microsoft:.net, so _in_range no longer compares
an SDK bound (patch 100+) with a runtime or the other way round. Without it
the runtime false positive would come back once NVD analyses the record.
.NET Framework (major < 5) is left alone.

ASP.NET Core was not checked at all. CVE-2025-55315 (CVSS 9.9),
CVE-2026-40372 and CVE-2026-26130 name only "ASP.NET Core x.0", never
".NET x.0". New aspnetcore-8/9/10 keys and the microsoft:asp.net_core CPE
match the shared framework and the runtime, not the targeting pack or the
hosting bundle options.

Checked against all 53 .NET CVE records since 06/2025: a fully patched
host with SDK 10.0.401 stays clean, runtime matches are unchanged.
2026-10-03 10:37:49 +02:00
vulncheck 14782c764d fix(app-scan): an SDK-only .NET CVE landed on every patched runtime
CVE-2026-58649 is in `dotnet watch` (SDK only), but Microsoft files it under
".NET 8.0/9.0/10.0" with the SDK feature-band fixes as bound, two in one
field: lessThan "8.0.130, 8.0.424". The dotnet-* keys compare the runtime
version from the package name, and every runtime (8.0.31) sits below 8.0.130,
so Host, FX Resolver, Runtime and Desktop Runtime were all flagged on fully
patched hosts.

A runtime patch never reaches 100 (one a month); an SDK band always does.
Bounds with patch >= 100 are now dropped for the dotnet-* keys at index time.
Of the 53 .NET CVEs since 06/2025 this is the only one with SDK bounds; real
runtime bounds (CVE-2025-55247, 8.0.21) still match.
2026-10-03 00:40:33 +02:00
vulncheck d83c1927ec fix(netdisco): read CTX697096's NetScaler records
CVE-2026-88771 .. 88778 (bulletin CTX697096, cvelistV5 2026-09-27) were
not reported on a 14.1-72.61 appliance, although the records were in
the ZIP before the nightly index build. NVD has them as Received with no
configuration, so cvelistV5 is the only source.

The CNA changed the record shape twice. The vendor is now "Citrix
NetScaler", which the anchored vendor pattern rejected, so the records
never reached the index. And the line moved from `version` into the
bound: version "0", lessThan "14.1-73.37", where scan_asset_netscaler
took only entries whose version equals the line.

The vendor pattern takes the third spelling. An entry with version "0"
applies when its bound is exactly line-dash-build of the appliance's
line; "14.1-73.37 FIPS" and "13.1-37.279 and NDcPP" are not builds and
still never reach a normal appliance.
2026-09-28 10:10:22 +02:00
vulncheck c71caf053b feat(scan): FortiPAM extension — 7.4 line, CNA cpe for NVD
cvelistV5 now carries CVE-2026-84388 (Fortinet as CNA, 2026-09-22) and
NVD lists it as Awaiting Analysis with no configuration. FG-IR-26-168
and the CNA description both now name "7.4 all versions" as affected
next to "8.0 all versions", so the entry gets a second row, 7.4.0 <= v
< 7.5, whose fix is on the 8.0 line (8.0.1.123). An advisory row takes
an `end` for that case.

The entry also carries the CNA's cpe (fortinet:fortipam_chrome_extension),
so NVD picks up the extension's later CVEs once it analyses them. For a
CVE an advisory row holds, the rows stay the verdict and NVD's result is
dropped: the CNA's structured data says only "8.0.1 affected", which
cannot tell 8.0.1.100 from the fixed 8.0.1.123 and misses 8.0.0 and
7.4. For the same reason cvelistV5 is not read for this extension.
2026-09-28 09:53:06 +02:00
vulncheck 96ab6a2b51 fix(app-scan): name the held Citrix CVEs in the scan log
"N CVE(s) with a bound not resolvable to a build — held, not decided"
gave a count only, so there was no way to tell which CVE or which
installed package was held. The line now lists the CVE IDs and the
package name and version.
2026-09-24 14:14:37 +02:00
vulncheck 62ac9363e6 fix(settings): show Users only to admins
/auth/users is RequireAdmin, but the Users section was marked
admin: false. Read-only and editor accounts saw an empty table and a
403 in the console. They now see Profile only, and the list is fetched
for admins only.
2026-09-24 14:14:37 +02:00
vulncheck 694bb0b6bd test(tomcat): pin the WebSocket CVEs that only cvelistV5 carries
A tester reported CVE-2026-76183 and CVE-2026-87022 as not detected on
"Apache Tomcat 9.0 Tomcat9 (remove only)" 9.0.107 on Windows. Both were
published on 23.09.2026 and NVD still has them at "Awaiting Analysis"
with no CPE, so cvelistV5 is the only structured source for now.

The real records, run through build_product_index and scan_asset, flag
the host, so the code is not the cause. The likely cause is timing: the
index is rebuilt at 01:30, so a scan on the day of publication could not
see them. The records are fixtures now, together with NVD's CPE ranges
for CVE-2026-73180. The test also covers the false-positive edges: the
fixed builds on each line (9.0.122, 10.1.60, 11.0.26) stay clean, and the
open "0 < 7.0.43" entry never reaches a 9.0 host.
2026-09-24 12:46:03 +02:00