vulncheckandClaude Opus 5 6b3744e569 fix(severity): read the Chromium severity Google states in prose
Chrome CVE records carry no metrics block whatsoever — Google puts the
severity in the description instead: "… (Chromium security severity:
Critical)". Every fresh Chrome finding therefore landed on the neutral
'medium' placeholder, so a Critical sandbox escape sorted level with a Low UI
glitch, both in the queue and in the daily digest mail. For a team that
triages by severity that is worse than no data.

The severity word is now read from the description when no metrics block
exists. No score is invented — only what the vendor stated, and a real CVSS
always wins over the prose. Once NVD publishes a score, the existing
CVSS-sync takes over as before.

Existing rows heal on the next scan: the upsert already lifts a finding off
the medium placeholder when a source reports a better severity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:06:15 +02:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00
2026-02-08 10:15:20 +01:00

TrueVuln Logo

TrueVuln Dashboard

Open Source Vulnerability Management — Wazuh · Nessus · Microsoft Intune
Aggregate findings from multiple scanners, enrich them from authoritative sources, and prioritize, verify, and resolve them with automated workflows and AI-powered analysis.

Docker Ready License Status Python Next.js


Overview

TrueVuln is a self-hosted vulnerability management dashboard that aggregates findings from Wazuh, Tenable Nessus, and Microsoft Intune/Defender into a single, prioritized view of your infrastructure's security posture. It enriches every CVE from authoritative open sources, detects end-of-life software and Microsoft 365 Apps gaps that scanners miss, and automates SLA tracking, patch verification, and reporting -- backed by AI analysis from multiple providers.

Key capabilities:

  • Multi-source inventory & findings: Wazuh agents, Nessus scans, Microsoft Intune (MDM/UEM) + Defender for Endpoint TVM
  • Cross-confirmation when several scanners report the same CVE on the same host
  • Threat-intel enrichment: EPSS, CISA KEV, ENISA EUVD, public-exploit catalogs (Exploit-DB / PoC-in-GitHub / Metasploit), and authoritative CVE dates/CVSS (cvelistV5 / Vulnrichment / NVD)
  • Multi-source remediation: scanner solution + Microsoft (MSRC KBs), Ubuntu USN, Red Hat/CentOS errata, and the OSV.dev aggregator
  • End-of-life / end-of-support detection (endoflife.date + the Microsoft product-lifecycle export) and Microsoft 365 Apps CVE detection (not in NVD)
  • Built-in App→CVE scanner (OSV / NVD-CPE / cvelistV5) for software with no real vulnerability scanner, plus Wazuh false-positive suppression
  • Mobile device security for Intune-managed phones/tablets: EOL/EOS (Samsung/Apple), Android patch-level staleness, and per-CVE Android detection (Samsung SMR / Google ASB)
  • Security Advisory Feeds page: CISA KEV "actively exploited" plus configurable RSS sources (ZDI, CERT-EU, BSI/CERT-Bund, Cisco PSIRT, custom URLs) — early-warning sources that often publish before NVD/cvelistV5
  • AI-powered CVE analysis + on-demand, OS-aware remediation generation
  • Priority + CPR risk scoring, SLA enforcement with email alerts, automated patch verification
  • Multi-provider authentication (local, LDAP, OIDC, SAML) with TOTP MFA
  • Role-based access with a full revision-proof audit trail, PDF/CSV reporting

Features

Vulnerability Management

  • Filter, sort, and search across all detected CVEs
  • Bulk status updates, user/group assignment, and deferral workflows
  • Priority scoring (0-100) dynamic formula:
    • Base Score: (CVSS Score + Exploit Bonus) * Asset Criticality Factor
    • Exploit Bonus: +4 (Exploit Available), +2 (Exploitable)
    • Asset Criticality: Factor 0.8 (Low) to 2.0 (Critical) based on Group Policy SLA
    • Age Penalty: +20 points max (Linear increase over 30 days)
  • Status tracking: Open, Patched, Pending Verification, Patch Failed, Accepted Risk, False Positive, Deferred
  • Per-vulnerability notification suppression

Scanner & Inventory Integrations

  • Wazuh -- auto-discover agents, sync vulnerability data, trigger Syscollector scans from the UI, automated patch verification (mark patched → rescan → confirm)
  • Tenable Nessus -- import findings from configured scans (X-ApiKeys), launch/poll/import scans, VPR score, exploit availability/maturity, scanner remediation text
  • Microsoft Intune (Graph API) -- app-only (client-credentials) sync of managed devices → assets + OS-EOL, and detectedApps → EOL/M365 detection (no Wazuh agent required)
  • Microsoft Defender for Endpoint (TVM) -- optional real per-device CVEs via the Defender API
  • One CVE per asset (deduplicated); multiple scanners on the same finding are merged and flagged cross-confirmed; assets are soft-inactivated (not deleted) when a source stops reporting them, with audit trail

