Commit Graph
284 Commits
Author SHA1 Message Date
vulncheck 10692c67d4 fix(docs): use relative path for dashboard screenshot, not a main-pinned URL
The image was hardcoded to /raw/branch/main/..., so viewing README.md on the
dev branch still rendered main's (stale) screenshot regardless of what dev
actually had. A relative path resolves against whatever ref/branch is being
viewed (Gitea/GitHub both do this), so dev shows dev's image and main shows
main's once merged.
2026-07-07 17:02:20 +02:00
vulncheck 4417dc18ae chore(docs): remove stray docs/dashboard.png/ duplicate upload
Accidental duplicate from the Gitea web-upload UI (a folder named
dashboard.png containing a file of the same name); the correct,
referenced screenshot lives at docs/screenshots/dashboard.png.
2026-07-07 17:00:30 +02:00
vulncheck 2aa8b60b5b Upload files to "docs/dashboard.png" 2026-07-07 16:56:28 +02:00
vulncheck 1f305b5be3 Upload files to "docs/screenshots" 2026-07-07 16:55:36 +02:00
vulncheck c90a7bc0e5 docs: document this session's features, simplify frontend README
README.md: the "Features" section was silent on everything built this
session — added a subsection for the built-in App→CVE scanner (OSV/NVD-CPE/
cvelistV5), Wazuh false-positive suppression, and mobile device security
(EOL/EOS, Android patch-level, per-CVE Android via Samsung SMR/Google ASB);
extended the KEV bullet to mention the new advisory dashboard widget; added
the new /api/v1/advisories endpoint group to the API table.

README.DEV.md: added a 6th feature-group entry to "What this branch adds"
and a full new section (matching the doc's existing per-feature style —
what/why, module map, design decisions, out-of-scope) covering the
App→CVE engine, FP suppression, mobile device security, and the KEV
advisory feed, so this session's work has the same documentation trail as
every earlier feature drop in this doc.

frontend/README.md: was still the untouched create-next-app boilerplate,
including "Deploy on Vercel" instructions that don't apply (this app ships
via Docker Compose). Replaced with a short pointer to the root README/
README.DEV for setup and deployment.

Not touched: .env.example DEFAULT_ADMIN_EMAIL (admin@vulnmanager.local) —
verified consistent with README's own reference to it, no drift.
2026-07-07 16:46:43 +02:00
vulncheck a4b8c85d06 fix(rebrand): catch the split-span brand name the string rename missed
Nav/Drawer render the name as two separate JSX text nodes ("Vuln" + a
<span> for the color-accented "Check") for the two-tone styling — the
literal string "VulnCheck" never appears in the source, so the earlier
exact-string rename pass didn't touch it. Logo/Nav still showed "VulnCheck"
after that commit (tester screenshot). Now "True" + accented "Vuln",
matching the rest of the rebrand. logo.svg itself is just the shield+check
glyph with no embedded text — nothing to change there.
2026-07-07 16:39:18 +02:00
vulncheck dbad9a365e chore(rebrand): VulnCheck → TrueVuln
Renames the product name in every user-visible surface and internal
self-reference: page title, nav/shell, login/MFA pages, email templates and
subject prefixes ([VULNCHECK] → [TRUEVULN]), TOTP issuer label, report/PDF
headers, notification previews, outbound User-Agent/HTTP-Referer headers we
set ourselves, docs (README, ARCHITECTURE, PROJECT_OVERVIEW, DATABASE_SCHEMA,
README.DEV, TROUBLESHOOTING is untouched — see below), and .env.example
placeholder config (LDAP/OIDC/SAML example domains and paths).

Also renamed the on-disk cache file paths (/tmp/vulncheck-*.zip|csv|json →
/tmp/truevuln-*), kept consistent across the two files that share the
cvelistV5 ZIP cache path — first run after deploy re-downloads that ~557 MB
cache once (harmless, disposable).

Deliberately LEFT UNCHANGED (not branding — real external references or
infra identifiers; renaming the text without renaming the underlying thing
would just break/mislead):
- The actual Gitea repo URL/path (gitea.isuit.ch/vulncheck/vulncheck) and the
  README lines derived from it (git clone target dir, tree listing) — a real
  repo rename is a manual Gitea-side step (Settings → repository name) the
  user would need to do themselves, and existing clones would need
  `git remote set-url` after.
- The real support mailbox (support-vulncheck.sq9vd@passmail.net, in both
  README and TROUBLESHOOTING) and the Buy Me A Coffee link — both point to
  accounts that still exist under the old name; renaming the text alone
  wouldn't create new ones.
- GitNexus MCP resource URIs in CLAUDE.md/AGENTS.md (gitnexus://repo/
  vulncheck/...) — tied to GitNexus's own index name for this repo, not our
  branding; those files are untracked in this repo anyway.
- docker-compose.yml container/network/Postgres user+db names
  (vulnmanager-*) — explicit user decision: infra naming carries real
  deploy/data risk on an already-running instance and isn't part of the
  product-branding ask.
- The Tailwind color token class `vulncheck-blue` (frontend/app/globals.css)
  — invisible internal CSS variable name, renaming it would touch ~270
  className occurrences for zero user-visible benefit.

