Skip to content

About

Up-To-Date CVE Manager: Enterprise-grade vulnerability management system with real-time CVE scanning, cross-platform software discovery, and professional EDR-style dashboard. Automatically identifies security vulnerabilities in installed software using NIST NVD API with encrypted credential storage

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

UTD CVE Manager

Software Bill of Materials vulnerability management, powered by the NIST National Vulnerability Database.

Python Flask License Vulnerability Data Local First

SBOM Formats Tests


The Problem

A workstation or server accumulates software faster than anyone tracks it. Applications install transitive dependencies, build pipelines pull packages, and nobody writes down what is actually running. When a CVE lands, the question is always the same: does this affect us?

UTD CVE Manager answers that by enumerating what is installed, matching it against the NIST National Vulnerability Database, and telling you exactly which components carry known vulnerabilities — with severity, CVSS score, and a recommended action.

It reads your inventory three ways, so nothing slips through the gaps:

flowchart LR
    subgraph Sources["Inventory Sources"]
        direction TB
        A["🪟 Registry Scan<br/>Windows · macOS · Linux"]
        B["📄 SBOM Import<br/>11 formats + gzip"]
        C["✍️ Manual Entry<br/>CSV or form"]
    end

    subgraph Engine["UTD CVE Manager"]
        D["Component Database<br/>SQLite"]
        E["NVD CVE Lookup<br/>CPE virtualMatchString"]
    end

    subgraph Output["Results"]
        F["📊 Live Dashboard"]
        G["🔍 CVE Detail View"]
        H["📄 Reports"]
        I["📧 Email Alerts"]
    end

    A --> D
    B --> D
    C --> D
    D --> E
    E --> F
    E --> G
    E --> H
    E --> I

    style Sources fill:#16213e,stroke:#00d4ff,stroke-width:2px,color:#fff
    style Engine fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff
    style Output fill:#16213e,stroke:#00d4ff,stroke-width:2px,color:#fff
    style D fill:#00d4ff,stroke:#fff,stroke-width:1px,color:#0a0a0a
    style E fill:#ff6b35,stroke:#fff,stroke-width:1px,color:#0a0a0a
Loading

Quick Start

git clone https://github.com/iampopg/UTD-CVE-Manager.git
cd UTD-CVE-Manager
pip install -r requirements.txt
python app.py

Your browser opens automatically at http://127.0.0.1:5000. If that port is busy, the app walks forward to 5009 and prints the URL it chose.

No API key is required. Scanning works immediately out of the box. Add an NVD API key when you want the faster rate limit.


Performance: With and Without an API Key

This is the single biggest decision affecting how long a scan takes. The NVD publishes different quotas for authenticated and anonymous callers, and the app honours both automatically.

Mode Delay between components Requests / 30s Throughput 300-component scan
🔑 With API key 0.6s 50 ~6,000 req/hour ~3 minutes
🌐 Without API key 6.0s 5 ~600 req/hour ~30 minutes

With an API key — 0.6s per component

xychart-beta
    title "With API key (0.6s per component)"
    x-axis ["25", "50", "100", "250", "500", "1000"]
    y-axis "Minutes" 0 --> 12
    bar [0.3, 0.5, 1, 2.5, 5, 10]
Loading

Without an API key — 6.0s per component

xychart-beta
    title "Without API key (6.0s per component)"
    x-axis ["25", "50", "100", "250", "500", "1000"]
    y-axis "Minutes" 0 --> 105
    bar [2.5, 5, 10, 25, 50, 100]
Loading
Each chart is scaled to its own range. The gap widens with inventory size, which is why an API key matters most on machines with large dependency trees.

Interface

Dashboard CVE Details
Live risk posture, vulnerable-component list, recent scan activity Every CVE with CVSS score, severity, description, and the affected component
Reports Settings
Executive, technical, and compliance summaries with severity filtering API key, scheduler, scan scope, external components, SMTP alerts

UTD CVE Manager dashboard


Supported SBOM Formats

Drop an SBOM from any common tool onto the SBOM Import page. The format is detected from the file contents, not the extension, so a renamed or extension-less file still imports correctly.

