Commit Graph
21 Commits
Author SHA1 Message Date
vulncheck 65d6c62561 fix(rbac): show assignee as text for read-only (not a dead 'Unassigned' select)
Read-only users can't fetch /auth/users (admin-only), so the disabled assign
<select> resolved to 'Unassigned' even when the finding/asset WAS assigned — the
head icon showed assigned but the name was hidden. Render the assignee name
straight from the payload (assigned_user_name / group name) as read-only text
instead. Applied on both the vulnerabilities and assets lists. Also fix a
pre-existing invalid title= prop on the Nessus <svg> (use a <title> child).
2026-07-13 13:09:00 +02:00
vulncheck 96e6072a41 fix(assets): hide write-actions from read-only users (no more 403 alerts)
A read-only user saw every row action (Wazuh/Nessus/app rescan, edit, delete,
assign) and Add Asset, but the backend gates those (RequireEditor/RequireAdmin)
so clicking threw 'Access denied. Required role: editor'. Fetch the current
role and show mutating actions only to editor+ (delete to admin); read actions
(installed software, coverage gap, exposure) stay for everyone.
2026-07-10 14:07:31 +02:00
vulncheck ae52cfe293 feat(assets): on-demand installed-software view per asset
New GET /assets/{id}/software pulls the live software inventory (Wazuh
syscollector packages, else Intune detectedApps) — the same inventory the
app-CVE scanner consumes — without persisting it. A list-icon button on each
Wazuh/Intune-backed asset row opens a modal with name/version/vendor.
Read-only, deduped, sorted. No DB schema (ponytail: on-demand until a
searchable/reporting inventory is actually needed).
2026-07-10 13:37:47 +02:00
vulncheck c3b9bb6cbf fix(app-scan): exclude OpenSSL FIPS builds (FP) + per-asset app re-scan button
- OpenSSL FIPS provider builds (e.g. Veeam's 'OpenSSL v3.0.0 FIPS') were
  matched against OpenSSL CVEs, but the advisories explicitly carve the FIPS
  modules OUT (vulnerable code is outside the FIPS boundary) and they carry a
  separate 4-part build version — pure false positive (CVE-2025-15467). Exclude
  via negative lookahead 'openssl(?!.*fips)'. Existing FP self-heals: next scan
  no longer detects it -> auto-resolve marks it patched (now audit-logged).
- Add an 'App CVE re-scan' action button on asset rows (Wazuh- or Intune-backed
  assets) that hits the existing POST /vulnerabilities/app-cve-scan?asset_id=,
  analogous to the Wazuh rescan button.
2026-07-10 13:33:25 +02:00
vulncheck d6d2c3209b fix(ui): rebrand left ~115 truevuln-blue classes with no matching CSS var
The rebrand renamed many Tailwind color classes vulncheck-blue → truevuln-blue
but left the theme token as --color-vulncheck-blue, so those 115 classes
referenced an undefined color → transparent backgrounds / unstyled text
(tester: the "Save Template" button was invisible until hover; also affected
buttons, links, focus rings, sort arrows app-wide).

Unified everything on truevuln-blue: renamed the @theme token to
--color-truevuln-blue and the remaining 156 vulncheck-blue class usages to
truevuln-blue. Now all 271 usages resolve to one defined token; 0 vulncheck-blue
left.
2026-07-09 08:21:07 +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 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 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 1fb49e4336 fix(assets): show INACTIVE assets by default — that's the point of the status column
Tester: a Nessus sync correctly soft-inactivated asset #36 (verified in
DB + audit ASSET_DEACTIVATED), but it then vanished from the Assets view
entirely — not even visible with an INACTIVE badge.

Cause: the asset list defaulted to ACTIVE-only and hid INACTIVE behind the
"Show inactive" toggle. But the whole purpose of the Status column is to
surface soft-inactive hosts inline (amber INACTIVE badge), not bury them.

Fix: default list now shows ACTIVE + INACTIVE (hiding only
DECOMMISSIONED, which is operator-final). The toggle is repurposed to
"Show decommissioned" (include_inactive=true reveals those). Status-filter
and audit/history behaviour unchanged.
2026-06-03 15:00:30 +02:00
vulncheck d22ddb73b9 fix: dashboard full width, EOL-NESSUS homogenisation, published sort fallback
Tester screenshots — four issues:

1) Dashboard/assets wasted huge left/right gutters. Dropped the
   max-w-[1800px] cap on both pages -> content uses full available width
   (minus AppShell padding).