Verified: backend py_compile clean on every touched .py file; frontend tsc
clean (two pre-existing, unrelated errors remain: assets/page.tsx SVG title
prop, mfa-setup missing qrcode.react types). All diffs are exact-string
renames — no other changes riding along.
2026-07-07 16:34:40 +02:00
vulncheck a75d2d87be feat(app-cve-scan): detect .NET Runtime/SDK and Microsoft Teams CVEs
Tester-reported false negatives (Wazuh's own detector misses these; our
app-scan is independent — it works off Wazuh's raw package inventory, not
Wazuh's CVE engine, so it can catch what Wazuh's detector can't).

- Microsoft .NET Runtime/Desktop Runtime/SDK/Host, and .NET Framework: NVD
  files ALL of them (6/7/8/9 and Framework 2.0–4.8.1) under the SAME CPE
  (cpe:2.3:a:microsoft:.net), each major version scoped by its own cpeMatch
  range — one registry row covers both families via the existing NVD-CPE
  scanner. .NET Framework itself typically isn't a Windows "installed
  program" (it's a Windows feature, not an MSI package), so it's usually
  invisible to Wazuh syscollector's package list regardless of this
  registry row — documented as a Wazuh-agent data-source limit, not
  something curation can fix. "Teams Machine-Wide Installer" excluded
  (placeholder entry, not a real Teams binary).
- Microsoft Teams (new + classic, both use cpe:2.3:a:microsoft:teams):
  added to both the NVD-CPE registry and the cvelistV5 registry (defense in
  depth — a fresh Teams CVE from Microsoft's own CNA data can predate NVD's
  CPE processing, as seen with Edge CVE-2026-58523 today).

Verified resolution against realistic installed-program name strings
(Desktop Runtime/SDK/Framework variants, Teams, Teams Machine-Wide).

Deferred (separate, larger pieces — not in this change):
- SAP Business Client/GUI/Analysis for MS Office: NVD's CPE for these covers
  a whole minor version (e.g. all of 2.8) with no patch-level (PL/SP)
  granularity, so a naive match would false-positive on already-patched
  installs (tester's own example: 2.8 SP29 flagged when only earlier SPs are
  vulnerable). Needs a dedicated PL/SP-aware matcher — parse the SP/PL
  number out of the installed version/display string and compare against a
  curated per-CVE fixed-SP table sourced from SAP's own advisories, similar
  in shape to the Samsung SMR-vs-ASB precision work. Real scope, own turn.
- Microsoft Teams classic EOL flagging: no endoflife.date product exists for
  Teams; would need a hardcoded EOL/retirement-date entry (same shape as the
  existing ms_lifecycle_service exotics for Silverlight etc). Small add-on,
  can do on request.
2026-07-07 13:40:22 +02:00
vulncheck 41947920dc fix(app-cve-scan): NVD cache TTL, zero-width ranges, Edge, Graph 429 pacing
1. NVD/OSV per-(product,version) cache TTL 7d → 24h. A version queried
   BEFORE a new CVE for that exact version is published stays cached empty
   for the whole window, hiding the CVE from every host on that version
   until it expires — exactly the tester's CVE-2026-14152 (undetected) vs
   CVE-2026-13778 (detected, same day) discrepancy. 24h still collapses
   most redundant traffic (many hosts share a version).

2. cvelistV5 range parser: some CNA records emit "version" == "lessThan"
   (e.g. a few Chrome entries) — read literally that's a zero-width,
   impossible range, so the CVE was silently unmatchable. NVD treats these
   as an open floor (no lower bound); we now do the same instead of
   dropping the CVE. Verified against the exact malformed record.

3. Added Microsoft Edge (Chromium-based) to the cvelistV5 registry — it was
   only in the NVD-CPE registry, so a fresh Edge CVE with no NVD-CPE data
   yet (verified: CVE-2026-58523 has none) was invisible to cvelistV5
   detection too, despite the CNA (Microsoft) publishing its own
   affected[]/cpeApplicability directly in the record.

4. Graph get_detected_apps: paced with a 300ms minimum gap between calls on
   one client. A device-by-device sync loop was sustaining 429s from Graph
   (single-call retry wasn't enough against back-to-back device calls).

(No change: the "400 then 200" on the first device of the beta fallback is
expected — _v1_detected_apps_ok is per GraphClient instance, and a fresh
client is built each sync run, so the first device in a run always probes
v1.0 once before flipping to beta for the rest of that run.)
2026-07-07 13:37:07 +02:00
vulncheck 7425d79453 feat(android): Samsung SMR as primary source, ASB as fallback; fix stale cache
1. Samsung SMR precision (tester's point 1): security.samsungmobile.com's
   yearly page (?year=YYYY) serves ALL ~12 months' content server-side in the
   raw HTML — the accordion is pure CSS/JS, it doesn't gate what's delivered
   — so it IS reliably parseable per month, contrary to what I assumed
   earlier. New samsung_smr_service parses each SMR-MMM-YYYY block: Google
   Critical/High CVEs MINUS the "Not applicable to Samsung devices" list,
   plus Samsung Semiconductor Critical/High fixes. Cached per year (24h).

   android_cve_service.check_android_cves now takes `manufacturer`: Samsung
   devices try the SMR month first (source 'samsung-smr'), falling back to
   raw ASB (source 'android-asb') only when SMR doesn't cover that month or
   the fetch fails. Non-Samsung Android stays on ASB. This directly fixes
   the false positive the tester hit: CVE-2025-59604 (Qualcomm-only) is
   explicitly "Not applicable to Samsung devices" on Samsung's own page and
   is now excluded — verified against the live page (absent from the parsed
   June-2026 set).

2. Cache bug (tester's point 2): a device stuck showing only the newest
   month's CVEs despite older months (verified live: 2025-09 had 59, 2025-12
   had 57 AOSP critical/high CVEs) turned out to be stale cache entries from
   before today's URL-format and SoC-section-filter fixes — the "non-empty is
   immutable" cache rule kept serving pre-fix (empty or wrong) data forever.
   Bumped _CACHE_PREFIX (asb_month_v2_) so every month is refetched under the
   current, correct logic.

Both parsers verified against live data (JS + Python) before shipping:
19 months of Samsung SMR (2025-01–2026-07) parsed without error with
plausible per-month counts; ASB severity counts confirmed non-zero and
variable across all 12 lookback months.
2026-07-07 13:30:55 +02:00
vulncheck 5d2669df21 feat(advisories): CISA KEV "actively exploited" awareness feed
New security-advisory section, independent of asset findings: a rolling view
of what's being actively exploited in the wild so operators see 0-days/
exploited CVEs even before a scanner flags an affected asset.

- Reuses the KEV catalog enrichment already fetches + caches (24h); added
  vendor/product/name to the cached entry (enrichment ignores the extras).
- advisory_service.get_recent_kev: newest KEV entries, annotated with whether
  the CVE is already in inventory (+ asset count) via one grouped query.
- GET /api/v1/advisories/kev-recent (new advisories router).
- Dashboard widget "Actively Exploited · CISA KEV": CVE, vendor/product,
  date added, 🔒 ransomware flag, and an "In inventory · N" badge (vs
  "not seen"). Rows link to our CVE detail; View All → the CISA catalog.

Backend py_compile + frontend tsc clean.
2026-07-05 17:17:55 +02:00
vulncheck f645cadc7b fix(android-asb): only import AOSP CVEs, skip chipset/SoC vendor sections
Raw ASB import created false positives on Samsung (and any) devices: ASB
lists chipset-vendor CVEs (Qualcomm/MediaTek/Unisoc/Imagination/Arm) that
only affect devices with that SoC — Samsung's own SMR marks many "Not
applicable to Samsung devices". The parser now walks section headers + rows
in order and keeps only the AOSP sections (Framework/System/Kernel/Runtime/
Media/Google Play/Widevine) that apply to any Android device at that patch
level; SoC/vendor sections are dropped.

Verified on 2025-09: 115 total → 59 AOSP kept, ~49 SoC dropped; the Framework
KEV CVE-2025-48543 is retained.

(Samsung's own page can't scope per-device either without JS — it loads
month bodies via AJAX — so AOSP-section filtering is the robust fix. Samsung-
proprietary SVE CVEs remain out of scope.)
2026-07-05 17:00:08 +02:00
vulncheck 4cd39b163a fix(enrichment): pass db to VulnOverrideService in cvelistV5 date pass
VulnOverrideService.__init__ requires db, but the bulk cvelistV5 ZIP date
pass called it with no args → "missing 1 required positional argument: 'db'"
and fell back to slow per-CVE HTTP lookups. db is in scope; pass it.
2026-07-05 17:00:08 +02:00
vulncheck 101445109f fix: ASB 2026 URL format, cvelistV5 fixed_version, macOS OS-CVE scan
- android-asb: from 2026 Google nests the bulletin under a year segment
  (/bulletin/2026/2026-01-01); older months stay flat (/bulletin/2025-10-01).
  fetch_asb_month now tries the year-nested URL first, then flat. Verified:
  2026-01 → nested 200/flat 404; 2025-xx → nested 404/flat 200.
- cvelistV5 scan: stop using lessThanOrEqual as fixed_version — that bound
  means the version is still affected (no published fix), so leave
  fixed_version empty (correct "no patch available", e.g. 7-Zip CVE-2026-58052
  ≤26.02). Only lessThan is a real fix target.
- app-cve OS scan: add macOS (cpe:2.3:o:apple:macos) alongside iOS/iPadOS so
  Mac assets get OS CVEs (e.g. Apple CVE-2026-43700, previously only Defender
  saw it).
2026-07-05 16:54:50 +02:00
vulncheck 59e4afbeb3 fix(android-asb): negative-cache empty/404 months to stop the refetch storm
Future ASB months Google hasn't published yet return 404. fetch_asb_month
only cached successful months, so every Android device re-fetched the same
404 months on every sync (the tester saw 2026-01…06 hammered dozens of times).

Now empty/404 results are cached too, with a 3-day TTL: months with CVEs stay
immutable/cached forever; empty months are skipped for 3 days, then retried
so a newly-published month is picked up. Legacy list-format cache entries
still read correctly.

(The 404s are expected — those months aren't published on source.android.com
yet; the 2025 months that exist already produced android-asb findings.)
2026-07-05 16:51:53 +02:00
vulncheck ed78810ad8 feat(intune): per-CVE Android detection from Google ASB
For Intune-managed Android devices, raise real-CVE findings for the months
the device is behind on patches. Device patch level (androidSecurityPatchLevel,
e.g. 2025-03-01) → every monthly Android Security Bulletin published after it
lists CVEs the device hasn't received. Fetched from source.android.com
(stable, static, per-month) and parsed (CVE + severity, severity carried
across rowspan rows); cached per month in a Setting.

Source 'android-asb' (real CVE ids → EPSS/KEV/CVSS enrichment applies). Wired
into the Intune sync alongside the patch-level-staleness headline finding.

Why not Samsung's SMR page: securityUpdate.smsb ignores the year/month query
param and loads the month via JS — a plain fetch returns the same latest month
regardless (verified: 2025-03/-04/-09 all identical). ASB is the upstream
source for the Google CVEs Samsung ships (the security-critical bulk).
Samsung-proprietary SVE CVEs aren't covered (their page is unscrapeable).

Volume guards: Critical + High only, last 12 months (tunable via _MAX_MONTHS
/ _WANT_SEV). Verified month math + the ASB parser against the live bulletins
(2025-09 → 109 CVEs, per-month distinct).
2026-06-30 10:48:06 +02:00
vulncheck c1fc992285 fix(app-cve-scan): build cvelistV5 index on manual scan + faster/robust build
Why the tester saw nothing: the manual "App CVE Scan" only LOADED the cached
index, it never built it — only the nightly did, and that build likely timed
out on the 557 MB download (120 s). So 7-Zip CVE-2026-58052 / Notepad++
CVE-2026-52885 (both in-registry, in-range) were never matched.

- Manual scan now builds the index when it's missing (same result as the
  nightly), then caches it.
- ZIP download timeout 120 s → 600 s.
- Index build pre-filters on raw bytes (only JSON-parse files mentioning a
  curated vendor) → ~99% fewer json.loads, build drops from minutes to ~a
  minute.

Verified the two CVEs' vendor/product (7-Zip/7-Zip, notepad-plus-plus) match
the registry and the installed versions (26.01 ≤ 26.02, 8.9.5 < 8.9.6.4) fall
in range — so they will now be detected once the index exists.
2026-06-30 10:40:06 +02:00
vulncheck 46594c153d feat(app-cve-scan): auto-suppress loose-CPE false positives via cvelistV5
Item 3 — the inverse of detection. Wazuh's CPE match over-reports CVEs across
product editions (a SQL Server 2019 / 15.x host carrying a CVE that only
affects 16.x/17.x). Now: for each open Wazuh finding, read the CVE's
cvelistV5 affected[] ranges scoped to the matched product, and if the
installed version is provably OUTSIDE every clean range → mark
status=false_positive (reversible via unmark, defer_reason explains why).

Conservative guards (never hide a real finding):
- Wazuh source only (the over-matcher); Nessus/app-scan/EOL untouched.
- Product match needs ≥2 shared significant tokens → only multi-word
  products (SQL Server, Visual Studio…) are ever scoped; single-token apps
  are left alone.
- If any relevant affected entry has no clean numeric range → abort (keep).
- If installed is inside ANY affected range → keep (it's real).

Nightly job runs it after the scan; manual POST /suppress-false-positives.
Verified on the tester's exact CVEs (installed 15.0.4013.40): CVE-2026-26116
(17.x-only) + CVE-2026-33120 (16.x-only) suppress; CVE-2024-29047 (covers
15.x) correctly kept.
2026-06-29 20:21:45 +02:00
vulncheck cd65dd0076 fix(assets): UnboundLocalError on list_assets without source filter
The source-filter branch did `from app.models.vulnerability import
Vulnerability` locally, which made Vulnerability a function-local everywhere —
so the later vuln_count query (line 465) raised UnboundLocalError whenever no
source filter was passed. Vulnerability is already imported at module level;
dropped the redundant local import.
2026-06-29 15:30:02 +02:00
vulncheck 61ce7497ff feat(app-cve-scan): cvelistV5 range-based detection for installed software
Catches CVEs the NVD-CPE scanner misses: fresh CVEs NVD hasn't CPE'd yet, or
ones filed under a different CPE product string than we curated (TeamViewer
lives under teamviewer:remote, not teamviewer:teamviewer). Matches directly
against cvelistV5 affected[].vendor/product + version ranges — the
authoritative MITRE feed we already cache as a ZIP.

- Curated product registry (name-regex → cvelistV5 vendor/product pairs):
  TeamViewer, Notepad++, Devolutions RDM, 7-Zip, Firefox, Chrome, VLC,
  PuTTY, WinSCP, Wireshark, FileZilla, Zoom. Unknown software ignored.
- build_product_index: one walk over the cached ZIP → {product_key:
  [{cve,start,lt,lte}]} for curated products only; cached in a Setting,
  rebuilt by the nightly job (the 557 MB walk happens once, not per scan).
- scan_asset: resolve installed software → indexed CVEs → version-range
  check → upsert (source 'app-scan', shared badge/cross-confirm/enrichment).
- Wired into run_app_cve_scan (loads cached index; skipped+logged if not
  built yet) and the nightly job (builds index first).

Verified against the real CVE JSON: TeamViewer CVE-2026-23572 (<15.74.5),
Notepad++ CVE-2026-52885 (<8.9.6.4), Devolutions CVE-2026-13372
(2026.2.5–2026.2.11) all detect at affected versions and correctly do NOT
match patched versions.
2026-06-29 15:05:20 +02:00
vulncheck ade5f435d9 fix(assets): Nessus filter via vuln-source; fix(ui): AI remediation render
assets: the previous filter keyed Nessus on nessus_host_uuid, but imports
whose host had no uuid (matched by hostname) carry it nowhere — they fell
into MANUAL. Now each scanner filter also matches assets with ≥1 finding
whose `sources` list contains that scanner ("nessus"/"wazuh"/...), which is
the maintained multi-source truth. MANUAL = no linkage AND no scanner vuln.

ui: the no-dependency AI-remediation renderer showed GFM tables as raw pipes,
literal "\n" inside code blocks, and "<br>" as text. Now: unescape literal
\n/\t, render <br> as a line break inline (without splitting the line, which
would corrupt table rows), and render GFM tables (header/sep/body).
2026-06-29 14:13:31 +02:00
vulncheck d14ed835e4 fix(mobile-eol): clean device EOL text + correct Samsung A11/Active5 Pro
Description bugs (tester screenshot):
1. lowercase vendor ("samsung Galaxy Tab A8") — Intune reports manufacturer
   lowercase. Use a fixed vendor label (Apple iPhone / Apple / Samsung).
2. doubled model ("Samsung Galaxy Tab A8 Galaxy Tab A8 — EOL") —
   upsert_eol_vulnerability appends the release label itself, so product_name
   must be the VENDOR only. Now title reads "Samsung Galaxy Tab A8 — EOL" and
   the full device name is written back into the package column.

Mapping fixes from the tester (he resolved the two I left open, and caught a
wrong one):
- SM-X236 was mapped to Tab A9+ → it's Tab A11+ (5G). Corrected.
- SM-X230 → Tab A11+ (Wi-Fi), SM-X130/X135 → Tab A11, SM-X356 → Tab
  Active5 Pro. Added. All names verified against the live API.
2026-06-25 13:12:37 +02:00
vulncheck 428c3f2737 feat(mobile-eol): add older Samsung Tab A / Tab Active / XCover3-4 models
From the tester's missing-model list: Tab A 7.0/8.0 (2015/16), Tab A9+ 5G
(SM-X236), Tab Active / Active2 / Active3 / Active5, XCover3/4/4s. All 13
new release names verified against the live endoflife.date API; SM-G556 /
G525 / G736 were already mapped.

Two codes left out pending the marketing name (avoid a wrong-EOL mapping):
SM-X356 and SM-X230 — not confidently identifiable.
2026-06-25 11:16:57 +02:00
vulncheck 35940de51e feat(dashboard): Mobile Security widget (EOL/EOS + Android patch level)
Separate dashboard widget for phones & tablets, kept apart from the
desktop-software EOL widget (which now excludes mobile findings).

Reuses existing data — the findings are already vuln rows. New
finding_type=mobile filter on the vulns endpoint ORs the mobile cve_ids
(ANDROID-PATCH-%, EOL-IPHONE/IPAD/SAMSUNG-MOBILE/SAMSUNG-GALAXY-TAB-%) and
bypasses the pseudo-CVE exclusion. The widget feeds from it (top 10, cvss
desc → vendor-EOL 9 > patch ≥1y 8 > EOL-soon 5.5 > end-of-active-support 3),
deduped by cve_id+asset so the same EOL stream on N devices stays N rows.
"View All" deep-links to /vulnerabilities?finding_type=mobile, which the
list page now reads from the URL.

ponytail: one combined widget, not two — severity sort already surfaces the
worst (vendor-EOL reached) above the milder patch-staleness rows, and each
row's title says which it is. Split into two later if the volume warrants.
2026-06-25 09:59:33 +02:00
vulncheck d8c838fa09 fix(assets): source filter by scanner linkage; expand Samsung EOL table
assets: the source filter used the creation-time `source` enum, so an asset
first created by Wazuh/Manual and later matched by Nessus stayed off the
"Nessus" filter. Now filter by the actual per-scanner id columns
(wazuh_agent_id / nessus_host_uuid / intune_device_id|defender_machine_id),
so a merged asset shows under every scanner that sees it. MANUAL = no linkage.

mobile-eol: expand the Samsung SM-code table from ~30 to ~100 models —
S20→S25, Note10/20, Z Fold/Flip 2→7, full A-series, XCover, Tab S6→S10 +
Tab A. All 100 release names verified against the live endoflife.date API.
Apple was never table-bound (it fuzzy-matches the full live list). Unmapped
Samsung models still skip (no false-positive). Graduated severity (eoas→low,
eol-soon→medium, eol→high) already comes from eol_service.
2026-06-25 09:53:38 +02:00
vulncheck 98bd366ddf feat(intune): mobile device EOL/EOS + Android patch-level staleness
For Intune-managed phones/tablets, runs during the Intune sync (device dict
already in hand — no extra fetch):

1. Device-model EOL/EOS via endoflife.date, reusing eol_service
   (fetch/cache/EOLStatus/upsert → EOL- pseudo-CVE). Model→release mapping:
   - Apple: Intune reports the marketing name → fuzzy-match the endoflife
     release label/name (iPhone 15 Pro Max → 15-pro-max, iPad Air (5th
     generation) → air-5). Bare identifiers (iPhone15,3) are skipped.
   - Samsung: SM-code has no textual overlap with endoflife → curated
     SM-prefix table (S21–S25, A-series 5G, XCover, Tab S9/S10), matched by
     prefix so region suffixes are ignored. Unmapped models are skipped (no
     false-positive). All release names verified against the live API.

2. Android security-patch-level staleness from Intune's
   androidSecurityPatchLevel: age >=90/180/365d → low/medium/high pseudo-
   finding (ANDROID-PATCH-LEVEL-STALE, one per asset). The control instance
   that flags "patches not actually applied" without scraping any vendor
   bulletin. is_pseudo_cve recognises the new prefix.

Added androidSecurityPatchLevel to the managedDevices $select.
Deferred (not built): per-CVE Android bulletin scraping (Google ASB /
Samsung SMR) — brittle, no API.
2026-06-24 10:32:37 +02:00
vulncheck 02cfb39486 feat(assets): filter inventory by sync source
Dropdown on the Assets page (All / Wazuh / Nessus / Intune+Defender /
Manual). Backend already accepts ?source=; this just wires the UI.
2026-06-23 10:01:37 +02:00
vulncheck d460364590 fix(app-cve-scan): platform check kills cross-platform false-positives
Desktop Firefox on a Windows host was matching the Firefox-for-iOS CVE
(cpe:2.3:a:mozilla:firefox:*:...:iphone_os:*, target_sw=iphone_os). We
matched on vendor:product only and ignored the CPE platform field.

Now record each matching cpeMatch's target_sw (CPE index 9) and keep a CVE
for an asset only when target_sw is platform-neutral (*) or names the asset's
OS family (_os_family). Applied to both package and OS scans.

Cache key bumped to v2: → pre-fix rows (without target_sw) are ignored so the
stale FPs aren't served from cache; they re-fetch with the platform data.

Also: 503 backoff raised to >=3s × attempt over 4 tries (NVD 2.0 503s under
load even WITH a key — it's server-side, not auth), and the scan logs whether
NVD_API_KEY is present so a missing key is obvious in the logs.
2026-06-23 10:00:02 +02:00
vulncheck 3123263cf0 feat(app-cve-scan): iOS/iPadOS OS-level CVEs
Apple ships the precise OS version (e.g. 18.1.2) and NVD carries proper
version ranges for cpe:2.3:o:apple:iphone_os / :ipados, so it's the same
clean CPE-range check the desktop apps already use — no new machinery.

Reads asset.operating_system + asset.os_version (already synced from Intune),
so it covers Intune-only iPhones/iPads that have no syscollector packages.
Runs per asset regardless of package inventory; cached per (cpe, version) so
N devices on the same iOS build = one NVD query.

Android intentionally omitted: NVD only lists the base version (13/14/15)
without ranges → needs the Intune security-patch level + Android bulletin
parsing, a separate feature.
2026-06-22 22:19:05 +02:00
vulncheck 36a560d508 fix(app-cve-scan): kill NVD noise/503 storm + Graph 400/429 hammering
Three bugs from the first app-scan run:

1. Linux rpm/deb packages flooded NVD with junk queries
   (python:python:4.6.5-3.el8, epoch 1:3.2, 2.43.0.windows.1, ...) →
   503 storm + false-positives. Those packages are Wazuh's domain. Add
   _clean_version: only clean dotted-numeric versions reach the scanner
   ("7.0.2 (34567)" → "7.0.2"); epoch/release-tag versions are skipped.

2. A transient NVD 503/429 cached an EMPTY result for 7 days → real CVEs
   missed until TTL. _query_nvd_cpe now retries (3x backoff) and raises
   _TransientNVD on persistent 429/502/503/504; lookup_cves returns []
   WITHOUT caching so the next run retries. Permanent 4xx still cache empty.

3. v1.0 $expand=detectedApps 400s on this tenant → it 400'd once per
   device, every run. Flip _v1_detected_apps_ok off after the first 400
   and go straight to beta. _get now retries 429 honouring Retry-After.

NVD_API_KEY strongly recommended — keyless NVD is the main 503 source.
2026-06-22 22:15:34 +02:00
vulncheck 36976c6647 feat(app-cve-scan): built-in software→CVE scanner (OSV + NVD-CPE)
Maps installed software (Wazuh syscollector packages + Intune detectedApps)
to real CVEs via OSV and NVD-CPE with an own version-range check. Closes the
coverage gap for Intune-only / mobile devices that have no real scanner
(Intune managedDevices add every device but carry no CVE data; Defender TVM
only covers MDE-onboarded hosts with findings).

Design: curated + precise (low false-positives).
- Curated product registry (~24 common apps) name-regex → CPE/OSV; unknown
  app names are ignored (no CPE auto-guessing → no FP storm).
- NVD-CPE: query per product CPE, then verify the installed version actually
  falls inside each CVE's cpeMatch range ourselves (start/end incl/excl,
  exact-version equal, wildcard skipped); versionEndExcluding → fixed_version.
- OSV: precise server-side version match for language ecosystems.
- Results cached per (product_key, version) in app_cve_cache (TTL 7d) so the
  same version across N hosts = one query (and stays under NVD's rate limit).

Findings upsert as source 'app-scan' with real CVE ids → the normal
EPSS/KEV/CVSS enrichment + multi-source remediation apply, and they
cross-confirm with Wazuh/Nessus/Defender on the same (cve, asset).

Wiring:
- Migration 035 + AppCveCache model.
- Piggyback in intune_service._run_app_inventory (detectedApps already fetched).
- POST /api/v1/vulnerabilities/app-cve-scan (sync def → threadpool).
- Nightly scheduler job app_cve_scan_nightly (03:25 UTC).
- Frontend: app-scan source badge + filter + "App CVE Scan" button.

NVD_API_KEY recommended for scale (rate limit). Run alembic upgrade head.
2026-06-21 12:16:44 +02:00
vulncheck cc9954595c fix(ui): Exposure/Risk sort NULLs-last + Affected-Asset links to its findings
Two tester bugs:
- Assets list: sorting Exposure or Risk descending surfaced the empty
  ("—") rows first instead of the real high scores. The generic numeric
  sort branch didn't use nulls_last; now NULLs always sort last in both
  directions.
- CVE detail "Affected Asset" linked to /assets?id= (just the asset list).
  Now links to /vulnerabilities?asset_id=<id> → all findings on that asset.
2026-06-21 11:36:16 +02:00
vulncheck 7890391283 fix(defender): populate affected-software (Package) for Defender CVEs
Tester: Defender-sourced CVEs showed an empty Package/affected-software
column, although Defender's own UI lists the affected software (e.g.
"OpenSSL 1.1.1.0").

Cause: /api/machines/{id}/vulnerabilities returns CVE definitions only —
no per-device software. Now run_defender_sync additionally pulls the
tenant-wide SoftwareVulnerabilitiesByMachine assessment (one paginated
export), builds a (machineId, CVE) → "vendor name version" map, and sets
package_name on each Defender finding (and backfills it on an existing row
whose package was empty). Best-effort: if the tenant/plan doesn't expose
the export, behaviour is unchanged.
2026-06-16 13:29:43 +02:00
vulncheck c3a75e7a77 feat(risk): Asset Risk Dimensions — high-value-target scoring + exposure rebalance + URS
Tester: the port-based exposure score put nearly every Windows host at 100
(no separation), and the thing that actually matters — whether a host runs
a crown-jewel role enabling lateral movement / domain takeover — wasn't
captured.

- Migration 034 + model: assets.high_value_score (0-100) + risk_dimensions
  (JSON roles) + _updated_at.
- app/services/risk_dimensions_service.py: detect_risk_dimensions(ports,
  packages) → roles from syscollector ports (port + process) and installed
  packages: Domain Controller, ADCS/CA, backup servers, SW-distribution,
  Exchange, WSUS, MSSQL, DNS, DHCP, WinRM. Score = max(weight) + 0.3·rest
  (cap 100). risk_factor() maps it to a URS band (>=90→1.5 … else 1.0).
- exposure_service: rebalanced port weights — baseline Windows
  (SMB/MSRPC/NetBIOS/WinRM) now LOW; real remote-control/cleartext
  exposures (Telnet/VNC/RDP/FTP) stay HIGH. Risk detection runs in the same
  pass (reuses fetched ports + one get_packages call).
- urs_service: URS uses max(operator criticality factor, role factor) — a
  DC/ADCS host rises to critical weighting even at criticality=normal;
  operator can still set higher. criticality field untouched.
- assets API: high_value_score + risk_dimensions in the response + sortable;
  Assets page gets a "Risk" column with score + role badges.

Verified detection: DC(88+389)→100, SQL pkg+WinRM→79, plain Win→0,
Exchange+Veeam→100. Migration 034 required: alembic upgrade head.
Roles need Wazuh syscollector (ports+packages); Nessus/Intune-only → v2.
2026-06-16 13:23:16 +02:00
vulncheck d0d27b2c99 fix(assets): paginate the Assets list (was silently capped at 100)
Tester: the Assets page showed at most 100 entries with no paging and no
page-size control.

- Backend list_assets now returns the pre-pagination total in an
  X-Total-Count header (body stays a plain array — the asset dropdowns on
  the scans/vulnerabilities pages still consume a list). limit/offset were
  already supported.
- Assets page: page + page-size (50/100/250/500/1000) controls, prev/next/
  first/last, "X–Y of N", debounced search, page resets to 1 on any
  filter/search/page-size change.
- The asset dropdowns on the scans + vulnerabilities pages now request
  limit=1000 so large estates aren't silently truncated there either.
2026-06-16 10:49:48 +02:00
vulncheck d90044e696 feat(msrc): newest KB per build-branch+type; sensible M365-Apps handling
Tester-approved: show the newest MSRC KB per (build-branch + update-type)
— normally one entry, two on hotpatch hosts (Security Update + Security
Hotpatch Update); drop the superseded rest.

- msrc_service now stores the MSRC update type (SubType) in the fix row's
  `detail`, so the detail view can keep Security-Update vs Security-Hotpatch
  separately. (Re-run MSRC Enrich to populate it on existing rows.)
- get_vuln_remediations: dedup fixes to newest per (branch, type); for a
  Windows-OS finding narrow to the host's own build branch (1–2 entries).

Special case — Microsoft 365 Apps findings: the MSRC KBs are perpetual/MSI
Office builds that never match the installed Click-to-Run channel build, so
branch-filtering is skipped (it would hide everything). Instead the deduped
per-Office-version set + the aka.ms pointer are shown, and a clear
"Update via Office channel to build <channel build>" hint is prepended —
the actually-actionable fix for C2R installs.

Verified: Windows 10.0.26100.32690 → only the 26100 SU + 26100 Hotpatch;
M365 example 7 KBs → 4 (newest per Office line + aka.ms).
2026-06-15 15:08:06 +02:00
vulncheck 4d6ccb71d2 fix: heavy manual endpoints run off the event loop (no more GUI freeze)
Tester: clicking "M365 CVEs" froze the whole web GUI until the cvelistV5
comparison finished — same class of bug as the earlier date-backfill case.

Cause: these endpoints were `async def` but call fully-synchronous,
blocking services inline (httpx + DB), starving the single asyncio event
loop. Fix: declare them as plain `def` — FastAPI/Starlette then runs them
in a worker threadpool, off the event loop, so the GUI stays responsive
(responses/counts unchanged). Converted: m365-check, eol-check,
exploit-intel/refresh, bulk enrich (Refresh Threat Intel),
override/vulnrichment (Correct CVSS), and the Nessus /sync trigger.

Also:
- Intune detectedApps: v1.0 `$expand=detectedApps` returns HTTP 400 on the
  managedDevice; now falls back to the beta /detectedApps navigation
  collection (paginated). Still best-effort.
- Defender 403 ("Missing application roles"): /api/machines needs
  Machine.Read.All in addition to Vulnerability.Read.All — documented in
  the Intune settings tooltip + .env.example. (Sync stays non-fatal.)
2026-06-15 15:04:00 +02:00
vulncheck 2087b484d9 docs(readme): update to current feature set, remove stale roadmap
The README still described VulnCheck as Wazuh-only with AI — out of date.
Updated to reflect what's actually shipped:

- Tagline/overview: multi-source (Wazuh + Nessus + Microsoft Intune /
  Defender), enrichment, EOL/M365, multi-provider auth.
- Features: new "Scanner & Inventory Integrations" (Wazuh/Nessus/Intune/
  Defender + cross-confirm + soft-inactive lifecycle), Threat-Intel
  Enrichment (EPSS/KEV/EUVD/exploit catalogs/cvelistV5/Vulnrichment/NVD),
  EOL & Microsoft 365 detection, Multi-Source Remediation (scanner/MSRC/
  Ubuntu/RHEL/OSV), AI on-demand remediation.
- Configure-integrations + Integrations note + RBAC settings row + the
  architecture diagram updated for the new sources.
- Removed the Roadmap section (its items — LDAP/AD auth, more SIEM
  integrations, scanning without Wazuh — are now shipped).
2026-06-15 09:57:54 +02:00
vulncheck 8e8cfee19a feat(msrc): host-matched KB + MSRC update-guide link in remediation block
Tester, two MSRC-detail changes:

1) The MSRC remediation listed a KB for every Windows version (noise).
   Now the block shows only the KB(s) matching the affected host's Windows
   build branch (e.g. installed 10.0.26100.32690 → only the 26100-line
   KBs, newest first); falls back to the full list when nothing matches or
   the host build is unknown. Filtering is server-side in
   GET /vulnerabilities/{id}/remediations using the asset os_version.

2) Prepend the deterministic MSRC update-guide link for the CVE
   (https://msrc.microsoft.com/update-guide/vulnerability/<CVE>) to the
   MSRC block. Advisory items now render as clickable links in the UI
   (also surfaces OSV/Ubuntu advisory URLs that were previously hidden).

No migration / no new fetch — pure response shaping over cached MSRC rows.
2026-06-15 08:30:57 +02:00
vulncheck 355adad829 feat(m365): fill real CVE title + description from cvelistV5
Tester: M365 findings showed the synthetic placeholder description; the
CVSS-correction cascade (by design, for efficiency) never touches
title/description. Fill them separately from the authoritative source.

apply_real_cve_metadata(db, cve_ids): fetches the real CVE title + English
description from CVE.org cvelistV5 raw and writes them onto the M365
findings (first_detected_by='m365_check'), keeping a trailing note that
the finding originated from the Microsoft 365 Apps source (so the
provenance stays clear). Runs after CVSS-correction + enrichment in both
run_m365_check and run_m365_for_packages.

Verified live: CVE-2026-45456 → title "Microsoft Outlook and Word Remote
Code Execution Vulnerability" + the real type-confusion description.
2026-06-13 13:22:16 +02:00
vulncheck c2cad0088d feat(intune): Phase 3 — Defender for Endpoint TVM real CVEs
Optional per-device CVE feed from Microsoft Defender for Endpoint (TVM),
toggled by `defender_tvm` in the Intune settings card. Reuses the same
Entra app (tenant/client/secret) but a separate API + scope.

- app/integrations/defender_client.py: client-credentials token for scope
  https://api.securitycenter.microsoft.com/.default; get_machines() +
  get_machine_vulnerabilities() (paginated); test_connection().
- app/services/defender_service.py: run_defender_sync — match Defender
  machine → asset by computerDnsName (pin defender_machine_id), upsert
  REAL Vulnerability rows (source='defender', cross-confirm via add_source
  with wazuh/nessus/intune), then VULNERABILITY_DETECTED audit + EPSS/KEV/
  date enrichment. Real CVE ids → cvelistV5/MSRC/OSV remediation applies.
- intune_service.run_intune_sync runs the Defender pass when enabled.
- Settings card: "Defender TVM CVEs" toggle (needs Vulnerability.Read.All
  on WindowsDefenderATP + Defender licensing).

Needs Application permission Vulnerability.Read.All (WindowsDefenderATP) +
admin consent. No migration (defender_machine_id added in 033).
2026-06-13 10:40:57 +02:00
vulncheck 922341b378 feat(intune): Phase 2 — detectedApps feed EOL + M365 detection
Intune managed devices often run without a Wazuh agent, so their installed
software was invisible to EOL/M365 detection. Now the Intune sync reads
each device's detectedApps (Graph $expand=detectedApps) and runs them
through the existing detection.

- eol_service.run_eol_for_packages(db, asset, packages): source-agnostic
  per-package EOL (endoflife.date → MS-lifecycle/exotics fallback), same
  precedence as the eol-check endpoint.
- m365_service.run_m365_for_packages(db, asset, packages): source-agnostic
  M365-Apps CVE detection (build-vs-channel) + real-metric correction.
- intune_service: detectedApps enrichment now on by default (toggle
  "Detected apps (EOL/M365)" in the Intune settings card).

Findings are CVE-level, so OSV/MSRC/Ubuntu remediation enrichment and the
normal EPSS/KEV/date enrichment apply automatically. No migration.
2026-06-13 10:37:22 +02:00
vulncheck 5418d6a9d3 feat(intune): Microsoft Intune/Graph inventory source — Phase 1 (devices + OS-EOL)
Third inventory source next to Wazuh/Nessus: pulls Intune managed devices
via Microsoft Graph (app-only client-credentials) → assets + OS-level EOL.

- Migration 033: INTUNE assetsource label + assets.intune_device_id /
  defender_machine_id (+ indexes). Model updated.
- app/integrations/graph_client.py: client-credentials token cache (mirrors
  wazuh_client), paginated managedDevices, get_detected_apps (phase 2),
  test_connection. Plain httpx, no msal dependency.
- app/services/intune_service.py: run_intune_sync — find-or-create asset
  (intune_device_id → hostname → auto-create) mirroring nessus_sync,
  source=INTUNE, refresh OS/version, OS-EOL via eol_service.check_os_eol,
  id-keyed lifecycle reconcile.
- asset_lifecycle.reconcile_intune_by_seen_ids (mirrors the Nessus one).
- app/routers/intune.py: POST /api/v1/integrations/intune/test (admin),
  /sync (editor, fire-and-forget 202). Registered in main.py.
- scheduler: intune_sync_nightly (sync def) at 02:10.
- Settings: encrypted intune_config (PROTECTED_SETTING_KEYS); settings PUT
  now MERGES secret subfields (blank/"***set***" keeps the stored secret)
  so JSON configs can be edited without re-typing secrets; client_secret
  added to redaction subfields.
- Frontend: "Microsoft Intune (Graph API)" settings card (tenant/client/
  secret + Test/Sync), intune/defender source badges, Asset type field.
- .env.example documents the Entra app registration + permissions.

Migration 033 required: alembic upgrade head. detectedApps→EOL/M365 is
phase 2 (toggle present, off by default until the per-package helpers land).
2026-06-13 10:34:51 +02:00
vulncheck 9714fb72a2 feat(m365): real CVSS/metrics + richer description at CVE check-in
Tester (follow-up to Plan P 45524f7): M365 Apps CVEs were created with a
placeholder severity=medium / cvss=None and a thin description. Pull the
real metrics straight away at check-in and clarify the description.

- After an M365 check creates/updates findings, run the CVSS-correction
  cascade (vulnrichment → cvelistV5 → NVD) for the touched CVE ids, then
  enrich_vulnerabilities (EPSS/KEV/EUVD + NVD dates). These are real CVE
  ids, so they resolve like any other. The override path never touches
  `description`, so the M365 source note is preserved.
- Description rewritten: states the affected product, installed vs fixed
  build + "update via the Office channel", explains the detection source
  (Wazuh syscollector build vs MS365 release notes — not in NVD/Wazuh),
  and notes metrics come from MSRC + Vulnrichment/cvelistV5 with a pointer
  to the MSRC remediation section.

No migration. CVSS/severity now populated on the next M365 Enrich run.
2026-06-13 10:05:09 +02:00
vulncheck 933a47a10e feat(osv): add OSV.dev as an additional CVE remediation source
Tester idea: use the OSV.dev aggregator to broaden Linux/cross-ecosystem
coverage. Design question (fallback vs overwrite) → neither clobbers:
OSV is added as its OWN source (own UI block, source='osv'), never
overwriting vendor rows, and it also fills gaps for distros/ecosystems the
vendor providers don't cover (Debian, SUSE, Alpine, Rocky, AlmaLinux, npm,
PyPI, Go, ...).

- linux_remediation_service.fetch_osv(): GET api.osv.dev/v1/vulns/<CVE> →
  per-ecosystem fixed versions (skips commit-hash "fixes") + filtered
  advisory references (USN/RHSA/DSA/GHSA/errata/SUSE/Alma...).
- enrich_cve_linux() now stores the matching vendor provider AND OSV as
  separate sources per CVE.
- GET /vulnerabilities/{id}/remediations triggers on-demand enrichment for
  ANY non-Windows host (was Ubuntu/RHEL only), so OSV covers Debian/SUSE/
  etc.; cached in cve_remediations, off the event loop.
- UI labels the block "via OSV.dev (aggregator)".

Verified live: CVE-2023-48795 → OSV yields Debian DSA / GHSA / FreeBSD
advisory links the vendor providers don't. No migration (reuses 032).
2026-06-13 10:01:45 +02:00
vulncheck ac16f99be7 feat(linux): Ubuntu USN + RHEL/CentOS errata remediation enrichment
Tester feature (step 2 of multi-source enrichment): add Linux distro fixes
alongside the Nessus scanner solution and MSRC, reusing cve_remediations.

Unlike MSRC (monthly bulk doc), the Linux trackers are queryable per-CVE,
free and unauthenticated — so we fetch on demand when the CVE detail opens
for a Linux host, then cache into cve_remediations (repeat views instant,
re-fetch refreshes).

app/services/linux_remediation_service.py:
  - provider_for_os(): Ubuntu -> ubuntu; CentOS/RHEL/Rocky/Alma/Oracle/
    Fedora -> redhat (CentOS/Alma rebuild RHEL, so the RHSA + fixed NVR is
    the actionable fix).
  - fetch_ubuntu(): ubuntu.com/security/cves/<CVE>.json → per-release fixed
    package versions (kind=fix), USN advisories, mitigation.
  - fetch_redhat(): access.redhat.com securitydata → RHSA advisory + fixed
    package NVR + errata link, mitigation/statement.
  - enrich_cve_linux(): pick provider by host OS, replace cached rows.

GET /vulnerabilities/{id}/remediations now triggers this enrichment
(off the event loop via asyncio.to_thread) on first view for a Linux host.
UI groups it under "via Ubuntu USN" / "via Red Hat / CentOS / Alma".

Verified live: CVE-2024-6387 (regreSSHion) returns per-release openssh
fixed versions + USN-6859-1 + the LoginGraceTime mitigation.

No migration (reuses cve_remediations from 032).
2026-06-10 13:31:39 +02:00
vulncheck 29f828678b feat(msrc): Microsoft (MSRC) per-CVE remediation enrichment
Tester feature (step 1 of multi-source enrichment): augment the Nessus-
only scanner remediation with authoritative Microsoft data, for Windows
OS *and* MS products (Office/365, .NET, SQL, Exchange, ...).

MSRC's per-CVE endpoint 404s, so we ingest the monthly CVRF documents
(api.msrc.microsoft.com/cvrf/v3.0/cvrf/{YYYY-Mon}, ~4 MB each) and extract
per-CVE remediations:
  - fixes:   KB number + FixedBuild + download URL (Remediations Type 2/3)
  - workarounds / mitigations (containment): Notes "Workarounds" /
    "Mitigations", HTML stripped to text — covers the "no KB yet, only
    containment" case the tester called out.

- Migration 032 + model: cve_remediations (CVE-level, source-tagged).
- app/services/msrc_service.py: refresh_msrc() pulls the last N monthly
  docs (default 18, setting msrc_months_back), stores rows only for CVE
  ids already in the DB (keeps it relevant). Re-parse replaces a CVE's
  rows so MS revisions (containment-only -> KB later) self-update.
- Endpoints: GET /vulnerabilities/{id}/remediations (scanner + external,
  grouped by source) and POST /vulnerabilities/msrc/refresh (fire-and-
  forget background thread). Weekly scheduler job (Sun 04:40).
- UI: CVE detail now renders a Remediation block per source ("via scanner"
  / "via Microsoft (MSRC)") with KB + download links, workarounds, and
  mitigation/containment. "🛡️ MSRC Enrich" button on the vuln list.

Verified parse against the live 2026-May CVRF doc (KB+build+catalog link
per Windows build). Migration 032 required: alembic upgrade head.

Step 2 (Linux: Ubuntu USN / CentOS errata) reuses cve_remediations next.
2026-06-10 13:29:25 +02:00
vulncheck eea9f5aa94 feat(ai): EOL-aware remediation prompt (upgrade plan, not "apply patch")
Tester: AI remediation for EOL/EOS findings was generic and sometimes
hallucinated a patch that doesn't exist (the product is out of support).

For pseudo-CVE findings (cve_id starts with EOL- / NESSUS-PLUGIN-), the
prompt now switches to an end-of-life system prompt: state the EOL/EOS
risk, name the supported target release + timeline, give OS-specific
upgrade/replace commands and download location, list interim compensating
controls / containment while the migration is pending, and a verification
step — explicitly told NOT to suggest applying a non-existent patch.
Real-CVE findings keep the existing patch-focused prompt.
2026-06-10 13:19:26 +02:00
vulncheck 00d22fc318 feat(audit): initial VULNERABILITY_DETECTED event for sync-created findings
Tester: a CVE newly created by a sync appeared in the vuln list but left
NO initial audit entry ("new CVE detected on asset X at ...") — the audit
trail only began with the first status change (open -> patched etc.).
Confirmed: not intentional, simply never built; fails the revisionssicher
requirement.

- Migration 031: ALTER TYPE auditeventtype ADD VALUE
  'VULNERABILITY_DETECTED' (idempotent, autocommit block).
- New app/services/audit_events.py: audit_new_vulnerabilities() writes one
  System/Auto event per newly created finding:
  "New finding detected: CVE-X on <hostname> (severity=..., source=...)".
- Wired into every creation path:
    * Wazuh full sync (run_wazuh_vulnerability_sync)  -> source=wazuh
    * Wazuh per-agent sync (sync_agent_vulnerabilities, covers the
      scheduler loop + per-asset rescan)              -> source=wazuh
    * Nessus sync (incl. Nessus EOL pseudo-vulns via
      newly_created_vuln_ids)                         -> source=nessus
    * endoflife.date upsert                           -> source=eol_check
    * M365 Apps upsert                                -> source=m365_check
  Full-sync and per-agent paths are independent (no double events).
- Best-effort: audit failure never breaks a sync.

Migration 031 required: alembic upgrade head.
2026-06-10 08:58:38 +02:00
vulncheck 3e382a489d fix(eol): stop short product names matching unrelated lifecycle listings
Tester false-positive: the evergreen Microsoft Edge browser (148.x/149.x)
was flagged EOL as "Azure Stack Edge" (end 2024-03-31).

Cause: the lifecycle-export matcher was bidirectional (rn in target OR
target in rn). "Microsoft Edge" normalises to just "edge" ("microsoft" is
stripped), and "edge" is a substring of the unrelated listing
"azure stack edge" — so the reverse direction matched.

Fix: one direction only — the LISTING name must be contained in the
product name. Verified against the live April-2026 export:
  Microsoft Edge / Edge WebView2  -> no match (was Azure Stack Edge)
  SQL Server 2014 Management Objects -> SQL Server 2014 (kept)
  Exchange Server 2016               -> kept
  a real "Azure Stack Edge" device   -> still matches

Existing wrong EOL-MS-LIFECYCLE-Azure_Stack_Edge rows on Edge hosts won't
be recreated; dismiss them once via the detail-page Dismiss button.
2026-06-10 08:52:14 +02:00