flowchart TD
    I["📥 Uploaded File"] --> S["Detect compression, then decode as text"]
    S --> F{"First character"}

    F -->|brace or bracket| J["Parse JSON"]
    F -->|angle bracket| X["Parse XML<br/>defusedxml"]
    F -->|SPDXVersion header| T["SPDX tag-value"]
    F -->|anything else| Y["YAML, then CSV/TSV"]

    J --> D{"Discriminator"}
    D -->|bomFormat=CycloneDX| C1["CycloneDX JSON"]
    D -->|spdxVersion| C2["SPDX JSON"]
    D -->|unrecognised| C3["Generic JSON"]

    X --> DX{"Root element"}
    DX -->|rdf:RDF| C4["SPDX RDF/XML"]
    DX -->|cyclonedx.org| C5["CycloneDX XML"]
    DX -->|SoftwareIdentity| C6["SWID Tag"]
    DX -->|other| C7["Generic XML"]

    T --> N["Normalized SbomComponent"]
    Y --> N
    C1 --> N
    C2 --> N
    C3 --> N
    C4 --> N
    C5 --> N
    C6 --> N
    C7 --> N

    N --> DB["💾 Component Database"]

    style I fill:#00d4ff,stroke:#fff,color:#0a0a0a
    style S fill:#00d4ff,stroke:#fff,color:#0a0a0a
    style N fill:#2ECC71,stroke:#fff,color:#0a0a0a
    style DB fill:#FF6B35,stroke:#fff,color:#0a0a0a
    style F fill:#16213e,stroke:#00d4ff,color:#fff
    style D fill:#16213e,stroke:#00d4ff,color:#fff
    style DX fill:#16213e,stroke:#00d4ff,color:#fff
Loading
Format Serializations Notes
SPDX 2.x .json · .yaml · tag-value (.spdx) · RDF/XML (.rdf) externalRefs purl and CPE are extracted
CycloneDX 1.x .json · .xml Nested sub-components and the metadata component are both read
SWID tags ISO/IEC 19770-2 .xml Identity read from SoftwareIdentity attributes
Generic .json · .yaml · .xml · .csv · .tsv Vendor schemas matched through field-alias tables
Compressed .gz · zlib Detected by magic bytes, not extension

Real-world vendor output works too. Microsoft sbom.json, GitHub, Syft, and Grype all import without configuration. Field names are matched case-insensitively through an alias table, so versionInfo, packageVersion, artifactVersion, and version are all accepted.

Tolerance for broken files. Generators routinely emit unquoted YAML values containing colons (supplier: Organization: Pallets), which makes strict parsers reject the entire document. A line-based fallback recovers the name and version pair rather than losing the file.

xychart-beta
    title "SBOM format coverage by component count"
    x-axis ["SPDX JSON", "SPDX YAML", "SPDX tag-value", "SPDX RDF", "CycloneDX JSON", "CycloneDX XML", "SWID", "Generic JSON", "Generic YAML", "Generic XML", "CSV/TSV"]
    y-axis "Components parsed" 0 --> 8
    bar [4, 3, 4, 3, 7, 4, 1, 3, 3, 3, 7]
Loading
Components recovered from each bundled test fixture in tests/samples/.

What happens on import

  1. Drop one or more files on the SBOM Import page, or click to browse.
  2. The document is parsed and previewed with its detected format, spec version, and component table.
  3. Confirm the import. Components are stored and then scanned by Scan System through the same code path as registry-discovered software — there is no second scanning path.
  4. Re-importing the same file is idempotent. Already-present components are counted, not duplicated.
  5. Import history records every upload. Deleting an import rolls back only the components it added.

Options

  • Only import components with a version — on by default, because a component without a version cannot be matched to a CVE. Uncheck it to keep versionless entries anyway.
  • Replace components from a previous import — rolls back an earlier SBOM before applying a newer one, which is how you keep a component list in sync across CI runs.

How a Scan Works

sequenceDiagram
    autonumber
    participant U as Browser
    participant A as Flask (app.py)
    participant S as Scanner Thread
    participant D as SQLite
    participant N as NVD API

    U->>A: POST /api/start-scan
    A->>S: spawn daemon thread
    A-->>U: {"success": true}
    A->>D: init schema

    rect rgb(22, 33, 62)
        Note over S,N: Per-component loop
        loop each component
            S->>N: GET /cves?virtualMatchString=CPE
            N-->>S: CVE list or 404
            S->>D: upsert component + insert CVEs
            S->>S: sleep(rate_limit_delay)
        end
    end

    S->>D: record scan_history
    S-->>U: scan_status → running=false

    loop every 1s
        U->>A: GET /api/scan-status
        A-->>U: progress, current component, ETA
    end
Loading

The scan runs in a daemon thread, so the UI stays responsive and you can navigate between pages mid-scan. The progress bar polls once per second and shows throughput-based time remaining, not just a percentage. Pause, resume, and stop are respected at the top of every loop iteration.