2) After a Nessus re-sync, a finding still showed BOTH the legacy
   EOL-NESSUS-{plugin_id} row AND the new slug-named row (e.g. Adobe
   Reader -> EOL-ADOBE-ACROBAT-...). The slug alias already resolves, but
   the old plugin-id row was never removed. _upsert_nessus_eol now deletes
   the legacy EOL-NESSUS-{plugin_id} row for the asset whenever the plugin
   resolves to a real product slug. Re-sync homogenises existing data.

3) "Newly Published" still looked unsorted: until the NVD published_date
   backfill drains, most rows have published_date NULL and were ordered by
   id (meaningless). Added a secondary sort on the CVE's own year+sequence
   so "newest CVE number first" holds even before backfill. Bumped the
   no-key NVD backfill cap 60 -> 150/run so dates fill faster.

4) Assets "Assigned To" select truncated to "Unas..." — widened
   maxWidth 140 -> 200px (minWidth 120).
2026-06-03 13:53:05 +02:00
vulncheck 45e2be8d13 fix(ui+audit): revisionssicher asset-delete log + wider page layout
From tester screenshot feedback (batch 2026-06-03):

#3 Asset deletion audit was thin ("Asset deleted: <truncated>") with
no clear WHO/impact. Now logs analog to the CVE status-change
entries: event_description names the host + IP + deleting user +
cascade-removed vuln count, and old_value carries a JSON snapshot
(hostname, ip, os, source, status, wazuh_agent_id, vuln count,
deleted_by). Audit committed BEFORE the delete so it survives even
if the cascade fails.

#2 + #4 Dashboard + Assets wasted large left/right gutters on wide
monitors and the assets table got squished/clipped (max-w-7xl =
1280px). Bumped dashboard, assets and scans pages to max-w-[1800px]
so wide screens use the space and the assets columns (incl. the new
Exposure col) stop clipping.

Still open (needs the tester's TXT, unreadable from Downloads
sandbox): EOL/EOS MS-product handling + remaining widget-fill polish.
2026-06-03 11:04:43 +02:00
vulncheckandvulncheck 808246f0a9 fix(feedback-2026-06-01): sync-driven INACTIVE, EOL naming, sortable assets
Tester feedback round 2026-06-01 — 8 issues grouped into 4 fixes.

**A. Sync-driven INACTIVE reconciliation** (highest priority)
- AssetSource.NESSUS enum value added; pre-existing MANUAL assets with
  a nessus_host_uuid re-tagged in 027 migration.
- New `reconcile_missing_from_sync()` helper in asset_lifecycle flips
  ACTIVE assets of a given source (WAZUH/NESSUS) to INACTIVE when their
  id (wazuh_agent_id or nessus_host_uuid) is missing from the latest
  sync. Vice-versa reactivates INACTIVE assets that re-appear.
- Hooked from nessus_sync.run_nessus_sync (after per-host loop, before
  commit) and from POST /api/v1/assets/sync_wazuh (after the agents
  loop). Both return assets_inactivated/assets_reactivated counts in
  their response.
- INACTIVE-asset CVEs now hidden from list_vulnerabilities and
  get_dashboard_statistics by default (new
  `include_inactive_assets` query param opts back in).
- AuditEventType gains ASSET_DEACTIVATED + ASSET_REACTIVATED so the
  transitions are filterable in the audit UI.

**B. EOL pseudo-CVE naming for Nessus + endoflife.date**
- nessus_sync: new `_office_pseudo_cve()` returns
  EOL-MS-OFFICE-{YEAR} for Office variants, else EOL-NESSUS-{pid}.
  `_normalise_office_pkg()` collapses MUI/Proofing strings to "MS
  Office". Title suffix "OSX MUI..." trimmed for Office.
- nessus_sync: end-of-pass sweep collapses pre-existing
  EOL-NESSUS-{pid} Office rows into the unified EOL-MS-OFFICE-{year}
  anchor (status=patched + audit).
- eol_service: _PRODUCT_SLUGS now maps microsoftoffice[proofing/osxmui
  /osxmuigerman/formac] -> ms-office. _YEAR_KEYED_SLUGS includes
  ms-office. _pseudo_cve_id produces EOL-MS-OFFICE-{year} for free.
- eol_service: existing-row branch bumps detected_at so the
  sort_by=detected_at widget ranks freshest finding first.

**C. Asset view icon asymmetry + dashboard panel fix**
- assets/page.tsx: coverage-gap icon now shows for any asset with
  wazuh_agent_id OR nessus_host_uuid.
- nessus_sync._find_or_create_asset: backfills ip_address on existing
  assets (UUID / hostname match) so the "Launch targeted Nessus scan"
  button (already gated only on asset.ip_address) lights up.
