Files
vulncheck/PROJECT_OVERVIEW.md
T
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

378 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TrueVuln — Project Overview
> Vulnerability Management Dashboard for a Swiss-school IT-Security setup.
> Wazuh agents + Nessus scanner + CISA/ENISA/EPSS threat intel + CIS-Benchmark
> compliance + Unified Risk Score, all behind a single FastAPI + Next.js stack.
>
> Stand: Mai 2026, dev-Branch, alembic head 022.
---
## 🎯 Summary
TrueVuln collects vulnerability findings from multiple scanners (Wazuh,
Nessus), enriches them with several free public threat-intel sources, scores
the result on a unified 0-100 risk scale, and surfaces them in a role-based
web UI with SLA-tracking, mail notifications, compliance evidence (Wazuh SCA
+ CIS-Benchmark impact weights), and an LLM-backed remediation analysis.
Designed for internal small-to-mid IT teams (5-100 hosts). Single-server
deployable via Docker Compose. Postgres-only persistence.
---
## 📁 Project Structure
```
vulnerability-dashboard/
├── app/ # FastAPI backend
│ ├── routers/ # HTTP API by resource
│ │ ├── auth.py, auth_admin.py
│ │ ├── auth_oidc.py, auth_saml.py
│ │ ├── vulnerabilities.py # vulns, sync, enrichment, overrides
│ │ ├── nessus.py # Tenable Nessus
│ │ ├── assets.py, scans.py, policies.py, groups.py
│ │ ├── compliance.py # SCA + URS endpoints
│ │ ├── notifications.py, reports.py, settings.py, audit.py
│ ├── services/ # business logic, scheduler-reusable
│ │ ├── enrichment_service.py
│ │ ├── vuln_override_service.py # 3-stage CVSS cascade
│ │ ├── override_jobs.py # async job tracker
│ │ ├── nessus_sync.py
│ │ ├── compliance_service.py, compliance_impact_import.py
│ │ ├── urs_service.py
│ │ └── email_service.py
│ ├── integrations/ # thin HTTP clients
│ │ ├── wazuh_client.py
│ │ ├── nessus_client.py
│ │ ├── ai_client.py, infomaniak_ai_client.py
│ ├── auth/ # strategy-pattern auth
│ │ ├── orchestrator.py
│ │ ├── strategies/ # local, ldap, oidc, saml
│ │ ├── jit_provisioner.py, role_mapper.py
│ ├── models/ # SQLAlchemy
│ │ └── vulnerability.py, asset.py, user.py, group.py, ...
│ ├── schemas/ # Pydantic v2 request/response
│ ├── scheduler.py # APScheduler
│ ├── main.py # FastAPI app + middleware
│ └── deps.py # auth dependency, DB session
├── alembic/
│ └── versions/ # 001 .. 022 migrations
├── alembic.ini
├── frontend/
│ ├── app/ # Next.js 14 App Router
│ │ ├── page.tsx # Dashboard (2-widget Recent Critical + Newly Published)
│ │ ├── vulnerabilities/page.tsx # main list
│ │ ├── vulnerabilities/[id]/ # detail
│ │ ├── assets/, compliance/, scans/, notifications/, settings/
│ ├── components/
│ ├── lib/api.ts # axios + auth header
│ ├── types/index.ts
│ └── public/logo.png
├── docs/
│ └── TROUBLESHOOTING.md
├── docker-compose.yml
├── ARCHITECTURE.md
├── DATABASE_SCHEMA.md
├── README.md # stable main feature set
└── README.DEV.md # dev-branch additions (this branch)
```
---
## 🚀 Quick Start
```bash
# 1. Clone
git clone <repo> && cd vulnerability-dashboard
# 2. Configure .env (see ARCHITECTURE.md §6 for full list)
cp .env.example .env
$EDITOR .env # set DATABASE_URL, JWT_SECRET, AUTH_PROVIDER_CRYPTO_KEY,
# WAZUH_API_URL/USER/PASS, WAZUH_INDEXER_URL
# 3. Start
docker compose up -d
# 4. First-start bootstrap creates the admin via /auth/setup-admin (run once)
curl -X POST http://localhost:8000/api/v1/auth/setup-admin \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<strong-pass>","email":"you@example.com"}'
# 5. Open
# UI: http://localhost:3000
# API: http://localhost:8000/docs
```
`docker compose up -d` runs `alembic upgrade head` automatically on backend
boot — no separate migration step needed for fresh deploys.
---
## 📊 Implemented Features
### Threat-intel enrichment (5 sources)
- **EPSS** — FIRST.org probability of exploitation, refreshed every 24 h
- **CISA KEV** — known-exploited catalog, ~1500 CVEs, ransomware-use flag
- **ENISA EUVD** — EU exploited + ENISA-flagged critical, ~1600 CVEs
- **CISA Vulnrichment** — authoritative per-CVE CVSSv3.1 corrections + SSVC decision points (Exploitation / Technical Impact / Automatable). Parses CNA + ADP containers.
- **NVD REST API** — 2nd-stage fallback for CVEs Vulnrichment hasn't analysed yet (rate-limit bounded ≤ 100 per run)
- **cvelistV5** — 3rd-stage backstop, exhaustive ~250 k CVEs, 12 h on-disk ZIP cache
All sources free, no API key required.
### Scoring
- **Priority Score** (0-100) — CVSS + exploit-bonus (KEV/EUVD/EPSS/SSVC/Wazuh/Nessus, capped via max) + SSVC technical-impact/automatable add-on + asset-criticality multiplier + age bonus
- **CPR Score** (0-100) — JacquesKruger percentile-weighted blend: `CVSS×10 × 0.6 + EPSS_percentile × 100 × 0.4`
- **Tenable VPR** (0-10) — surfaced as parallel reference, not folded into Priority/CPR
- **URS** (0-100) — Unified Risk Score combining AVS (CPR-derived) + ASS (impact-weighted SCA) × criticality multiplier, daily snapshots feed trend arrow
### Multi-scanner ingest
- **Wazuh** — agent-based vulnerability detection + SCA compliance
- **Nessus** — REST-API import alongside Wazuh on the same `(cve_id, asset_id)` row; pseudo-CVE pseudo-IDs for non-CVE plugins; per-host scan rows
- **Manual overrides** — 3-stage CVSS/SSVC/fixed_version cascade triggered by "Correct CVSS" button or 03:00 UTC nightly job
### Multi-provider authentication
- **Local** — username + password (bcrypt cost-12) + optional TOTP MFA
- **LDAPS** — search-then-bind, encrypted bind password
- **OIDC** — PKCE + JWKS, Entra ID / Okta / Keycloak / Google
- **SAML 2.0** — python3-saml, strict signature + XSW protection
- JIT provisioning + group-to-role mapping editable in UI
### Compliance + URS
- Wazuh SCA pulled per agent and stored as `compliance_results` + `compliance_checks`
- CIS-Benchmark impact CSV upload weighted into per-policy `weighted_score`
- Unified Risk Score per asset with daily snapshots and 7-day trend arrow
### SLA tracking + notifications
- Per-severity SLA days configurable globally + per policy
- Hourly SLA breach checker → digest or single mode mails
- Policy `DISABLED` status suppresses mails for that policy's assets
- Master kill-switch via `sla_breach_enabled` setting
- Per-vuln 24 h throttle prevents repeat-pinging
- New-vuln digest after every Wazuh/Nessus sync
### UI features
- 2-widget dashboard ("Recent Critical" + "Newly Published")
- Vulns list with VPR badge, EPSS / KEV / EUVD / SSVC pills, cross-scanner confirmation, badge legend popover
- Per-vuln detail with threat-intel sections + Priority/CPR breakdown
- Compliance page with per-asset modal and URS table
- Settings UI for SMTP, Nessus, LDAP/OIDC/SAML, role mappings, mail templates
---
## 🔧 Configuration
`.env` keys at a glance:
```bash
# Database
DATABASE_URL=postgresql://vulnmanager:<pw>@postgres/vulnmanager
# JWT
JWT_SECRET=<random-256-bit>
JWT_ALGORITHM=HS256
JWT_EXPIRY_MINUTES=60
# Auth crypto (Fernet, encrypts TOTP + LDAP pw + SMTP pw at rest)
AUTH_PROVIDER_CRYPTO_KEY=<fernet-key>
# Wazuh
WAZUH_API_URL=https://wazuh-manager:55000
WAZUH_INDEXER_URL=https://wazuh-indexer:9200
WAZUH_API_USER=truevuln-readonly
WAZUH_API_PASS=<pw>
# Optional
DASHBOARD_URL=http://localhost:3000 # used in mail links
INFOMANIAK_API_KEY=<key> # AI analysis
LDAP_BIND_PASSWORD_BOOTSTRAP=<pw> # one-shot for first LDAP setup
HTTPS_PROXY=http://proxy:8080 # for outbound enrichment
```
All UI-relevant config (SMTP, Nessus, LDAP, OIDC, SAML, role mappings,
mail templates) lives in the `settings` table — no env-var redeploy needed.
---
## 📡 API Overview
See `ARCHITECTURE.md §5` for the endpoint reference and `/docs` (Swagger UI)
on the running backend for the full schema. Curl-friendly examples in
`README.DEV.md`.
---
## 🔒 RBAC Permissions
| Role | Capabilities |
|---|---|
| **admin** | Everything — user management, provider config, settings, role mappings, audit log |
| **editor** | Sync, override, enrich, assign, defer, mark FP, schedule scans, compliance refresh, impact import |
| **viewer** | Read-only — list / search vulns, assets, scans, reports, exports |
Enforced by `require_role()` dependency on every router. JWT carries the role
claim; group→role mapping re-evaluates on each SSO/LDAP login.
---
## 🗄️ Database Schema (PostgreSQL)
See `DATABASE_SCHEMA.md` for column-level detail. 20+ tables, 22 alembic
migrations. Key tables:
- `users`, `groups`, `user_groups`
- `assets`, `asset_groups`
- `vulnerabilities`
- `scans`, `scan_schedules`
- `policies`
- `notification_logs`, `audit_logs`
- `compliance_results`, `compliance_checks`, `compliance_impacts`, `asset_risk_snapshots`
- `settings` (KV store), `ai_analyses`, `ai_reports`
---
## 🎨 Workflow Examples
### Daily admin morning routine
1. Dashboard widget shows URS trend + Critical / Newly Published CVEs
2. SLA-breach mail already arrived (00:0008:00 stack)
3. Click "Correct CVSS" once a week to refresh Vulnrichment-corrected scores
4. Compliance widget shows worst 5 assets — drill in via per-asset modal
5. Investigate "Recent Critical" rows — KEV / EUVD badges flag externally-confirmed exploit risk
### Adding a Nessus scan
1. Settings → Integrations → Tenable Nessus → Configure
2. Paste Base URL + API access/secret keys → Test connection
3. Pick default scan IDs → Save
4. Sync now (or wait for next scheduled run)
5. Vulns list now shows Wazuh + Nessus + cross-confirmed rows
### Updating a Vulnrichment-corrected score for a single CVE
```bash
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"…"}' | jq -r .access_token)
curl -X POST "http://localhost:8000/api/v1/vulnerabilities/override/vulnrichment?dry_run=false&cve_ids=CVE-2026-8390" \
-H "Authorization: Bearer $TOKEN"
```
Response stats include `not_found` (CVEs CISA hasn't analysed yet — silently skipped).
### Disabling SLA mails system-wide
```bash
docker compose exec postgres psql -U vulnmanager -d vulnmanager -c "
INSERT INTO settings (key, value) VALUES ('sla_breach_enabled','false')
ON CONFLICT (key) DO UPDATE SET value='false';"
```
Re-enable:
```sql
UPDATE settings SET value='true' WHERE key='sla_breach_enabled';
```
---
## 🚧 Known Limitations
- **`sla_breach_enabled` toggle has no UI yet** — DB-only (Settings page improvement on backlog)
- **CIRCL aggregator** not used — we hit CISA + ENISA directly
- **No real-time KEV push** — we poll the CISA feed (24 h cache)
- **No two-way Nessus FP sync** — marking false-positive in TrueVuln doesn't update Nessus
- **Wazuh-side fix-version field doesn't exist** in the indexer schema — relying entirely on Vulnrichment/NVD/cvelistV5 cascade for PATCH AVAILABLE
- **Per-framework URS breakdown** schema-ready but UI hidden
- **No WebAuthn / FIDO2** — TOTP is the only second factor
---
## 🛠️ Development
### Run tests
```bash
docker compose exec backend pytest tests/
```
Smoke test covers auth, RBAC, vuln CRUD, sync mocking. Coverage is partial
— integration paths (Wazuh, Nessus, enrichment HTTP) rely on manual + tester
verification.
### Add a new vuln column
1. Edit `app/models/vulnerability.py` — add the `Column(...)`
2. Edit `app/schemas/vulnerability.py` (or wherever the Pydantic schemas live)
3. Generate migration:
```bash
docker compose exec backend alembic revision --autogenerate -m "add foo column"
```
4. Review the generated file (autogenerate misses index intent sometimes)
5. `alembic upgrade head`
6. Update `_build_vuln_response()` in `app/routers/vulnerabilities.py` if it should be in the API response
### Add a new threat-intel source
1. New service module in `app/services/` (e.g. `mysource_service.py`)
2. Cache feed JSON in `settings` table for 24 h
3. Wire into `enrichment_service.refresh_threat_intel_enrichment()`
4. Add columns to `vulnerabilities` table via alembic
5. Surface in `_build_vuln_response()` + frontend list/detail
---
## 📈 Performance Optimisations
- Wazuh indexer pagination beyond OpenSearch 10 000-hit cap (search-after)
- Vulnrichment cascade auto-routes to ZIP when > 25 CVEs requested
- cvelistV5 ZIP cached on disk 12 h
- NVD stage capped at 100 CVEs per run (rate-limit safe)
- Async "Correct CVSS" — long-running cascade returns a job_id and a progress card
- Indexed columns on every filter / sort path
- `sources` / `enrichment_sources` JSON columns avoid join-heavy queries
---
## 📚 Further Documentation
- `ARCHITECTURE.md` — system topology, security model, scheduler jobs
- `DATABASE_SCHEMA.md` — every column, every enum, every migration
- `CODEBASE_KNOWLEDGE_BASE.md` — module-by-module walk-through for new contributors
- `README.DEV.md` — dev-branch feature deep-dives (auth, Nessus, compliance, URS)
- `docs/TROUBLESHOOTING.md` — common production issues and fixes
---
## 🐛 Debugging
```bash
# Backend logs (tail and follow)
docker compose logs -f backend
# All services
docker compose logs -f
# Postgres logs
docker compose logs -f postgres
# Inside backend container
docker compose exec backend bash
docker compose exec backend python3 -c "from app.services.enrichment_service import refresh_threat_intel_enrichment; refresh_threat_intel_enrichment()"
# DB shell
docker compose exec postgres psql -U vulnmanager -d vulnmanager
```
`docs/TROUBLESHOOTING.md` lists symptom → cause → fix patterns for the
recurring production issues (alembic head conflicts, MFA setup 500s,
LDAP bind decryption failures, missing SLA mails, etc.).
---
## 📧 Support
Internal IT-Security team. See `README.md` for project owner contact.