Rate limiting is enforced between components, never during, so pausing genuinely stops the clock.


Architecture

flowchart TB
    subgraph FE["Frontend — vanilla JS, no build step"]
        direction LR
        PAGES["5 pages<br/>dashboard · cve-details<br/>reports · settings · sbom"]
        FJS["main.js — scan control, ETA<br/>sbom.js — upload, history"]
    end

    subgraph BE["Backend — Flask"]
        direction LR
        RT["Page routes"]
        API["JSON API<br/>/api/*"]
        SCAN["Scan engine<br/>run_scan · scheduler"]
        MAIL["Notifications<br/>SMTP · HTML alerts"]
    end

    subgraph CORE["core/"]
        direction LR
        SB["sbom/<br/>detect · parse · import"]
        CA["app_scanner.py<br/>registry · dpkg · rpm"]
        CS["cve_scanner.py<br/>CPE build · CVSS"]
        CD["database.py<br/>schema · queries"]
        CE["encryption.py<br/>credentials"]
    end

    DB[("cve_database.db<br/>SQLite")]
    NVD["NIST NVD API 2.0"]

    PAGES --> RT
    FJS --> API
    RT --> CD
    API --> SB
    API --> MAIL
    SCAN --> CA
    SCAN --> CS
    SCAN --> CD
    SCAN --> MAIL
    MAIL --> CD
    SB --> CD
    CS --> NVD
    CD --> DB
    CE -.-> DB

    style FE fill:#16213e,stroke:#00d4ff,color:#fff
    style BE fill:#1a1a2e,stroke:#00d4ff,color:#fff
    style CORE fill:#16213e,stroke:#8A2BE2,color:#fff
    style DB fill:#00d4ff,stroke:#fff,color:#0a0a0a
    style NVD fill:#FF6B35,stroke:#fff,color:#0a0a0a
Loading

Data model

Table Purpose
applications Discovered inventory with version, publisher, scan state, CVE count, highest severity
cves Per-component vulnerability records with CVSS score and description
external_apps Manually entered and SBOM-imported components, with purl and cpe
sbom_imports Import history: filename, detected format, spec version, per-file counts
scan_history Audit trail of every scan with duration and totals
settings Configuration, with credentials stored separately from plain values

Schema changes are additive only. New columns are applied with ALTER TABLE ... ADD COLUMN guarded by a PRAGMA table_info check, so an existing database upgrades in place on startup and no scan data is lost.


Features

Discovery

  • Windows registry enumeration (32/64-bit views)
  • macOS and Linux system component detection
  • OS, BIOS, firmware, and web server tagging
  • Multiple versions of the same product tracked separately
  • Manual components via CSV or form
  • SBOM import — 11 formats plus gzip

Scanning

  • NVD API 2.0 with virtualMatchString matching
  • CVSS v2.0, v3.0, v3.1 and v4.0 base metrics
  • CVSS v3.1 temporal and v4.0 threat assessment
  • EPSS exploit-prediction scoring
  • Composite CVSS × EPSS risk ranking
  • API key detection with automatic rate-limit switching
  • Background scanning with pause, resume, and stop
  • Live progress with throughput-based time remaining
  • Scheduled scans from 2 minutes to quarterly

Analysis

  • Live dashboard with risk statistics
  • CVE detail view with search, filter, and pagination
  • Sort by risk, severity, or exploit likelihood
  • Executive, technical, and compliance reports
  • CSV and JSON export alongside print-to-PDF
  • Explicit coverage and data-quality statements in every report
  • Print-optimised document output

Notifications

  • Per-component HTML email alerts
  • Bundles all critical, high, and medium CVEs into one message
  • Top-three CVEs highlighted with counts for the rest
  • TLS, SSL, and plaintext SMTP
  • Multiple recipients, up to five
  • Connection test and sample-alert test
  • Notifications off by default

Privacy

Everything stays on your machine.

  • Component inventory lives in a local SQLite file. Nothing is uploaded.
  • The only outbound request is a CVE lookup against services.nvd.nist.gov.
  • No telemetry, no analytics, no account, no cloud service.
  • The API key and SMTP password are the only stored secrets.

Note on credential storage. Credentials are obfuscated with a keyed cipher before being written to the database. This stops casual disclosure — a screenshot, a shared screen, a committed file — but it is not a substitute for a system keychain or a secrets manager, because the key ships with the application. Treat the database as a secret.


Configuration

All settings live in the Settings page and persist across restarts.