- frontend types + backend AssetResponse expose nessus_host_uuid +
  source.
- page.tsx CVE-id cell: `break-all` -> `whitespace-nowrap` so the
  free L/R dashboard space isn't wasted on character-wrapped ids.

**D. Assets table sortable columns**
- list_assets: new sort_by / sort_order with whitelist-driven ORDER BY.
  Last-scan sort uses nulls_last(). Joins Policy/User only for those
  columns. Unknown sort_by falls back to hostname asc.
- assets/page.tsx: sortBy/sortOrder state, handleSort toggler, SortArrow
  indicator, clickable headers with cursor-pointer + hover style, and
  a useEffect re-fetch on change. Mirrors the pattern in
  vulnerabilities/page.tsx.

**Out of scope (flagged)**
- `delete_asset` remains a hard delete with cascade. The user's
  "previously-deleted asset not re-created by Scan+Sync" expectation is
  intentional design — the auto-create gate (auto_create_assets) and
  the new INACTIVE flip cover the rest of the use cases.
- DECOMMISSIONED AssetStatus is still unreachable through automation.
- Vulnerability-count column in assets table stays non-sortable (it
  is a Python-side count, not a SQL column).

**Migration**
- 027_add_nessus_source_and_audit_events.py: ALTER TYPE for both
  enums + UPDATE backfill. Run `alembic upgrade head` once on prod.
2026-06-02 08:58:51 +02:00
vulncheck f2a2f644e1 fix(eol+ui): Office year-keyed EOL match + table overflow clipping
eol_service: Office (and other year-keyed products) report a build
number from Wazuh syscollector (16.0.4266.1001) while endoflife.date
keys releases by year (name/label '2016'). The numeric prefix match in
_pick_release never matched, so Office 2016 EOL went undetected. Add a
year-from-product-name resolver (_YEAR_KEYED_SLUGS, _extract_year,
_pick_release_by_year) used before the numeric fallback. SQL Server and
other numeric-versioned products are unaffected.

dashboard: long EOL pseudo-CVE ids (EOL-WINDOWS-SERVER-2016) plus
whitespace-nowrap forced the 'Newly EOL/EOS' widget wider than its grid
column, scrolling CPR/Flags out of view. CVE cell now break-all.

assets: 9 columns at px-6 padding (~432px) overflowed max-w-7xl and
clipped the Actions column. Reduced table padding to px-3.
2026-06-01 13:16:06 +02:00
vulncheck af94797c3b feat(assets): coverage-gap report — installed packages with no finding (#4)
Tester: Wazuh's vuln-detector misses some products (MS365, mRemoteNG,
…). Chosen approach (safe option): surface the GAP, make NO automatic
CVE assignment → zero false positives.

Backend
- GET /api/v1/assets/{id}/coverage-gap: fetches syscollector packages,
  cross-references against the asset's open-vuln package_name blob
  (token match, len>=3 to avoid substring noise). Returns packages
  WITHOUT any finding, each annotated with its endoflife.date status
  when known (EOL / EOL SOON / out-of-active-support) — a hint only,
  never a CVE claim. Pure local data, no NVD, no rate-limit risk.

Frontend
- Amber check-circle action per Wazuh-linked asset opens a modal
  listing the gap packages (name / version / EOL hint), with a clear
  "investigate manually — no automatic CVE claim" disclaimer.