Threat-Intelligence Enrichment

  • EPSS (FIRST.org) exploitation probability, CISA KEV (known-exploited, plus a dedicated "actively exploited" advisory dashboard widget independent of asset findings), ENISA EUVD (EU exploited/critical)
  • Public-exploit catalogs -- Exploit-DB, PoC-in-GitHub, Metasploit module index
  • Authoritative CVE metadata -- published/last-modified dates, CVSS, and descriptions from CVE.org cvelistV5, CISA Vulnrichment, and the NVD API
  • CVSS-correction cascade (for placeholder/missing scores) -- CISA Vulnrichment → NVD → cvelistV5 → GitHub Advisories (GHSA), each source only for the CVEs the previous one left empty; real CVE-IDs only. An NVD_API_KEY raises the NVD stage from 5 to 50 req/30s, an optional GitHub PAT lifts GHSA from 60 to 5000 req/h
  • Mozilla MFSA -- per-CVE impact rating (critical/high/moderate/low) straight from Mozilla's advisory repo, for fresh Firefox CVEs that have no score in NVD/cvelistV5 yet
  • Metrics are CVE-global -- a finding created without a score inherits CVSS/EPSS/KEV from the same CVE on another asset, so the same CVE never shows two different scores

End-of-Life & Microsoft 365 Detection

  • EOL/EOS detection for installed software and OS via endoflife.date plus the Microsoft product-lifecycle export (covers exotics like SQL Server, Visual C++ Redistributables, Silverlight)
  • Severity scales with age -- a product 1000+ days past end-of-life is rated CRITICAL rather than a flat HIGH, and EOL findings cite the control failure (PCI-DSS 6.3.3, NIST 800-53 CM-8, HIPAA 164.312(a)(1)) for audit reports
  • Self-correcting -- findings whose product match no longer holds are re-evaluated and closed automatically; the check runs as a background job with live progress instead of blocking the browser
  • Microsoft 365 Apps CVE detection -- compares the installed Office build against the Microsoft 365 Apps security-update channels for CVEs that never reach NVD or Wazuh

Windows OS & Microsoft Product CVE Detection

  • Windows OS CVEs (Server AND Client) via cvelistV5's bounded build ranges, family-matched (a Win11 24H2 host never matches a Server-2025-only fix even though both live on build line 26100) — patch-level accurate, available on Patch Tuesday, well ahead of the Wazuh CTI feed
  • SharePoint 2013/2016/2019/Subscription and modern .NET (8/9/10) detection with one registry key per release, so generic version floors can't cross-match releases (.NET Framework is excluded by design: its ARP version is static across monthly patches — Defender TVM covers it file/KB-based)
  • MSRC fixed-build scan as a second, authoritative source (KB numbers per servicing branch); cross-confirms, auto-resolves once a host catches up, nightly job + manual trigger (POST /api/v1/vulnerabilities/msrc-scan)
  • Microsoft Edge CVEs -- MSRC is the only machine-readable source for these (they appear in neither NVD nor cvelistV5); matched against Edge's own fixed build, never against Chromium/Chrome ranges, since the two build schemes diverge entirely (Edge 150.0.4078.99 rides on Chromium 150.0.7871.187)

Built-in App→CVE Scanner & Mobile Device Security

  • App→CVE scanner -- maps installed software (Wazuh syscollector packages, Intune detectedApps) to real CVEs via a curated OSV / NVD-CPE registry plus a direct cvelistV5 range match (catches fresh CVEs NVD hasn't CPE'd yet, or ones filed under a CPE product string that wasn't curated); results cross-confirm and enrich like any other source
  • False-positive suppression -- flags a Wazuh finding as false-positive when the installed version is provably outside every clean cvelistV5 version range for the matched product (e.g. a SQL Server 2019 host wrongly flagged with a 2022/2025-only CVE)
  • GitHub repository advisories -- CVEs a project publishes only on its own GitHub advisory page, reaching neither NVD nor cvelistV5 (e.g. Notepad++); version ranges come straight from the maintainer, with the published patched version as a safety net
  • One finding, every affected product -- when a single CVE hits two products on the same host (e.g. Chrome and Edge), the finding lists both with their own installed and fixed versions instead of naming only whichever scanner ran first
  • Vendor / publisher captured from every inventory source (Wazuh vendor, Intune publisher, Defender softwareVendor) and shown on the finding
  • Mobile device EOL/EOS (Samsung, Apple) and Android security-patch-level staleness for Intune-managed phones/tablets, with a dedicated dashboard widget
  • Per-CVE Android detection -- Samsung's own SMR bulletin (precise, excludes chipset CVEs that don't apply to the device) with Google's ASB as a fallback