Setting Default Purpose
NVD API key empty Unlocks the faster rate limit
Scheduled scan off Interval from 2 minutes to quarterly
Scan system components on Enumerate installed software
Scan external components on Include manual and SBOM components
Email notifications off Per-component critical/high/medium alerts
Debug mode off Verbose scan logging to the console

Tech Stack

Layer Choice Why
Web framework Flask 2.3 Small, dependency-light, no build step
Database SQLite via sqlite3 Single file, no server, transactional
HTTP client requests Retries, timeouts, session reuse
SBOM parsing Hand-written, PyYAML + defusedxml 11 formats, tolerant of vendor quirks
Frontend Vanilla JS, no framework Nothing to install, nothing to build
Styling Hand-written CSS, Font Awesome Consistent EDR dark theme
Colour output colorama Cross-platform terminal colours

Runtime dependencies are four packages. There is no bundler, no transpiler, and no node_modules.


Testing

python tests/test_sbom_parsers.py    # 19 tests: format detection and parser coverage
python tests/test_sbom_endpoints.py   # 18 tests: HTTP endpoints and scan handoff
python tests/test_cpe_epss.py         # 37 tests: CPE derivation, CVSS metrics, risk scoring

All three suites run against throwaway databases in a temporary directory and leave no files in the repository.

Suite Tests Focus
test_sbom_parsers.py 19 Detection, every format, error handling, dedupe, persistence
test_sbom_endpoints.py 18 Page render, all endpoints, failure modes, scan integration
test_cpe_epss.py 37 CPE grammar, vendor derivation, CVSS v2/3.0/3.1/4.0 metrics, risk ranking

The base-score implementation is the specification formula rather than a lookup table, and is verified against published NVD scores for known CVEs. CPE tests assert the full 13-component CPE 2.3 grammar and assert that no field contains a character the NVD cannot match against — the regression that made 55 components silently report clean.


Risk Scoring

CVSS answers how bad is this if exploited. EPSS answers how likely is it to be exploited. They are different questions, and a professional view needs both.

flowchart LR
    N["NVD API 2.0"] --> B["CVSS base score<br/>v2.0 · v3.0 · v3.1 · v4.0"]
    F["FIRST EPSS"] --> P["Exploit probability<br/>+ percentile"]
    A["Analyst assessment"] --> T["CVSS v3.1 temporal<br/>E · RL · RC"]
    A --> T4["CVSS v4.0 threat<br/>E · T"]

    B --> C{"Composite<br/>risk 0-100"}
    P --> C
    T --> C
    T4 --> C

    C --> O["Prioritised remediation plan"]

    style N fill:#FF6B35,stroke:#fff,color:#fff
    style F fill:#8A2BE2,stroke:#fff,color:#fff
    style A fill:#ffa502,stroke:#fff,color:#0a0a0a
    style B fill:#00d4ff,stroke:#fff,color:#0a0a0a
    style P fill:#00d4ff,stroke:#fff,color:#0a0a0a
    style T fill:#2ECC71,stroke:#fff,color:#0a0a0a
    style T4 fill:#2ECC71,stroke:#fff,color:#0a0a0a
    style C fill:#ff4757,stroke:#fff,color:#fff
    style O fill:#16213e,stroke:#00d4ff,color:#fff
Loading

EPSS

Fetched from FIRST after a scan, in batched requests, and cached for 24 hours. A finding whose EPSS is at or above 10% — the "likely to be exploited in the next 30 days" threshold — is called out separately from its severity band, because an actively exploited medium outranks an unexploited critical in every practical sense.

The composite risk score scales severity by exploit likelihood:

EPSS 0% EPSS 50% EPSS 95%
CVSS 9.8 66.7 83.3 98.0
CVSS 5.5 37.4 46.8 55.1
CVSS 3.0 20.4 25.5 30.0

That is the point: a 5.5 with 95% exploitation likelihood ranks above a 9.8 that nobody is exploiting.

CVSS temporal and threat metrics

The NVD publishes only base metrics. Temporal and environmental metrics are an assessment of how exploitable a finding is in your environment, so they are recorded per finding rather than read from the feed. Both sets are implemented:

Metric group Metrics
CVSS v3.0 / v3.1 Temporal Exploit Code Maturity, Remediation Level, Report Confidence
CVSS v4.0 Threat Exploit Maturity, Threat

Metrics left undefined take the value the specification names as "Not Defined", which carries a multiplier of 1.0. An unassessed finding therefore keeps its base score rather than being silently inflated or deflated.

