# VulnCheck — Architecture Overview

> Stand: Mai 2026 (dev-Branch, post-Migration 022). Diese Doku spiegelt
> den `dev`-Branch wider. Stable `main` ist eine Teilmenge — siehe
> `README.md` vs. `README.DEV.md`.

---

## 1. System Architecture

```
                       ┌──────────────────────────────────────┐
                       │            Browser (UI)              │
                       │   Next.js 14 App Router (SSR + CSR)  │
                       └──────────────────┬───────────────────┘
                                          │ HTTPS/JSON + JWT (Cookie+Bearer)
                                          ▼
┌──────────────────────────────────────────────────────────────────────────┐
│                       Backend — FastAPI (Python 3.12)                    │
│                                                                          │
│   ┌──────────────┐  ┌─────────────────┐  ┌─────────────────────────┐    │
│   │  Routers     │  │  Services       │  │  Integrations (clients) │    │
│   │  (HTTP API)  │──▶ (Business Logic)│──▶ Wazuh / Nessus / NVD /  │    │
│   └──────────────┘  └─────────────────┘  │ EPSS / KEV / EUVD /      │    │
│                                          │ Vulnrichment / cvelistV5  │    │
│                                          └─────────────────────────┘    │
│                                                                          │
│   ┌──────────────────────────────────────────────────────────────────┐   │
│   │  APScheduler — Background Jobs                                   │   │
│   │  - sync_schedules (every 60s, picks up DB changes)               │   │
│   │  - SLA breach checker (hourly)                                   │   │
│   │  - Daily threat-intel refresh (EPSS + KEV)                       │   │
│   │  - Nightly compliance SCA refresh (02:00 UTC)                    │   │
│   │  - Nightly Vulnrichment cascade (03:00 UTC)                      │   │
│   │  - Nightly URS recompute + snapshot prune (04:00 UTC)            │   │
│   │  - User-defined scan schedules (Wazuh or Nessus, cron/interval)  │   │
│   └──────────────────────────────────────────────────────────────────┘   │
└────────────────────┬──────────────────────────────┬──────────────────────┘
                     │                              │
              SQLAlchemy 2.x                  httpx clients
                     ▼                              ▼
        ┌────────────────────────┐       ┌────────────────────────┐
        │  PostgreSQL 16         │       │  External Sources      │
        │  - alembic-managed     │       │  - Wazuh Indexer/API   │
        │  - 22 migrations live  │       │  - Nessus REST API     │
        │  - JSON columns:       │       │  - NVD REST API        │
        │    sources, refs,      │       │  - FIRST.org EPSS      │
        │    enrichment_sources  │       │  - CISA KEV feed       │
        │  - Settings KV store   │       │  - ENISA EUVD          │
        └────────────────────────┘       │  - cisagov/vulnrichment│
                                         │  - cveproject/cvelistV5│
                                         │  - SMTP server (mails) │
                                         │  - LDAPS / OIDC / SAML │
                                         └────────────────────────┘
```

### Stack

| Layer | Tech |
|---|---|
| Frontend | Next.js 14 App Router, React 18, TypeScript, Tailwind CSS, Heroicons |
| Backend | FastAPI 0.115, Pydantic v2, Uvicorn |
| ORM | SQLAlchemy 2.x + Alembic |
| Database | PostgreSQL 16 |
| Scheduler | APScheduler 3.x (interval + cron triggers) |
| Auth | Local (bcrypt + TOTP), LDAPS (`ldap3`), OIDC (`authlib` + JWKS), SAML 2.0 (`python3-saml`) |
| AI | Infomaniak AI proxy (configurable via env) |
| HTTP client | `httpx` (sync + async) |
| Container | Docker Compose (backend, frontend, postgres) |

---

## 2. Security Architecture (OWASP Top 10 Mitigation)

| OWASP risk | Defence in VulnCheck |
|---|---|
| **A01 Broken Access Control** | RBAC enforced via `require_role()` dependency on every router; admin / editor / viewer scopes. Asset-ownership filter on vuln listings. |
| **A02 Cryptographic Failures** | TOTP secrets encrypted at rest with `AUTH_PROVIDER_CRYPTO_KEY` (Fernet). LDAP bind password same. bcrypt cost-12 for local passwords. JWT secret rotated per deployment. |
| **A03 Injection** | SQLAlchemy parameterised queries everywhere. CVE-IDs and search inputs sanitised. Wazuh-Indexer queries built via OpenSearch DSL, not string interpolation. |
| **A04 Insecure Design** | Strategy-Pattern auth (`LocalAuthStrategy`, `LdapStrategy`, `OidcStrategy`, `SamlStrategy`) — single orchestrator, single JWT path. JIT provisioning + role mapping configurable, no hard-coded admins. |
| **A05 Security Misconfiguration** | `verify_ssl=True` default for upstream integrations. CORS restricted to configured frontend host. CSP header on Next.js. |
| **A06 Vulnerable Components** | `pip-audit` + `npm audit` in CI. Dependabot on `dev`. |
| **A07 Auth Failures** | Failed-login counter on `users` table, lockout after threshold. Constant-time bcrypt verify for unknown usernames (pre-computed dummy hash). MFA gate before JWT issue. SAML strict signature + XSW check. |
| **A08 Data Integrity** | `created_at`/`updated_at` mixin on every audit-relevant row. AuditLog row per security event. Alembic migrations idempotent. |
| **A09 Logging & Monitoring** | Structured logger across backend, NotificationLog table, AuditLog table, `/api/v1/audit/logs` admin view. |
| **A10 SSRF** | All outbound integrations whitelist-URL; no user-controllable URLs. |