Multi-Source Remediation

The CVE detail page shows remediation from every available source side by side:

  • Scanner solution (Nessus), Microsoft (MSRC) KB + fixed build + download link (filtered to the host's build), Ubuntu USN, Red Hat / CentOS / Alma errata, and the OSV.dev aggregator (Debian, SUSE, Alpine, Rocky, language ecosystems)
  • Workarounds / mitigations / containment when no patch exists yet; the MSRC update-guide link per CVE

AI-Powered Analysis

Supports multiple AI providers for CVE analysis, threat assessment, and on-demand OS-aware remediation generation (including an OpenRouter integration for the per-CVE "Generate fix steps" button, EOL-aware for end-of-life findings):

Provider Models
OpenAI GPT-5.2, GPT-4.1, o3, o3-mini
Anthropic Claude Opus 4.5, Sonnet 4.5, Haiku 4.5
Google Gemini Gemini 3 Pro, 3 Flash, 2.5 Pro
DeepSeek V3, R1 (Reasoner)
Ollama (local) Llama 3.3, Mistral, CodeLlama, Phi-4
Infomaniak Llama 3, Mistral 3, Mixtral, Granite, Qwen 3, Gemma 3n

Ollama in Docker: Use http://host.docker.internal:11434/v1 as the Base URL when running TrueVuln in Docker with Ollama on the host.

Automated Workflows

  • Scheduled scans with configurable intervals (hourly, daily, weekly) or cron expressions
  • SLA breach monitoring runs hourly, sends email alerts to assigned users/groups
  • Patch verification triggers a Wazuh rescan and updates status automatically

Notifications

  • New-CVE email alerts from every scanner source (Wazuh, Nessus, App→CVE scanner, Defender TVM) — not just Wazuh
  • Customizable HTML email templates for SLA breaches, single new-CVE alerts, and the aggregated digest — all editable in the UI with live preview
  • Digest table sorted by CPR (priority) descending, with per-CVE affected-systems count and ready-made deep links; template variables include {{cpr_score}}, {{affected_assets_count}}, {{cve_link}}, {{asset_link}}
  • Delivery schedule: per-sync (immediate) or a nightly roundup — one aggregated mail per recipient of the day's new CVEs
  • Send rate limiting (delay between mails + max per run) to respect SMTP-provider anti-spam limits
  • Recipient routing by asset/finding assignment (user or group), with a configurable Default Recipient(s) fallback — or all admins — for unassigned new-CVE mails
  • Notification history with status tracking (Sent, Failed, Suppressed) — admins see all; other roles see only notifications addressed to them

Performance Note: Most SMTP providers (Proton, Gmail, Outlook) enforce strict rate limits (~10-20 emails/minute). Use nightly roundup mode and/or the send rate limit to stay under them. SLA-breach notifications are throttled to 1 email per vulnerability per 24 hours by default.

Asset Management

  • Auto-discovery from Wazuh agents or manual creation
  • Bulk assignment to users, groups, and SLA policies
  • Track OS, location, owner, and scan history per asset

Asset ownership: Assigning an asset (or an individual finding) to a user/group is a notification/ownership tag — it routes new-CVE and SLA-breach emails to that owner. It does not affect scanning, scoring, or visibility (all view-permitted users see every CVE). New-CVE mails fall back to the default recipients / admins when unassigned; SLA-breach mails have no fallback and require an assignee, so assign each asset to a system owner. See README.DEV for details.

SLA Policies

  • Define severity-based remediation windows (e.g., Critical: 2 days, High: 7 days)
  • Assign policies to assets
  • Automatic breach detection with throttled email alerts (1 per 24h per vulnerability)

Reporting

  • Executive summary PDF with severity breakdown and compliance metrics
  • CSV export of all vulnerabilities with full metadata
  • Dashboard with real-time statistics, trends, and AI prioritization

Security

  • JWT authentication with role-based access control (Admin, Editor, Readonly)
  • 30-minute access token with silent refresh against a 7-day refresh cookie -- an actively used session stays signed in; only real inactivity logs you out
  • Revisionssicher audit trail in both directions: a finding going open → patched and patched → open is recorded, naming the CVE, the host and the scanner that caused it
  • Audit log with full-text search (description, event, resource, IP, user), sortable columns and real pagination
  • Bcrypt password hashing; login rate-limit + account lockout after 5 failed attempts — temporary 15-min auto-unlock, or opt-in permanent lockout cleared only by an admin (the last active admin is never permanently locked)
  • Comprehensive audit logging (who changed what, when), optionally forwarded to a syslog/SIEM server (UDP/TCP, RFC-5424, per-event severity) for central alerting
  • Security headers (CSP, HSTS, X-Frame-Options)
  • Rate limiting on API endpoints

Role-Based Access Control (RBAC)

Three roles with hierarchical permissions: Admin > Editor > Readonly

Action Readonly Editor Admin
View vulnerabilities, assets, reports, dashboards
View/download reports (Executive, Technical CSV, ISO 27001, Patching)
View scan history
View notification history own only own only all
View AI analysis history
Edit vulnerabilities (status, assign, defer, reopen)
Trigger Wazuh sync and scans
Run AI analysis on vulnerabilities
Create/Edit assets
Edit SLA policies
Manage scan schedules
Bulk update vulnerabilities and assets
Delete assets
Create/Delete SLA policies
Manage users (create, edit, delete, reset password)
Manage groups
Configure settings (Wazuh, Nessus, Intune, AI, SMTP)
Send test/critical notifications
View audit logs

Screenshots

Dashboard

TrueVuln Dashboard


Quick Start

Prerequisites

System Requirements

Resource Minimum Recommended
CPU 2 cores 4 cores
RAM 2 GB 4 GB
Disk 10 GB 20 GB

Note: These requirements are for the TrueVuln application only. If running Wazuh on the same VM, add its requirements accordingly.

1. Clone the repository

git clone https://gitea.isuit.ch/vulncheck/vulncheck.git
cd vulncheck

2. Configure environment

Important: You must create a .env file before running docker compose. Skipping this step produces: required variable POSTGRES_PASSWORD is missing a value: POSTGRES_PASSWORD env var must be set

cp .env.example .env

Then edit .env and set at minimum:

  1. POSTGRES_PASSWORD — must be non-empty. Replace the default changeme with a strong password.

  2. JWT_SECRET_KEY — generate with:

    openssl rand -hex 32
    

    Paste the output as JWT_SECRET_KEY in .env.

  3. DEFAULT_ADMIN_PASSWORD — must satisfy strength rules: ≥8 chars with uppercase, lowercase, digit, and special character. The default ChangeMe123! passes; weaker values (like changeme) cause the admin not to be created and login will fail.

  4. AUTH_COOKIE_SECUREtrue only when serving the UI over HTTPS (e.g. behind a reverse proxy with TLS). If you are accessing the app over plain HTTP (e.g. http://server:3003 during testing), set this to false, otherwise the browser silently drops the session cookie and login appears to do nothing — the page refreshes but the user stays logged out.

    # Plain HTTP / testing without reverse proxy
    sed -i 's/^AUTH_COOKIE_SECURE=.*/AUTH_COOKIE_SECURE=false/' .env
    

The .env.example ships with production-ready defaults for running behind a reverse proxy with HTTPS. Also review TIMEZONE and DASHBOARD_URL.

Verify the file exists and POSTGRES_PASSWORD is set before continuing:

grep -E '^(POSTGRES_PASSWORD|JWT_SECRET_KEY)=' .env

See .env.example for all available options with descriptions.

3. Start the application

docker compose up -d --build

Run this command from the same directory as docker-compose.yml and .env. Docker Compose auto-loads .env from the working directory; running from elsewhere will trigger the missing-variable error above.

Wait 2-3 minutes after deploy before opening the UI. The first start has to pull images, run database migrations, build the Next.js frontend, and warm up the backend. Hitting the login page too early can show a blank screen, a 502, or login that "looks" broken because the frontend bundle is still compiling. Watch docker compose ps until all three services report Up (healthy) / Up, and docker compose logs frontend shows ✓ Ready in ….

This starts three containers:

Service URL Description
Frontend http://localhost:3003 Web UI
Backend http://localhost:8022 API (bound to localhost only)
PostgreSQL Port 5432 Database

TrueVuln is designed to run behind a reverse proxy (Nginx Proxy Manager, Traefik, Caddy, etc.):

  1. Point your reverse proxy to the frontend port (default 3000)
  2. The frontend proxies all API calls to the backend internally via Docker networking
  3. You do not need to expose the backend port (8000) externally

Required proxy settings:

  • Forward X-Forwarded-For and X-Real-IP headers (for rate limiting and audit logs)
  • Enable WebSocket passthrough if needed

Required .env settings (already set in .env.example):

  • TRUST_PROXY_HEADERS=true
  • AUTH_COOKIE_SECURE=true (when using HTTPS)

4. First login

On first run (only if no admin exists), an admin account is created using:

  • DEFAULT_ADMIN_USERNAME (default: admin)
  • DEFAULT_ADMIN_PASSWORD (default: ChangeMe123!)
  • DEFAULT_ADMIN_EMAIL (default: admin@vulnmanager.local)

Password strength is enforced. DEFAULT_ADMIN_PASSWORD must be at least 8 characters and contain an uppercase letter, a lowercase letter, a digit, and a special character (e.g. !@#$%^&*). If the password fails this check, the admin is not created and login will fail. Check docker compose logs backend | grep -i admin to confirm.

Change the admin password immediately after first login via Settings > User Management. The default/bootstrapped credentials are not re-applied if an admin already exists.

Verify the admin was created:

docker compose logs backend | grep "Default admin user created"

If you see DEFAULT_ADMIN_PASSWORD does not meet strength requirements instead, fix the password in .env, then either restart the stack with a fresh database or use the Emergency admin reset.

Login page refreshes but nothing happens?

Almost always one of two issues:

  1. AUTH_COOKIE_SECURE=true while serving over HTTP. The browser silently drops the cookie. Set AUTH_COOKIE_SECURE=false in .env and restart the backend (docker compose up -d backend).
  2. Frontend container was not rebuilt after pulling updates. Run docker compose up -d --build frontend, then clear the site's cookies / use an incognito tab.

End-to-end sanity check from the server itself (replace host/port if you changed them):

curl -c /tmp/jar -s -o /dev/null -w "login: %{http_code}\n" \
  -X POST http://localhost:3003/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"ChangeMe123!"}'
curl -b /tmp/jar -s -o /dev/null -w "me:    %{http_code}\n" \
  http://localhost:3003/auth/me

Both lines should print 200. If login: 200 and me: 401, the cookie is being rejected on the round-trip — recheck AUTH_COOKIE_SECURE (must be false over HTTP).

Emergency admin reset (optional)

If you lost the admin password, you can reset it via a one-time setup token.

  1. Set these env vars and restart the backend:
SETUP_ADMIN_TOKEN=change-me
ALLOW_ADMIN_RESET=true
  1. Call the endpoint (replace token & password):
curl -X POST https://your-domain.tld/auth/setup-admin \
  -H "Content-Type: application/json" \
  -H "X-Setup-Token: change-me" \
  -d '{"username":"admin","password":"NewStrongPass123!","email":"admin@vulnmanager.local"}'
  1. Remove SETUP_ADMIN_TOKEN (and set ALLOW_ADMIN_RESET=false) and restart.

5. Configure integrations

In Settings, configure the integrations you need (all optional, all stored encrypted at rest):

  1. Wazuh SIEM -- API URL, credentials, and Indexer connection
  2. Tenable Nessus -- base URL + API keys, default scan IDs
  3. Microsoft Intune (Graph API) -- tenant/client ID + client secret (app-only); optional Defender TVM
  4. AI Provider / OpenRouter -- choose your provider and enter API credentials
  5. SMTP Email -- for SLA breach and vulnerability notifications

Architecture

┌─────────────┐     ┌──────────────┐     ┌──────────────┐
│   Browser    │────>│  Next.js 16  │────>│   FastAPI     │
│              │     │  (Port 3003) │     │  (Port 8022)  │
└─────────────┘     └──────────────┘     └───────┬───────┘
                                                  │
                                    ┌─────────────┼─────────────┐
                                    │             │             │
                              ┌─────▼────┐  ┌────▼─────┐  ┌───▼────┐
                              │PostgreSQL │  │ Scanners │  │   AI   │
                              │    15     │  │ & feeds  │  │Provider│
                              └──────────┘  └──────────┘  └────────┘

External sources: Wazuh, Tenable Nessus, Microsoft Graph (Intune) + Defender TVM, and read-only enrichment feeds — EPSS/KEV/EUVD, cvelistV5/Vulnrichment/NVD, endoflife.date + MS lifecycle export, MSRC, Ubuntu/Red Hat advisories, OSV.dev, public-exploit catalogs.

Tech Stack:

Layer Technology
Frontend Next.js 16, React 19, Tailwind CSS 4, DaisyUI, TypeScript
Backend FastAPI, SQLAlchemy 2.0, Pydantic 2, APScheduler
Database PostgreSQL 15
Auth JWT (python-jose), bcrypt
Reports ReportLab (PDF), CSV export
Deployment Docker Compose, multi-stage builds, non-root containers

Configuration

Environment Variables

All configuration is done via the .env file:

Variable Default Description
POSTGRES_USER vulnmanager Database username
POSTGRES_PASSWORD changeme Database password
POSTGRES_DB vulnmanager Database name
POSTGRES_PORT 5432 Database port
BACKEND_PORT 8022 Backend API port
FRONTEND_PORT 3003 Frontend UI port
JWT_SECRET_KEY -- Required. JWT signing key (openssl rand -hex 32)
ENV production development or production
TIMEZONE UTC Server timezone (e.g., Europe/Zurich)
NVD_API_KEY -- Optional. Raises the NVD stage of the CVSS cascade from 5 to 50 req/30s and its batch cap from 100 to 1500 CVEs
AUTH_COOKIE_SECURE auto true for HTTPS, false for HTTP. Auto-detected from ENV if not set
AUTH_COOKIE_SAMESITE lax Cookie SameSite policy
TRUST_PROXY_HEADERS false Trust X-Forwarded-For for client IP (enable behind reverse proxy)
DEFAULT_ADMIN_USERNAME admin Default admin username (first run only)
DEFAULT_ADMIN_PASSWORD changeme Default admin password (first run only)
DASHBOARD_URL -- External URL for email notification links

Integrations

All integrations (Wazuh, Nessus, Intune/Defender, AI/OpenRouter, SMTP) are configured through the Settings page in the UI and stored encrypted at rest. No additional environment variables are required. Optional env keys exist for headless/CI use (e.g. NVD_API_KEY, OPENROUTER_API_KEY) — see .env.example. Multi-provider auth (LDAP/OIDC/SAML/MFA) is configured via env + the auth admin UI; see .env.example.


Production Deployment

Reverse Proxy Setup

TrueVuln is designed to run behind a reverse proxy. Only the frontend port needs to be reachable by the proxy -- the frontend handles all API routing internally.

Internet → Reverse Proxy (443/HTTPS) → Frontend (3003) → Backend (8022, internal)

Nginx Proxy Manager example:

  1. Add a new proxy host pointing to http://<docker-host>:3003
  2. Enable SSL with Let's Encrypt
  3. No custom location blocks needed -- the frontend proxies everything

Manual Nginx example:

server {
    listen 443 ssl;
    server_name vuln.example.com;

    location / {
        proxy_pass http://127.0.0.1:3003;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

The backend port (8022) is bound to 127.0.0.1 by default in docker-compose.yml and does not need to be exposed.

Security Checklist

  • Set a strong JWT_SECRET_KEY (openssl rand -hex 32)
  • Set a strong POSTGRES_PASSWORD
  • Change the default admin password after first login
  • Set ENV=production
  • Set AUTH_COOKIE_SECURE=true (HTTPS)
  • Set TRUST_PROXY_HEADERS=true (behind reverse proxy)
  • Configure TLS via reverse proxy
  • Restrict database port (5432) to internal networks
  • Block external access to ports 3003 and 8022 via firewall

Development

Local Setup (without Docker)

Backend:

# Python 3.11+
pip install -r requirements.txt
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Frontend:

cd frontend
npm install
npm run dev

Database:

Start PostgreSQL locally or via Docker:

docker run -d --name vulncheck-db \
  -e POSTGRES_USER=vulnmanager \
  -e POSTGRES_PASSWORD=changeme \
  -e POSTGRES_DB=vulnmanager \
  -p 5432:5432 \
  postgres:15-alpine

Database Migrations

Migrations are managed with Alembic:

# Apply all migrations
alembic upgrade head

# Create a new migration
alembic revision --autogenerate -m "description"

Project Structure

vulncheck/
├── app/                    # FastAPI backend
│   ├── main.py            # Application entrypoint
│   ├── database.py        # SQLAlchemy configuration
│   ├── scheduler.py       # APScheduler (scans, SLA checks)
│   ├── models/            # SQLAlchemy ORM models
│   ├── routers/           # API route handlers
│   ├── services/          # Business logic (email, etc.)
│   ├── integrations/      # Wazuh & AI client libraries
│   └── auth/              # JWT & RBAC
├── frontend/              # Next.js application
│   ├── app/               # Pages (App Router)
│   ├── components/        # React components
│   ├── lib/               # API client, utilities
│   └── types/             # TypeScript definitions
├── alembic/               # Database migrations
├── docker-compose.yml
├── Dockerfile             # Backend container
└── frontend/Dockerfile    # Frontend container (multi-stage)

API Overview

The backend exposes a REST API at /api/v1/. All endpoints require JWT authentication unless noted otherwise.

Endpoint Group Base Path Description
Authentication /auth Login, logout, user management
Vulnerabilities /api/v1/vulnerabilities CRUD, AI analysis, Wazuh sync, App→CVE scan, FP suppression, bulk ops
Assets /api/v1/assets Inventory, assignment, sync-source filter, bulk ops
Advisories /api/v1/advisories CISA KEV feed + configurable RSS advisory feeds (/feeds, /feeds/refresh)
Scans /api/v1/scans Scan jobs, schedules
Policies /api/v1/policies SLA policy management
Groups /api/v1/groups User group management
Notifications /api/v1/notifications Notification log, test email
Reports /api/v1/reports PDF/CSV export
Settings /api/v1/settings System configuration
Audit /audit Audit trail (admin only)
Health /health Health check (no auth)

Interactive docs (Swagger UI): http://<host>:8022/docs, ReDoc at /redoc. Enabled automatically in development (ENV=development). On a production instance set ENABLE_API_DOCS=true in .env to turn them on. The raw OpenAPI spec is always served at /openapi.json — import it into Postman/Insomnia or generate a client, regardless of the docs toggle.

Calling the API from an external system

The API is standard REST + JSON, secured with a JWT Bearer token. Flow: (1) log in once to get a token, (2) send it as Authorization: Bearer … on every request. Access tokens expire after 30 min; use the refresh token or just log in again for a fresh one.

Use a dedicated service user (role viewer is enough for read-only ITSM/CMDB pulls) and disable MFA on it — otherwise /auth/login returns mfa_required and expects a second /auth/mfa/verify step. Replace https://truevuln.example.com with your host.

1 — Log in, get a token

TOKEN=$(curl -s -X POST https://truevuln.example.com/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"svc-itsm","password":"••••••"}' \
  | jq -r .access_token)

Response body:

{
  "access_token": "eyJhbGci…",
  "refresh_token": "eyJhbGci…",
  "token_type": "bearer",
  "user": { "id": 7, "username": "svc-itsm", "role": "viewer" },
  "mfa_required": false
}

2 — List all vulnerabilities (with the token)

curl -s https://truevuln.example.com/api/v1/vulnerabilities \
  -H "Authorization: Bearer $TOKEN"

Filter — the list endpoint takes query params (combine freely):

# Only actively-exploited (CISA KEV), critical, open findings — top 500
curl -s "https://truevuln.example.com/api/v1/vulnerabilities?kev_only=true&severity=critical&status=open&limit=500" \
  -H "Authorization: Bearer $TOKEN"

# Everything on one specific asset
curl -s "https://truevuln.example.com/api/v1/vulnerabilities?asset_id=42" \
  -H "Authorization: Bearer $TOKEN"

# Only Nessus-sourced findings, sorted by CVSS
curl -s "https://truevuln.example.com/api/v1/vulnerabilities?source=nessus&sort_by=cvss&sort_order=desc" \
  -H "Authorization: Bearer $TOKEN"

Common query params: severity (critical/high/medium/low), status, kev_only, exploitable, epss_min (0.01.0), source (wazuh/nessus/manual), asset_id, search (CVE-ID/package/title), sort_by (priority/cvss/detected_at), sort_order, limit (≤1000). Full list + response schema in /docs.

3 — Pull the asset inventory (ITAM/CMDB sync)

curl -s https://truevuln.example.com/api/v1/assets \
  -H "Authorization: Bearer $TOKEN"

4 — Refresh an expired token (no re-login needed within refresh-token life)

curl -s -X POST https://truevuln.example.com/auth/refresh \
  -H 'Content-Type: application/json' \
  -d "{\"refresh_token\":\"$REFRESH_TOKEN\"}"

Python example (e.g. an ITSM integration script):

import requests

BASE = "https://truevuln.example.com"
tok = requests.post(f"{BASE}/auth/login",
                    json={"username": "svc-itsm", "password": "••••••"}).json()["access_token"]
hdr = {"Authorization": f"Bearer {tok}"}

vulns = requests.get(f"{BASE}/api/v1/vulnerabilities",
                     params={"kev_only": True, "status": "open", "limit": 1000},
                     headers=hdr).json()
for v in vulns:
    print(v["cve_id"], v["severity"], v["asset_hostname"])

Each finding is a flat JSON object — key fields: cve_id, asset_id, asset_hostname, cvss_score, severity, status, epss_score, kev_listed, exploit_available, priority_score, sources, detected_at.

Response shape: with the default sort_by=priority the endpoint returns a bare JSON array. With sort_by=cvss or sort_by=detected_at it returns a paged object {"items": [...], "total": N} instead — read .items in that case.

Note: tokens are short-lived user JWTs (30 min). A long-lived API key / service token and outbound webhooks are not built yet — say so if you need them.


Contributing

Contributions are welcome. To get started:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/your-feature)
  3. Make your changes
  4. Run tests if applicable (pytest for backend)
  5. Commit and push (git push origin feature/your-feature)
  6. Open a Pull Request

Please open an issue first for larger changes to discuss the approach.


License

This project is licensed under the GNU Affero General Public License v3.0 (AGPLv3). See LICENSE for details.


Operations Guide

Admin Account Locked Out

Accounts are locked after 5 failed login attempts and auto-unlock after 15 minutes.

# Option 1: Wait 15 minutes (auto-unlock)

# Option 2: Unlock immediately via database
docker exec -it vulnmanager-db psql -U vulnmanager -d vulnmanager -c \
  "UPDATE users SET failed_login_attempts = 0 WHERE username = 'admin';"

Reset Admin Password

# 1. Generate a new bcrypt hash inside the backend container
docker exec -it vulnmanager-backend python3 -c "
from app.auth.jwt_handler import hash_password
print(hash_password('YourNewSecurePassword123!'))
"

# 2. Update the hash in the database
docker exec -it vulnmanager-db psql -U vulnmanager -d vulnmanager -c \
  "UPDATE users SET password_hash = '<paste-hash-here>' WHERE username = 'admin';"

Alternatively, use the "Reset PW" button in Settings > User Management (requires another admin account).

Create Emergency Admin User

If all admin accounts are inaccessible:

# 1. Generate a password hash
docker exec -it vulnmanager-backend python3 -c "
from app.auth.jwt_handler import hash_password
print(hash_password('EmergencyAdmin123!'))
"

# 2. Insert a new admin user
docker exec -it vulnmanager-db psql -U vulnmanager -d vulnmanager -c \
  "INSERT INTO users (username, email, password_hash, role, is_active, is_verified, failed_login_attempts, created_at, updated_at)
   VALUES ('emergency_admin', 'admin@example.com', '<paste-hash-here>', 'admin', true, true, 0, NOW(), NOW());"

Database Backup & Restore

# Backup
docker exec vulnmanager-db pg_dump -U vulnmanager vulnmanager > backup_$(date +%Y%m%d).sql

# Restore
docker exec -i vulnmanager-db psql -U vulnmanager vulnmanager < backup_20260201.sql

Run Database Migrations Manually

Migrations run automatically on container start. To run manually:

docker exec -it vulnmanager-backend alembic upgrade head

View Logs

# All services
docker compose logs -f

# Backend only
docker compose logs -f backend

# Last 100 lines
docker compose logs --tail=100 backend

Restart Services

# Restart all
docker compose restart

# Restart only backend
docker compose restart backend

# Full rebuild (after code changes)
docker compose down && docker compose up -d --build

Check Service Health

# Container status
docker compose ps

# Backend API docs
curl -s http://localhost:8022/docs | head -1

# Database connectivity
docker exec vulnmanager-db pg_isready -U vulnmanager

Support

If you encounter issues or have questions, please open an issue in this repository.

Email Support: support-vulncheck.sq9vd@passmail.net


Support the Project

If TrueVuln is useful to you, consider buying me a coffee!

Buy Me A Coffee


Disclaimer

USE AT YOUR OWN RISK. This software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement.

In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.

This software is intended for authorized security testing and vulnerability management only. Users are solely responsible for ensuring compliance with all applicable laws and regulations. The authors assume no liability for misuse or any damages resulting from the use of this software.

S
Description
No description provided
Readme AGPL-3.0
22 MiB
Languages
TypeScript 54.1%
Python 44.3%
Shell 1%
Dockerfile 0.4%