Environmental metrics are deliberately not implemented. In both v3.1 and v4.0 the environmental score is not a multiplier on the base score — it recomputes the whole sub-score from modified confidentiality, integrity and availability values. Producing a plausible-looking number without the full base vector would risk a wrong severity in a compliance report, so the call raises an explicit error instead.


Matching Accuracy

A CPE built naively as cpe:2.3:a:*:<name>:<version> fails silently on some inputs. Measured against a real 126-component inventory, all 55 scoped npm packages returned zero CVEs while every unscoped package matched:

Input Old CPE New CPE
@radix-ui/react-accordion cpe:2.3:a:*:@radix-ui/react-accordion:1.2.3:* cpe:2.3:a:radix-ui:react-accordion:1.2.3:*
@aws-sdk/client-s3 cpe:2.3:a:*:@aws-sdk/client-s3:3.1003.0:* cpe:2.3:a:aws-sdk:client-s3:3.1003.0:*
Google Chrome cpe:2.3:a:*:google_chrome:120.0.6099.109:* cpe:2.3:a:google:chrome:120.0.6099.109:*
org.apache.logging.log4j cpe:2.3:a:*:org.apache.logging.log4j:2.14.1:* cpe:2.3:a:org.apache:log4j:2.14.1:*

@ and / are not legal in a CPE name field, so the NVD could not resolve those queries at all. The components were reported clean, which is the worst possible failure mode: a false negative.

CPEs are now derived in order of preference:

  1. a CPE recorded in the SBOM, used verbatim
  2. a Package URL — pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1 yields both vendor and product
  3. the component name, with npm scopes, Maven groups and dotted Java packages handled

If the first query returns nothing, up to two progressively looser fallbacks are tried, including a wildcard-vendor variant. A curated table covers products the registry names differently from NVD (Adobe Acrobat Reader DC → adobe:acrobat_reader).


Reports

Three views, generated from the same data:

  • Executive Summary — posture headline, severity distribution, exploit likelihood, prioritised remediation plan
  • Technical Detail — every finding with its CVSS vector, version, assessment, full description
  • Compliance Audit — complete inventory table plus explicit coverage statements

Every report states its own limitations. If a scan is only partway through, or EPSS coverage is partial, or a filter is active, the report says so at the top rather than presenting a filtered count as if it were the whole picture.

Export to CSV (RFC 4180 quoting) or JSON for downstream tooling, or print to PDF with the application chrome stripped and the document reflowed for paper.


  • Vendor-aware CPE derivation — currently every component is matched as cpe:2.3:a:*:* with a * vendor, which NVD does not resolve reliably. Scoped npm names such as @scope/package also contain characters that are invalid in a CPE.
  • OSV.dev as a second source — far better coverage for open-source dependencies, with no API key and no rate limit.
  • Scan diffing — show only what changed between two runs, so recurring scans surface new vulnerabilities instead of re-reporting known ones.
  • Multi-host fleet mode — aggregate several machines into one dashboard.
  • Additional report export formats — CSV and SARIF for SIEM ingestion.

Roadmap

  • CVSS environmental metrics — the v3.1 Environmental and v4.0 Vuln Reactionability groups. Requires the full base vector plus the modified C/I/A requirements; deliberately left unimplemented rather than approximated.
  • Scan diffing — show only what changed between two runs, so a recurring scan surfaces new vulnerabilities instead of re-reporting known ones.
  • Fleet mode — aggregate several machines into one dashboard.
  • Additional export formats — SARIF and CycloneDX VEX for SIEM ingestion.
  • Reusable threat assessments — save a temporal or threat profile and re-apply it on a schedule, so an assessment does not have to be re-entered per finding.

Contributing

Contributions are welcome.

  1. Fork the repository
  2. Create a feature branch
  3. Make your change, adding tests where behaviour is observable
  4. Run both test suites
  5. Open a pull request

Guidelines

  • Schema changes must stay additive so existing databases upgrade in place
  • New SBOM formats need a fixture in tests/samples/ and an entry in the format table
  • Keep runtime dependencies minimal; the project installs with four packages
  • Match the existing EDR dark theme for anything user-facing

License

Released under the MIT License.

Vulnerability data is provided by the NIST National Vulnerability Database, which is in the public domain.


UTD CVE Manager — keeping your systems secure, one vulnerability at a time.

Developed by iampopg

About

Up-To-Date CVE Manager: Enterprise-grade vulnerability management system with real-time CVE scanning, cross-platform software discovery, and professional EDR-style dashboard. Automatically identifies security vulnerabilities in installed software using NIST NVD API with encrypted credential storage

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages