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.
378 lines
14 KiB
Markdown
378 lines
14 KiB
Markdown
# 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:00–08: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.
|