Operator workflow: spot a high-value uncovered package (e.g. an EOL
mRemoteNG), investigate in NVD/vendor advisories, mark accordingly.
Deliberately conservative — keeps data quality intact.
2026-06-01 08:45:32 +02:00
vulncheck f6ea2f1e17 feat(assets): network-exposure risk dimension from Wazuh ports (feedback #5)
Tester: use Wazuh IT-hygiene (open listeners) as an extra risk
indicator — a host exposing VNC/RDP/Telnet is network-vulnerabler
regardless of CVE count.

Schema (migration 026)
- assets.network_exposure_score (FLOAT 0-100, partial-indexed >0)
- assets.exposed_services (JSON [{port, proto, service, risk, ip}])
- assets.exposure_updated_at

Service (exposure_service.py)
- get_ports() added to WazuhClient (/syscollector/{agent}/ports).
- analyze_ports(): classifies LISTENING sockets against a risky-port
  table (Telnet 40, RDP/VNC/SMB 30-35, FTP/rsh 28-30, DB ports 22-26,
  SSH 8, …). Loopback-bound listeners excluded. Score = strongest
  listener at full weight + 40% of each additional, capped 100.
- refresh_all_exposure() walks Wazuh-linked assets, persists.

API + scheduler
- POST /api/v1/assets/refresh-exposure (on-demand).
- Nightly job 02:30 UTC.
- AssetResponse exposes network_exposure_score + exposed_services
  (JSON parsed to list) + exposure_updated_at.

Frontend
- Assets list: new Exposure column — coloured score badge (red ≥60,
  orange ≥30, yellow else) with the top services inline + full list
  in the tooltip.
- " Exposure" toolbar button triggers a refresh.

Informational only — does NOT auto-change asset criticality (operator
owns that). Surfaces the data so the operator can raise criticality
on heavily-exposed hosts.
2026-06-01 08:40:36 +02:00
vulncheck 77adee6d91 feat(assets): soft-inactive lifecycle for decommissioned assets (Plan G2)
Tester: when a system is removed from Wazuh + Nessus (org's "system
no longer exists" process), VulnCheck kept a data corpse needing
manual deletion. Hard-delete would lose the vuln history + break the
revisionssicher audit trail. Compromise: SOFT-inactive.

Service (asset_lifecycle.py)
- reconcile_asset_lifecycle(db): asset whose last_scan (any source
  sync) is older than `asset_inactive_after_days` (setting, default
  30, 0=disabled) → status INACTIVE. Recently-seen INACTIVE assets
  auto-revive to ACTIVE. DECOMMISSIONED is operator-final, never
  auto-revived. Both transitions audit-logged (user_id=None).
  Vuln rows + audit survive — only the default views hide it.

Scheduler + endpoint
- Nightly job at 04:15 (after URS).
- POST /api/v1/assets/reconcile-lifecycle for on-demand run.

Asset list
- Default now shows ACTIVE only (was active+inactive). New
  include_inactive=true param surfaces inactive + decommissioned.

Frontend
- "Show inactive" checkbox on the assets page (re-fetches on toggle).
- Status badge: active=green, inactive=amber, decommissioned=grey,
  each with an explanatory tooltip.

Configurable retention: set asset_inactive_after_days in settings
(0 disables auto-inactivation entirely for orgs that prefer manual).
2026-06-01 08:28:03 +02:00
vulncheck b1f9a86923 feat(urs): dashboard urs widget + asset criticality picker
Dashboard widget:
- new 'Unified Risk Score (URS)' card above the existing compliance
  widget. Hidden when no asset has a URS yet so empty installs stay
  clean. Three columns:
    * Avg URS (big colour-coded number per band)
    * Severity spread badges (CRITICAL/HIGH/MEDIUM/LOW/NONE counts)
    * Top-5 highest-risk assets with URS + severity pill
- 'Open >' link jumps to /compliance page

Asset edit modal:
- new 'Asset Criticality' dropdown (Low/Normal/High/Critical, with
  the multiplier ×0.7..×1.5 shown next to each option for transparency)
- value preloaded from asset.criticality on edit, persisted via the
  PUT /api/v1/assets/{id} endpoint extended in the previous commit
2026-05-19 14:56:52 +02:00
vulncheck 5c4a9a8bf9 fix(ui): clarify Nessus rescan confirm dialog wording 2026-05-14 08:19:07 +02:00
vulncheckandClaude Opus 4.7 7b5d1cdc45 feat(nessus): scanner_type in schedules + targeted host rescan
- ScanSchedule: scanner_type field now exposed in API schema + UI
  modal dropdown (Wazuh / Nessus); existing schedules default to wazuh
- Schedule list shows WAZUH/NESSUS badge per row
- NessusClient: _post() helper + launch_scan(scan_id, alt_targets)
  uses Nessus POST /scans/{id}/launch with alt_targets override
- POST /api/v1/vulnerabilities/nessus/scan-host: resolves asset IP,
  picks scan template from config/request, launches targeted Nessus
  scan on that single host — useful for post-patch rescans
- Assets page: purple globe button per asset with IP address triggers
  targeted Nessus scan via scan-host endpoint

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 08:06:23 +02:00
vulncheck 6969d0c62e Initial release v1.0.0
VulnCheck - Open Source Vulnerability Management for Wazuh

Features:
- Vulnerability management with Wazuh integration
- AI-powered CVE analysis (OpenAI, Anthropic, Google, DeepSeek, Ollama, Infomaniak)
- SLA policy enforcement with automated email alerts
- Automated patch verification via Wazuh Syscollector
- Role-based access control (Admin, Editor, Readonly)
- PDF/CSV reporting for compliance workflows
- Full audit trail

https://gitea.isuit.ch/vulncheck/vulncheck
2026-02-08 10:15:20 +01:00