Commit Graph
40 Commits
Author SHA1 Message Date
vulncheckandClaude Opus 4.7 6a1fddfd89 feat(ui): cve_id sort default on dashboard + VPR badge in vulns list
Tester feedback: 'Last Vulnerabilities' on the dashboard should be
sorted by CVE-ID descending (newest CVE number first) — not by
detected_at. Backend already exposes sort_by=cve_id.

Tester also missed the Tenable VPR Score because it was only rendered
on the vuln detail page. Adds a small colored VPR badge in the
Priority column of the vulns list when nessus_vpr_score is present,
reusing the colour scale from the detail page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 16:22:20 +02:00
vulncheckandClaude Opus 4.7 f70bffe6f6 fix(override): use github raw URL + correct vulnrichment 2.x parser shape
The previous load_cisa_vulnrichment_data() hit a non-existent feed
URL (cisa.gov/sites/default/files/feeds/vulnrichment.json) and parsed
a flat shape that does not match the real Vulnrichment 2.x format.
Result: the 'Fix CVSS' button silently corrected nothing, e.g.
CVE-2026-8390 stayed at the Wazuh placeholder 10.0 instead of the
authoritative 7.3 HIGH from CISA.

This change:

- Fetches per-CVE JSON from
  raw.githubusercontent.com/cisagov/vulnrichment/develop/{year}/{N}xxx/
  {CVE-YYYY-N}.json. 404 = CVE not yet analysed by CISA (very common
  for new CVEs) and is treated as 'no data', not an error.
- New _parse_vulnrichment_record() walks containers.adp[].metrics[] to
  extract cvssV3_1.baseScore / baseSeverity plus the SSVC Exploitation
  option (none|poc|active|widespread).
- correct_vulnerability_scores() now returns a not_found counter so the
  UI can distinguish 'feed unreachable' from 'CVE not in feed yet'.
- Vulns page alert appends 'N CVEs noch nicht im Feed' when not_found>0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 16:21:11 +02:00
vulncheckandClaude Opus 4.7 4ed1beab8e feat(brand): new logo — brand-blue shield with bold check + verified ping
Replaces the generic round-shield-with-thin-check mark with a logo that
matches the actual brand palette:

- Shield body uses the vulncheck-blue (#0066FF) primary token with a
  subtle vertical gradient to deeper royal blue. Reads well on the
  bg-gray-900 sidebar and the bg-gray-50 login screen alike.
- Bold white check (44pt stroke) with a soft inner highlight — no toy /
  crosshair vibe.
- Small securis-success (#10B981) ping at the check terminus = the
  "vulnerability verified / cleared" semantic.
- Stays crisp at 24px (favicon, browser tab) up to 240px (login).

No code changes required — all consumers (layout.tsx favicon, login
page, AppShell, Drawer) already point at /logo.svg.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 08:23:31 +02:00
vulncheck 3074820737 feat: CVSS score override service with CISA Vulnrichment support
- Add VulnOverrideService to correct erroneous Wazuh CVSS scores (e.g. 10.0 placeholder)
- Add CISA Vulnrichment integration for verified CVSSv3.1 scores and SSVC exploitation status
- Add new API endpoints:
  - GET /override/check - find incorrect scores
  - POST /override/nessus/{asset_id} - override from Nessus
  - POST /override/vulnrichment - override from CISA (all CVEs)
  - GET /override/stats - score error statistics
- Add exploitation_status (SSVC: none/poc/active/widespread) and exploitation_source columns
- Add 'Correct CVSS' button in vulnerabilities frontend
- Add SSVC badge display in vulnerability table
- Update Vulnerability model and types

Fixes: CVE-2026-8390 incorrect CVSS 10.0 → 7.3 HIGH
2026-05-14 20:36:51 +02:00
vulncheck d903ca41cb fix(nessus): bug fixes from code review
- plugin_cvss() now uses _plugin_attrs() helper (eliminates duplicate
  inline path extraction — single place to update if Nessus changes payload)
- non_cve_skipped initialised to 0 in stats dict so the field is always
  present in the sync response even when every finding has a CVE
- vuln detail page: show title + description separately instead of
  title || description (description was silently hidden when title existed)
- vuln detail page: add "Detection Sources" card — source badges (WAZUH /
  NESSUS), cross-confirmed indicator, first_detected_by, Nessus plugin ID
  (links to tenable.com/plugins), and Tenable VPR score with colour coding
2026-05-14 17:38:05 +02:00
vulncheck 9ae16a3668 feat(nessus): import VPR, exploit_available, maturity, description, solution
Nessus plugin payloads carry more than just severity + CVE — we now
extract and store everything actionable:

- nessus_vpr_score (new column, migration 011): Tenable's VPR rating
  (0-10), independent from our own priority_score
- exploit_available (existing boolean): True when Nessus knows of a
  public exploit; only escalates, never overwrites Wazuh-confirmed True
- exploit_maturity (existing string): Unproven / PoC / Functional / High
- description: prose description + 'Solution:' section, only set when
  empty so hand-written notes survive
- references: see_also URL list as JSON, only when existing is null

Create path sets all fields directly; merge path backfills empties so
existing Nessus findings get enriched on the next sync. API response
adds nessus_vpr_score; frontend Vulnerability type mirrors it.
2026-05-14 17:24:08 +02:00
vulncheck 515c9e3084 fix(nessus): populate package_name from plugin_name + sortable source column
- nessus_sync: new findings now store plugin_name as package_name so the
  Vulns table's PACKAGE column shows the affected software for Nessus-only
  rows (was blank). Merge path backfills package_name / title on existing
  rows that were created before this fix.
- vulnerabilities router: sort_by='source' maps to first_detected_by,
  so users can group rows by scanner from the table header.
- Vulns page: Source column header is now clickable with the same
  hover-style and SortArrow as the other sortable columns.
2026-05-14 16:37:21 +02:00
vulncheck b91970f3bb feat(mfa): allow LDAP users to enrol TOTP
LDAP users can now enable/disable app-side TOTP just like local users.
Password confirmation during setup/disable is verified by re-binding to
LDAP as the user — the password itself is never stored.

- orchestrator: MFA gate no longer restricted to local accounts; any
  credential-authenticated user with totp_enabled is challenged
- auth router: _verify_user_password helper routes to password_hash
  (local) or LDAP rebind (ldap); SAML/OIDC remain rejected
- MfaCard: shows enrolment UI for LDAP users with a hint that the LDAP
  password is verified via directory rebind
2026-05-14 16:24:12 +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 26dd991135 fix(ui): dark-mode form readability + Recent Vulns sort
Two feedback items from a tester running Windows 11 in Dark Mode:

1. Form inputs (login fields, search boxes, date pickers, autofill)
   were rendered with the browser's dark color scheme on top of the
   app's light-gray background, making text + placeholders unreadable.
   Set color-scheme: light globally (:root + html/body) so the browser
   chrome stays light regardless of OS preference. Hardened autofill
   styling for Chromium so OS dark-mode can't tint autofilled inputs.
   Forced placeholder color to gray-400 for consistent contrast.

2. Dashboard 'Recent Vulnerabilities' table was sorted by priority
   (backend default) instead of newest-first. Changed the dashboard
   fetch to ?sort_by=detected_at&sort_order=desc so the list now
   shows the most-recently detected CVEs at the top, matching the
   intent of the section title.

Neither change touches role-based UI hiding from the previous commit;
the sidebar mapping stays as-is.
2026-05-13 23:36:37 +02:00
vulncheck e90bf1c1fa feat(rbac): hide sidebar entries based on user role
Sidebar now respects the logged-in user's role and hides items that
would 401/403 anyway when clicked. Mapping:

  Dashboard, Vulnerabilities, Assets, Reports, Notifications, Settings
      ── all roles
  Scan Jobs, Policies
      ── editor + admin
  Groups, Audit Logs, Auth Providers
      ── admin only

Implementation:
- /auth/me read on mount; role cached in component state
- nav items get an optional 'requires: admin | editor' marker; items
  hidden when current role rank is below required rank
  (readonly < editor < admin)
- pre-resolve render shows only items without 'requires' to avoid
  the admin-only entries flashing on slow networks before /auth/me
  returns
- Management section header is hidden entirely when no items in it
  pass the role check (cleaner UI for readonly users)

This is a UX hide, not a security boundary — the backend already
enforces RequireAdmin / RequireEditor on the routes. Typing the URL
manually still hits a 403.
2026-05-13 23:32:45 +02:00
vulncheck 7b7fb0ffd8 feat(nessus): Sync Data (Nessus) button on Scan Jobs page
Adds a parallel 'Sync Data (Nessus)' button next to the existing
'Sync Data (Wazuh)' button on /scans. Same UX pattern: confirm dialog,
spinning icon during run, toast with stats (created / merged / hosts /
unmatched) afterwards.

POSTs /api/v1/vulnerabilities/nessus/sync with an empty body, so it
uses nessus_config.default_scan_ids from Settings. If Nessus isn't
configured the backend returns 400 and the toast surfaces the
message.
2026-05-13 23:26:03 +02:00
vulncheck 0a67e5bd2a feat(nessus): Vulns list — source column, filters, false-positive workflow
Backend (app/routers/vulnerabilities.py):
- _build_vuln_response includes sources[], cross_confirmed,
  first_detected_by, nessus_plugin_id, is_pseudo_cve
- New query params on GET /api/v1/vulnerabilities:
    ?source=wazuh|nessus|manual    — filter by scanner attribution
    ?cross_confirmed=true          — only multi-scanner findings
  Implementation uses SQL LIKE on the sources JSON text (rows with a
  comma in the JSON list have >=2 sources, sufficient for our scale).
- New endpoints:
    PATCH /api/v1/vulnerabilities/{id}/false-positive
    PATCH /api/v1/vulnerabilities/{id}/unmark-false-positive
  Both audit-logged. False-positive marks set
  notification_suppressed=true automatically (no SLA-breach spam).

Frontend:
- frontend/types/index.ts: sources, cross_confirmed, first_detected_by,
  nessus_plugin_id, is_pseudo_cve fields
- frontend/app/vulnerabilities/page.tsx:
    * Filter bar gains a Source dropdown (all/wazuh/nessus/manual) and
      a Cross-confirmed checkbox
    * New 'Source' column between Status and Assigned-To with per-scanner
      badges (Wazuh green, Nessus purple, manual gray), ✓×2 emerald
      badge when cross-confirmed, NON-CVE amber badge for pseudo-CVE
      (Nessus EOL / Compliance / Cipher findings)
    * NoSymbolIcon button in Actions: click → prompt for reason →
      PATCH /false-positive. Re-click on a false-positive vuln reverts
      back to open.

This makes the Wazuh ∪ Nessus merged view actionable: admins see which
findings have multi-scanner confirmation and can mark Nessus-only false
alarms without affecting Wazuh-confirmed entries.
2026-05-13 23:14:28 +02:00
vulncheck 472adbc853 feat(nessus): Settings UI — Nessus config modal + Sync-now button
Adds a Tenable Nessus integration card to Settings → Integrations Status
mirroring the existing Wazuh entry:

- Card shows Active/Config-Required badge based on whether base_url +
  access_key are set
- 'Configure' opens a modal with: base URL, access + secret keys (secret
  field is password type), default scan IDs (comma-separated), Verify
  SSL toggle, Auto-create-assets toggle
- 'Test connection' button hits POST /api/v1/vulnerabilities/nessus/test
  and reports server version + plugin set + visible-scan count
- 'Sync now' button on the card (visible only once configured) hits
  POST /api/v1/vulnerabilities/nessus/sync and surfaces the stats
- Status row reflects last sync result (created / merged / hosts /
  unmatched) inline on the card

API client uses the existing /api/v1/[...path] catch-all proxy — no new
route files needed.

Saving normalises default_scan_ids: comma string → number[] before PUT.
Existing config is parsed back into the same form on next page load.
2026-05-13 23:09:59 +02:00
vulncheck e12f11134e feat(notifications): digest mode for new-vulnerability emails
Replaces the per-CVE inline email send during Wazuh sync with a batched
digest dispatcher that runs once at the end of the sync.

- Old behavior (now opt-in via notification_mode=single): one email per
  CVE per recipient. A 100-CVE sync with 3 assignees = 300 mails, often
  triggering SMTP rate limits (Gmail/Outlook/Proton ~20/min).
- New default (notification_mode=digest): one email per recipient with
  a styled HTML table listing every CVE relevant to them (severity
  counts at the top, sorted rows below, button to dashboard). 100 CVEs
  × 3 assignees collapse to 3 mails.

Implementation:
- email_service.dispatch_new_vuln_notifications(db, new_vulns):
  resolves recipients per vuln (vuln.assigned > group > asset.assigned
  > asset.groups), applies notification_min_severity, groups by email,
  and sends. Logs one NotificationLog per recipient.
- email_service.send_new_vulnerability_digest() renders new
  DEFAULT_DIGEST_TEMPLATE (responsive HTML, severity badges, table).
- email_service.get_notification_mode() reads notification_mode setting.
- Sync paths (run_wazuh_vulnerability_sync, sync_agent_vulnerabilities)
  now accumulate newly_created_vuln_ids and call the dispatcher once
  after the loop instead of mailing inline.
- Settings UI gains a 'Delivery Mode' dropdown next to the severity
  threshold.
2026-05-12 21:13:55 +02:00
vulncheck b95c540db6 feat(mfa): admin reset endpoint + Reset MFA button in user management
Adds an admin-only escape hatch for the 'user lost their device'
scenario:

- POST /auth/users/{user_id}/reset-mfa  (RequireAdmin)
  Clears totp_enabled + totp_secret. User can then log in with
  password only, will be prompted to re-enrol via /settings.
  Audit-logged as MFA_DISABLED with admin actor.

- Settings → User Management gets a 'Reset MFA' button next to
  Reset PW / Edit / Delete. Confirmation prompt explains the
  security implication.

- frontend/app/auth/users/[userId]/reset-mfa/route.ts proxies the
  request to backend (matches existing reset-password proxy pattern).

Note: the user-facing 'Disable MFA' flow in /settings still requires
password confirmation; this new endpoint is for admins resetting
someone else.
2026-05-12 20:32:40 +02:00
vulncheck 535f580e43 fix(proxy): add catch-all /auth/[...path] proxy for MFA + SSO endpoints
POST /auth/mfa/setup returned 404 with x-nextjs-cache: HIT because the
Next.js app proxied each /auth/* endpoint via an individual route.ts
file (login, logout, me, register, users) and had no entry for the new
sub-paths added in the multi-provider auth work.

Solution: a catch-all [...path] route under /auth that forwards anything
not handled by an explicit route to the backend via proxyRequest().
Covers /auth/mfa/{setup,activate,disable,verify}, /auth/providers,
/auth/oidc/{login,callback}, /auth/saml/{login,acs,metadata}.

Specific routes (login, me, users, ...) keep priority — Next.js matches
exact segments before catch-alls.
2026-05-12 20:24:24 +02:00
vulncheck ba66964761 feat(auth-ui): TOTP enrolment card in Settings page
Adds a self-service MFA setup UI for local-auth users:

- frontend/components/auth/MfaCard.tsx — three-stage card:
    1. Idle → 'Enable MFA' / 'Disable MFA' (state-aware)
    2. Setup → confirm password → POST /auth/mfa/setup, receive secret+URI
    3. Activate → QR (qrcode.react SVG, all client-side, no external service)
       + readable secret fallback + 6-digit code field → POST /auth/mfa/activate
  Disable flow: password confirm → POST /auth/mfa/disable. Card hides
  itself for non-local users (their IdP handles MFA).

- frontend/app/settings/page.tsx renders MfaCard above the existing
  'Your Profile' panel.

- /auth/me now returns auth_provider and mfa_enabled so the card can
  decide which state to show without an extra fetch.

- qrcode.react dependency added (~20KB, MIT). QR renders locally as SVG;
  the otpauth secret never leaves the browser.
2026-05-12 19:32:31 +02:00
vulncheck 21b17bcb0d feat(auth-admin): admin UI for provider status, role mappings, connection tests
Phase 6: gives admins runtime control over the parts of the multi-provider
auth stack that should be editable at runtime, while keeping credentials/
endpoint config in environment variables (where they belong).

Backend (app/routers/auth_admin.py):
- GET  /api/v1/auth-config/status        provider enablement + readiness + user counts
- GET  /api/v1/auth-config/role-mappings current mapping rules
- PUT  /api/v1/auth-config/role-mappings replace mapping rules (validated against
                                          AuthProvider + UserRole enums)
- POST /api/v1/auth-config/ldap/test     opens LDAPS conn, binds service account,
                                          optionally searches a sample username
- POST /api/v1/auth-config/oidc/test     fetches discovery doc + JWKS, reports
                                          issuer / endpoints / key count
- POST /api/v1/auth-config/saml/test     parses IdP metadata, returns SSO URL
                                          + entityID + cert presence
All RequireAdmin. PUT logs CONFIG_CHANGE audit event.

Frontend (frontend/app/admin/auth/page.tsx):
- Global config panel (lookup order, JIT, default role, crypto-key set?)
- Provider status cards with enabled/configured badges + per-provider
  Test button (with output dump)
- Inline role-mapping editor per provider: add/edit/reorder/remove rules,
  validates client-side (role dropdown), saves via PUT

Sidebar: new 'Auth Providers' entry under /admin/auth (KeyIcon).
2026-05-12 19:15:17 +02:00
vulncheck b8e8870b29 feat(auth): LDAPS + OIDC + SAML 2.0 strategies and SSO routers
Phase 2-4 of multi-provider authentication. All three providers slot
into the AuthOrchestrator from phase 1; no further changes to
/auth/login are needed.

LDAPS (app/auth/strategies/ldap_strategy.py):
- ldap3 with strict TLS cert validation (CERT_REQUIRED + CA bundle)
- Service-account search-then-bind flow (DN never exposed to caller)
- Filter chars escaped via ldap3.utils.conv.escape_filter_chars
- AD-flavored defaults: sAMAccountName / memberOf / objectGUID
- Refuses if filter returns >1 entry (anti-impersonation safety)
- Bind password encrypted at rest (Fernet), bootstrapped from env on
  first start then stored in settings table

OIDC (app/auth/strategies/oidc_strategy.py + app/routers/auth_oidc.py):
- Authorization Code + PKCE (S256)
- Strict ID-Token validation via Authlib: signature (JWKS w/ auto-refresh
  on rotation), iss (essential), aud (essential, must == client_id),
  exp (essential), nonce (replay protection)
- State/PKCE-verifier/nonce stored in signed itsdangerous cookie (no
  server-side session store needed)
- Discovery + JWKS cached in-process; JWKS auto-refetched on key miss
- Groups merged from both id_token claims and userinfo endpoint
- Hardened: prompt=select_account to defeat silent IdP reuse

SAML 2.0 (app/auth/strategies/saml_strategy.py + app/routers/auth_saml.py):
- python3-saml (OneLogin) with strict=true; xmlsec1 handles signature
  validation. XSW attacks mitigated via strict assertion/response
  signature position checks plus wantAssertionsSigned=true
- SP-initiated (/auth/saml/login) + IdP-initiated (POST /auth/saml/acs)
- /auth/saml/metadata serves signed SP descriptor
- RelayState same-origin check to prevent open redirect
- IdP metadata loaded from URL or file at startup

Wiring:
- app/main.py imports auth_oidc/auth_saml routers behind try/except so
  the app still starts when authlib or python3-saml aren't installed
- Frontend login page fetches /auth/providers and renders matching
  redirect buttons (Sign in with Entra ID / SAML SSO) plus the local
  credentials form. Adds MFA second-step screen with 6-digit OTP input
  when /auth/login returns mfa_required=true.

.env.example: full provider config blocks with worked examples for
Entra ID, Okta, Keycloak, Google. Each block is commented with the
exact format the corresponding admin needs.
2026-05-12 19:11:32 +02:00
vulncheck c3c5134a4e ux(vulns): drop 'Scroll table' label, slimmer scroll-control bar
Label was redundant — chevron icons are self-explanatory. Bar now
right-aligned and slightly tighter (py-1.5 instead of py-2).
2026-05-12 16:35:01 +02:00
vulncheck 7197a59cfc i18n(vulns): translate all enrichment UI text to English
Column help popovers, scroll controls, enrichment confirm/alert dialogs,
and aria-labels were in German — translated to English for consistency
with the rest of the app.
2026-05-12 16:34:07 +02:00
vulncheck 5067980cb0 ux(vulns): use full available width — remove max-w-7xl cap
Page was capped at 1280px (max-w-7xl), wasting horizontal space on
wide monitors. Now uses w-full so table expands with viewport. The
AppShell already provides responsive horizontal padding (px-4 sm:px-6
lg:px-8), so we just let the table flow into all of it.

Sticky header bar also extended with matching responsive -mx/-px so
its background fills the visible width.
2026-05-12 16:27:51 +02:00
vulncheck 3b464df260 ux(vulns): compact header + sticky filter bar
Reduces wasted vertical space before the table:
- Heading h2 text-3xl -> text-xl, mb-6 -> mb-2
- Counter inline with heading ("N total · M shown") instead of separate row
- Filter bar padding p-4 -> p-3
- Filter bar wrapped in sticky top-0 div with backdrop-blur so it stays
  accessible when scrolling down through results. No need to scroll back
  up to change a filter.

Table row data stays scrollable as before — sticky thead inside an
overflow-x-auto container is a CSS conflict (axis bleed) so we skip
that for now.
2026-05-12 16:26:47 +02:00
vulncheck a787048533 fix(vulns): help icons open click popover (title-tooltip didn't work on click)
Replaces title-attribute tooltip with a proper React-controlled popover.
Click on the (i) icon now opens a 320px popover with formatted text,
close button, click-outside-to-close behaviour. Hover tooltip kept as
fallback via title attribute for quick peek.
2026-05-12 16:09:20 +02:00
vulncheck b299b1fdfe feat(vulns): horizontal scroll controls + visible scrollbar
Adds ← → arrow buttons above the table that appear only when horizontal
scroll is needed and disable themselves at the scroll boundary. Solves
the case where a mouse doesn't have a horizontal scroll wheel and the
Actions column was unreachable.

Also forces a visible horizontal scrollbar via .scroll-visible-x CSS
(macOS hides overlay scrollbars by default), so users can grab and
drag the scroll thumb directly.
2026-05-12 16:08:23 +02:00
vulncheck 09c6904cab feat(vulns): help tooltips on column headers (CVSS, EPSS, KEV, EUVD, CPR, Priority, Severity)
Adds InformationCircleIcon next to each metric column with a hover-tooltip
explaining what the score means, value range, and source. Saves users
having to research EPSS / CISA KEV / ENISA EUVD / CPR externally.

Click on the icon stops propagation so sort toggle doesn't fire when
user just wants to read the explanation.
2026-05-12 16:01:05 +02:00
vulncheck c7b881cc2f fix(vulns): sticky Actions column overlap — z-10, border, solid bg
Sticky Actions cell had no z-index and used inline conditional bg,
which let the Assigned-To dropdown render behind it visually. Now uses
z-10, left border, stronger shadow, and Tailwind group-hover for proper
hover state matching the row.

Also reduced Assigned select maxWidth 140 -> 120 so its dropdown
doesn't extend into the sticky-right reserved space.
2026-05-12 15:45:22 +02:00
vulncheck 34324996f8 feat(vulns): click-to-sort table headers + sticky Actions column
- Headers (CVE, Severity, CVSS, EPSS, KEV, EUVD, CPR, Priority, Status)
  clickable, toggle asc/desc, arrow indicator. Defaults to priority desc.
- Backend sort_by extended: epss, cpr (CVSS*EPSS proxy in SQL),
  kev, euvd, severity, status, cve_id. NULL values sort last on desc.
- CPR computed in Python (CVSS*EPSS*10), SQL uses coalesce for NULL safety.
- Priority re-sorted Python-side after SQL proxy fetch since real score
  needs policy+age context.
- Actions column sticky right with shadow — stays visible when table
  scrolls horizontally on narrow viewports.
2026-05-12 15:16:34 +02:00
vulncheck 356bf7b97f feat(risk-score): add ENISA EUVD enrichment + CPR score
EUVD (EU Vulnerability Database, ENISA) integration as second
authoritative catalog alongside CISA KEV. EU-Compliance use cases
benefit from a non-US source; CVEs confirmed by both catalogs get
the highest priority via score stacking.

Two ENISA endpoints are merged into one cached map (24h TTL):
- /exploitedvulnerabilities (analogous to CISA KEV)
- /criticalvulnerabilities (ENISA Critical flag)

Priority-Score formula:
- Exploit-Signal now triggered by KEV OR EUVD listing
- Catalog-Bonus stacking: KEV +10, EUVD +10, KEV-ransomware +5,
  EU-Critical +3. A CVE in both catalogs adds +20 base.

CPR Score (Cybersecurity Priority Risk = CVSS x EPSS x 10) added
as separate metric next to Priority, per JacquesKruger/EPSS-Server
convention. Calculated on-the-fly, no DB column needed.

New API filters: euvd_only, eu_critical, in_any_catalog,
in_both_catalogs. Setting toggle enrichment_euvd_enabled (default true).

Frontend: new EUVD column (blue badge, EU-CRIT sub-badge), CPR column
with mini-bar, four catalog filter checkboxes. Detail page splits
threat intel into CISA KEV / ENISA EUVD / EPSS sections; breakdown
shows EUVD bonus row and CPR score with both-catalogs hint.
2026-05-11 19:47:37 +02:00
vulncheck 65f77c9d80 fix(vulns): EPSS filter input now uses percent (0-100)
Filter input previously expected the raw 0.0-1.0 EPSS value while
the table column displayed percent — confusing UX. Input now matches
the column format: typing "60" filters for EPSS >= 60%.
2026-05-11 16:04:17 +02:00
vulncheck 14356c9a07 feat(notifications): configurable severity threshold for new-vuln emails
Add a settings-page dropdown (Critical / High+ / Medium+ / Low+) that
controls which severities trigger a 'new vulnerability' email during
Wazuh sync. Stored in the existing settings KV table under
'notification_min_severity', default 'critical' (no behaviour change
on upgrade).

Backend:
- email_service.should_notify_for_severity(db, severity) reads the
  threshold from settings and compares using a severity rank.
- Both sync paths in vulnerabilities.py now delegate to the helper
  instead of the hard-coded 'critical only' check (the comment
  already lied about 'CRITICAL or HIGH').

Frontend:
- New control in the Notification Settings section, persists via the
  generic /api/v1/settings/{key} PUT endpoint.
2026-05-11 15:41:40 +02:00
vulncheckandClaude Opus 4.7 42f867a58b feat(risk-score): enrich priority with EPSS + CISA KEV
Risk score now pulls from multiple threat intel sources instead of
only AI/CVSS data:

- EPSS (FIRST.org) — probability of exploitation in next 30 days
- CISA KEV — known actively exploited vulnerabilities (with ransomware flag)
- Existing Wazuh exploit flags as fallback

Adds DB columns (epss_score, epss_percentile, kev_listed, kev_*,
enrichment_sources, enrichment_updated_at), an enrichment_service
with cached KEV catalog (24h TTL in settings table) and batched
EPSS lookups, manual + bulk + KEV-refresh endpoints, automatic
enrichment after Wazuh sync, and a daily scheduler job to refresh
scores.

Frontend gets KEV badges, EPSS column with percentile, KEV-only +
EPSS-min filters, a "Refresh Threat Intel" button, and a priority
score breakdown card on the detail page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 15:37:53 +02:00
vulncheck 571c5de3cc fix(auth): force full reload after login to avoid cookie race
router.push('/') performs client-side navigation, which causes AppShell
to mount and fire /auth/me before the browser has committed the
Set-Cookie from the login response. The result was a 401 on the first
/auth/me, bouncing the user back to /login as if the credentials were
wrong; only after a couple of retries (and an eventual hard reload)
would the cookie be picked up and the dashboard load.

Switch to window.location.href = '/' so the browser performs a full
navigation. The cookie jar is guaranteed to be committed before the new
page loads, so the very first /auth/me succeeds.
2026-05-11 14:28:58 +02:00
vulncheck 662eac673e fix: prevent /auth/me 401 redirect loop
Axios interceptor redirected to /login on any 401, including the
/auth/me probe itself, causing infinite reload loop when the session
cookie was missing or expired. Skip redirect for auth-check endpoints
and when already on /login.
2026-05-11 13:39:18 +02:00
vulncheck 9ada95424a Improve perf and security hardening
Perf:
- AppShell: auth check once on mount instead of every pathname change
- Dashboard: replace bare <a> with Next Link for prefetch

Security:
- Migrate python-jose to PyJWT (CVE-2024-33663, CVE-2024-33664)
- JWT exp/iat now UTC-aware via datetime.now(timezone.utc)
- Drop default 'changeme' fallback for DEFAULT_ADMIN_PASSWORD
- Force POSTGRES_PASSWORD env in docker-compose
2026-04-28 16:16:29 +02:00
vulncheck faf8b89168 Fix scan sync: severity fallback, solved-filter, timestamps
- Add Wazuh severity string as fallback when CVSS score is missing,
  preventing critical vulns from being classified as "none"
- Fix solved-filter to check only the LATEST alert status per CVE
  instead of filtering all CVEs that were ever solved (fixes re-emerged
  vulns being incorrectly hidden)
- Try both score.base and score.base_score for indexer compatibility
- Add detailed sync logging (severity breakdown, new/updated counts)
- Show time alongside date for detected_at in frontend and PDF reports
2026-02-15 15:54:07 +01:00
vulncheck 862ef59f4a Allow clearing default group setting 2026-02-08 11:18:15 +01: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