---

## 3. Component Details

### 3.1 Routers (`app/routers/`)

Each router groups a logical resource. All require JWT except `/auth/*` login endpoints.

| Router | Resource | Key endpoints |
|---|---|---|
| `auth.py` | Local login + MFA | `/auth/login`, `/auth/verify`, `/auth/mfa/{setup,activate,disable}`, `/auth/providers` |
| `auth_admin.py` | Provider config + role mapping | `/auth/admin/role-mappings`, `/auth/admin/{ldap,oidc,saml}/test` |
| `auth_oidc.py` | OIDC SSO | `/auth/oidc/login`, `/auth/oidc/callback` |
| `auth_saml.py` | SAML 2.0 SSO | `/auth/saml/login`, `/auth/saml/acs`, `/auth/saml/metadata` |
| `vulnerabilities.py` | Vulns, enrichment, overrides, sync | `/vulnerabilities`, `/vulnerabilities/sync/wazuh`, `/vulnerabilities/enrich/*`, `/vulnerabilities/override/*` |
| `nessus.py` | Tenable Nessus | `/vulnerabilities/nessus/{test,scans,sync,scan-host,cleanup-pseudo-cves}` |
| `assets.py` | Asset CRUD + assignment | `/assets`, `/assets/{id}`, `/assets/bulk-update`, `/assets/{id}/assign` |
| `scans.py` | Scan history + schedules | `/scans`, `/scans/summary`, `/scans/clear`, `/scans/autoscan`, `/scans/schedules` |
| `compliance.py` | SCA + URS | `/compliance/summary`, `/compliance/assets`, `/compliance/{asset_id}`, `/compliance/refresh`, `/compliance/impacts/{import,stats}`, `/compliance/urs[/...]` |
| `policies.py` | Security policies (SLA) | `/policies`, `/policies/{id}`, `/policies/bulk-update` |
| `groups.py` | User groups | `/groups`, `/groups/{id}`, `/groups/default` |
| `settings.py` | Key-value config | `/settings`, `/settings/{key}` |
| `notifications.py` | Mail log + test | `/notifications/log`, `/notifications/test`, `/notifications/notify-critical` |
| `reports.py` | CSV / PDF exports | `/reports/csv`, `/reports/pdf` |
| `audit.py` | Security audit trail | `/audit/logs` |

### 3.2 Services (`app/services/`)

Pure business logic, no FastAPI imports. Reusable from scheduler + routers.

| Service | Role |
|---|---|
| `enrichment_service.py` | EPSS / KEV / EUVD enrichment per CVE, with 24h cache in `settings` |
| `vuln_override_service.py` | 3-stage CVSS / SSVC / fixed_version cascade (Vulnrichment → NVD → cvelistV5) |
| `override_jobs.py` | Async job tracker for the long-running "Correct CVSS" button |
| `nessus_sync.py` | Per-scan import, asset matching cascade, pseudo-CVE handling |
| `compliance_service.py` | Wazuh SCA pull + weighted-score computation per policy |
| `compliance_impact_import.py` | CIS-Benchmark CSV importer with loose-header matching |
| `urs_service.py` | AVS + ASS → URS formula, snapshot writes, daily prune |
| `email_service.py` | SMTP send, per-recipient templates, digest + single modes |

### 3.3 Integrations (`app/integrations/`)

Thin HTTP clients, no DB writes.

| Client | Purpose |
|---|---|
| `wazuh_client.py` | OpenSearch indexer DSL — paginated vuln + SCA fetch |
| `nessus_client.py` | REST API + plugin output parsing (`fixed_version`, VPR, host UUID) |
| `ai_client.py` | LLM dispatcher (currently routes to Infomaniak) |
| `infomaniak_ai_client.py` | Infomaniak chat-completion proxy |

### 3.4 Auth Stack — Strategy Pattern

```
   POST /auth/login (username + password)
              │
              ▼
   ┌────────────────────────────┐
   │ AuthOrchestrator           │  chain = AUTH_LOOKUP_ORDER
   │ (env: local,ldap)          │
   └──┬─────────────────────────┘
      │  on success
      ▼
   ┌────────────────────────────┐
   │ MFA gate (if totp_enabled) │
   └──┬─────────────────────────┘
      │  OK
      ▼
   JWT (httpOnly cookie + bearer in response body)

   GET  /auth/oidc/login    ── PKCE redirect ── IdP
   POST /auth/oidc/callback ── JWKS-validated id_token → orchestrator.complete_sso_login()

   GET  /auth/saml/login    ── python3-saml AuthnRequest
   POST /auth/saml/acs      ── strict signature + XSW check → orchestrator.complete_sso_login()
```

JIT provisioning (`auth.jit_provisioner`) auto-creates `User` rows on first
SSO/LDAP login. `RoleMapper` re-evaluates role on every login from
`external_groups`, so AD-side membership changes apply immediately.

### 3.5 Scheduler (`app/scheduler.py`)

| Job ID | Cadence | What it does |
|---|---|---|
| `scheduler_sync` | every 60 s | Picks up DB changes to `scan_schedules` and re-registers jobs |
| `sla_breach_check` | every 1 h | SLA-overdue scan; honors `sla_breach_enabled` toggle + `PolicyStatus.DISABLED` skip; digest or single mode |
| `threat_intel_refresh` | every 24 h | Refreshes EPSS, KEV, EUVD across all open vulns |
| `compliance_sca_nightly` | 02:00 UTC | Wazuh SCA pull for every linked asset |
| `vulnrichment_nightly` | 03:00 UTC | 3-stage CVSS/SSVC/fixed_version cascade |
| `urs_nightly` | 04:00 UTC | URS recompute + asset_risk_snapshots prune (>90d) |
| user-defined | per schedule | Per-`scan_schedules` row, routes to `run_wazuh_vulnerability_sync` or `run_nessus_sync` based on `scanner_type` |

---

## 4. Data Model

See [DATABASE_SCHEMA.md](DATABASE_SCHEMA.md) for column-level detail. High-level
entities and their main relationships:

```
   User ─┐                            Policy ── 1:N ──┐
         │ M:N (user_groups)                          │
   Group ┴── 1:N ──┐                                  │
                   │                                  │
                   ▼ M:N (asset_groups)               ▼
                Asset ──── 1:N ────► Vulnerability ──── 1:N ──► AIAnalysis
                  │                       │
                  │ 1:N                   │ 1:N
                  ▼                       ▼
                Scan                  NotificationLog
                  │
                  └── 1:N ──► ComplianceResult ── 1:N ──► ComplianceCheck

   ComplianceImpact  (cis_id, benchmark)   ◄── lookup for weighted_score
   AssetRiskSnapshot (asset_id, snapshot_date, avs, ass, urs, severity)

   AuditLog          (user_id, event_type, …)
   Setting           (key, value)         ◄── KV store: smtp, nessus_config,
                                              enrichment flags, sla toggle,
                                              cache for EPSS/KEV/EUVD JSON,
                                              role-mapping JSON, etc.
```

Vulnerabilities carry a `sources` JSON list (`["wazuh","nessus"]`) so one row
holds the union per `(cve_id, asset_id)`. Non-CVE Nessus plugins land under
pseudo-IDs `NESSUS-PLUGIN-{plugin_id}`.

---

## 5. API Endpoints (summary)

Full reference: see Router section above + Swagger UI at `/docs`. JWT auth required
on everything except `/auth/login`, `/auth/oidc/*`, `/auth/saml/*`, `/auth/setup-admin`.

| Group | Examples |
|---|---|
| **Auth** | `POST /auth/login`, `POST /auth/verify` (MFA), `POST /auth/mfa/setup`, `POST /auth/oidc/login` |
| **Vulnerabilities** | `GET /vulnerabilities?source=wazuh|nessus|manual&kev_only=true&epss_min=…&sort_by=cve_id`, `POST /vulnerabilities/sync/wazuh`, `POST /vulnerabilities/override/vulnrichment/start`, `PATCH /vulnerabilities/{id}/false-positive` |
| **Assets** | `GET /assets`, `POST /assets/{id}/assign`, `POST /assets/{id}/rescan` |
| **Scans** | `GET /scans/summary`, `POST /scans/autoscan`, `POST /scans/schedules` |
| **Compliance / URS** | `POST /compliance/refresh`, `POST /compliance/impacts/import` (multipart), `GET /compliance/urs?avs_mode=hybrid` |
| **Nessus** | `POST /vulnerabilities/nessus/test`, `POST /vulnerabilities/nessus/sync` |
| **Settings** | `PUT /settings/{key}` (admin) |

Query parameters of note on `GET /api/v1/vulnerabilities`:

`status`, `severity`, `source`, `cross_confirmed`, `kev_only`, `euvd_only`,
`eu_critical`, `in_any_catalog`, `in_both_catalogs`, `epss_min`,
`assigned_to_me`, `host`, `package`, `cve_id`, `sort_by`
(`cve_id|priority_score|cvss_score|cpr_score|detected_at|published_date|updated_at`),
`sort_order`, `limit`, `offset`.

---

## 6. Deployment Architecture

```
   docker-compose.yml
   ├── postgres     (volume: pgdata)
   ├── backend      (FastAPI + APScheduler)  ── alembic upgrade head on start
   └── frontend     (Next.js standalone build, served via node)
```

Environment configuration via `.env` (gitignored). Required keys:

```
DATABASE_URL=postgresql://vulnmanager:…@postgres/vulnmanager
JWT_SECRET=…                # rotate per deployment
AUTH_PROVIDER_CRYPTO_KEY=…  # Fernet, encrypts TOTP + LDAP bind pw

# Wazuh
WAZUH_API_URL=https://…:55000
WAZUH_INDEXER_URL=https://…:9200
WAZUH_API_USER=…
WAZUH_API_PASS=…

# Optional
DASHBOARD_URL=http://localhost:3000          # used in mail links
INFOMANIAK_API_KEY=…                          # AI analysis
LDAP_BIND_PASSWORD_BOOTSTRAP=…                # first-start LDAP setup
HTTPS_PROXY=…                                 # for outbound enrichment
```

Optional outbound HTTPS endpoints (firewall whitelist if locked-down):

- `https://api.first.org` (EPSS)
- `https://www.cisa.gov` (KEV)
- `https://euvdservices.enisa.europa.eu` (EUVD)
- `https://raw.githubusercontent.com` (Vulnrichment per-CVE + ZIPs)
- `https://github.com` (cvelistV5 ZIP)
- `https://services.nvd.nist.gov` (NVD REST)
- `https://nessus.your-internal/...` (Nessus)

### Upgrade flow

```bash
git pull origin dev
docker compose build backend frontend
docker compose up -d backend frontend
docker compose exec backend alembic upgrade head
docker compose exec backend alembic current   # expect: 022 (head)
```

---

## 7. Performance & Scaling

- Wazuh-Indexer pagination beyond the OpenSearch 10 000-hit cap: search-after / scroll in `WazuhClient.get_vulnerabilities`.
- Vulnrichment cascade auto-routes to ZIP snapshot when > 25 CVEs requested — avoids 25 × N raw fetches.
- cvelistV5 ZIP (~557 MB) is on-disk-cached 12 h at `/tmp/vulncheck-cvelistv5-cache.zip`.
- NVD stage caps at 100 CVEs per run (rate limit). >100 missing → falls through to cvelistV5 directly.
- `sources` / `enrichment_sources` JSON columns avoid table-join chatter for per-vuln source filtering.
- Indexed columns: `cve_id`, `asset_id`, `cvss_score`, `severity`, `epss_score`, `kev_listed`, `euvd_listed`, `exploitation_status`, `ssvc_technical_impact`, `ssvc_automatable`, `nessus_vpr_score`, `nessus_plugin_id`, `package_name`, `detected_at`.
- "Correct CVSS" runs as an async job (`override_jobs.py`) so the UI returns immediately with a job_id and a polling progress card — full-DB corrections take 5-20 min.

---

## 8. Monitoring & Observability

- **Structured logger** to stdout; `docker compose logs backend` is the primary view.
- **AuditLog** captures security-relevant events (login success / failure, role change, MFA reset, password change, deletes).
- **NotificationLog** records every mail attempt (status, error, recipient).
- **`/scans/summary`** surfaces sync history with per-run vuln deltas.
- **`/notifications/log`** lists outbound mail history with retry status.
- **APScheduler logger** prints next_run + last_run per job.

Recommended Prometheus exporters: `postgres_exporter`, `node_exporter`,
and a thin `/healthz` endpoint on the FastAPI side (returns DB ping + scheduler
status). Not yet shipped — placeholder for future ops work.

---

## 9. Development Roadmap

See `README.DEV.md` "Out of scope" sections for the live backlog. Big-ticket
items currently parked:

- WebAuthn / FIDO2 second factor
- Per-framework URS breakdown (URS_CIS / URS_ISO27001 / URS_NIS2)
- SCIM v2 endpoint for IdP-driven provisioning
- endoflife.date integration for EOL/EOS package detection
- Real-time KEV push from Wazuh (currently poll-based)
- Two-way Nessus false-positive sync
- Concentration penalty on URS (≥ 3 high-impact failures → +10)
- Settings-UI toggle for `sla_breach_enabled` (currently DB-only)
