Skip to content

Repository files navigation

cve-watchdog

CI License: MIT Python

Advanced CVE monitoring for embedded system components. Queries NVD, OSV.dev, and GitHub Advisory Database for vulnerabilities, enriches findings with EPSS exploit probability scores and CISA KEV status, computes composite risk scores, and delivers multi-channel notifications.

Supports SARIF output for GitHub Code Scanning integration and VEX (Vulnerability Exploitability eXchange) import for filtering not_affected/fixed CVEs.

Built for teams shipping embedded Linux products who need continuous visibility into supply-chain vulnerabilities across firmware components like BusyBox, OpenSSL, U-Boot, the Linux kernel, and everything in between.

cve-watchdog terminal output showing risk-scored CVE findings

Why

The EU Cyber Resilience Act (CRA) and IEC 62443 both require manufacturers to monitor known vulnerabilities in shipped products throughout their lifecycle. For embedded systems with dozens of third-party components, this quickly becomes unmanageable without automation.

cve-watchdog solves this by:

  • Querying NVD, OSV.dev, and GitHub Advisory Database against a YAML component list or SBOM
  • Enriching findings with EPSS exploit probability and CISA KEV status
  • Computing composite risk scores (0-100) from CVSS + EPSS + KEV + exploit availability
  • Evaluating NVD version ranges (versionStartIncluding, versionEndExcluding)
  • Detecting transitive dependency risks
  • Running in daemon mode with state tracking (only alerts on new findings)
  • Generating reports in table, JSON, CSV, Markdown, HTML, and SARIF formats
  • Producing SARIF output for GitHub Code Scanning / GitLab SAST integration
  • Importing VEX documents (OpenVEX, CSAF) to filter not_affected/fixed CVEs
  • Running in offline mode with cached NVD/KEV data
  • Custom risk scoring weights via config
  • Sending alerts via Slack, Microsoft Teams, email, PagerDuty with severity routing
  • Supporting suppression rules with expiry dates for accepted risks

Installation

pip install .

Or install in development mode:

pip install -e ".[dev]"

Requires Python 3.9 or later.

Quick start

1. Create a watchlist

cve-watchdog init

This creates a watchlist.yaml with example embedded components. Edit it to match your product.

2. Run a scan

cve-watchdog watch

3. Run with full intelligence enrichment

cve-watchdog watch --check-epss --check-kev --check-exploits

4. Run in daemon mode

cve-watchdog watch --daemon --daemon-interval 4

CLI reference

cve-watchdog watch [OPTIONS]

Options:
  -c, --config PATH          Path to watchlist YAML (default: watchlist.yaml)
  --sbom PATH                Import from CycloneDX/SPDX JSON SBOM
  --threshold FLOAT          Override CVSS threshold
  --json PATH                Override JSON report output path
  --check-epss               Enable EPSS exploit probability scoring
  --check-kev                Enable CISA KEV catalog checking
  --check-exploits           Check for known public exploits
  --suppress PATH            Path to suppression rules YAML
  --format {table,json,csv,markdown,html,sarif}
                             Output format (default: table)
  --sort-by {risk,cvss,epss,date,component}
                             Sort results by field (default: risk)
  --group-by {ecosystem,severity,risk_score}
                             Group results by field in output
  --vex FILE                 VEX document (OpenVEX/CSAF) to filter findings
  --offline                  Use cached NVD/KEV data only, no network
  --since DATE               Only show CVEs published after date (YYYY-MM-DD)
  --daemon                   Run continuously, scanning every N hours
  --daemon-interval HOURS    Override daemon scan interval in hours
  -v, --verbose              Enable debug logging

Configuration

The watchlist YAML defines your components, scan settings, and notification channels:

settings:
  cvss_threshold: 7.0
  sources:
    - nvd
    - osv
    - ghsa
  report_path: ./reports/latest.json

  # NVD API key for higher rate limits (50 req/30s vs 5 req/30s).
  # nvd_api_key: "your-key-here"

  # Intelligence enrichment.
  check_epss: true
  check_kev: true
  check_exploits: false

  # Daemon mode.
  daemon_interval_hours: 6.0
  state_file: .cve-watchdog-state.json

  # Suppression rules file.
  # suppress_file: suppress.yaml

  # Notification channels.
  # notifications:
  #   slack_webhook: https://hooks.slack.com/services/...
  #   teams_webhook: https://outlook.office.com/webhook/...
  #   pagerduty_routing_key: "your-routing-key"
  #   email_smtp_host: smtp.example.com
  #   email_to: [security-team@example.com]
  #   routing:
  #     CRITICAL: [pagerduty, slack]
  #     HIGH: [slack, teams]
  #     MEDIUM: [email]
  #     LOW: []

components:
  - name: openssl
    version: "3.2.1"
    ecosystem: linux
    cpe: "cpe:2.3:a:openssl:openssl:3.2.1:*:*:*:*:*:*:*"

  - name: curl
    version: "8.6.0"
    ecosystem: linux
    depends_on:
      - openssl
      - zlib

Component fields

Field Required Description
name Yes Package name (used for keyword/OSV/GHSA queries)
version Yes Deployed version
ecosystem No OSV ecosystem (default: linux)
cpe No CPE 2.3 string for precise NVD matching
depends_on No List of dependency names for transitive analysis

Intelligence sources

Source Description Flag
NVD NIST National Vulnerability Database (default) in sources list
OSV OSV.dev vulnerability database (default) in sources list
GHSA GitHub Advisory Database in sources list
EPSS FIRST.org Exploit Prediction Scoring System --check-epss
CISA KEV Known Exploited Vulnerabilities catalog --check-kev
Exploit-DB Public exploit/PoC cross-reference --check-exploits

Risk scoring

The composite risk score (0-100) combines four signals:

Signal Weight Description
CVSS 40% Base vulnerability severity (0-10)
EPSS 25% Exploit probability in next 30 days (0-1)
CISA KEV 20% Active exploitation confirmed (bool)
Exploits 15% Public PoC/exploit available (bool)

Automatic boosts:

  • CVEs in CISA KEV are floored at risk score 70
  • CVEs with CVSS >= 9.0 AND public exploits are floored at 80

Risk labels: CRITICAL (>=80), HIGH (>=60), MEDIUM (>=35), LOW (>0)

SARIF output (GitHub Code Scanning)

Generate SARIF 2.1.0 output compatible with GitHub Code Scanning, GitLab SAST, and other SARIF consumers:

cve-watchdog watch --format sarif > results.sarif

Upload to GitHub Code Scanning:

# GitHub Actions
- name: CVE scan
  run: cve-watchdog watch --format sarif --json reports/cve.sarif
- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: reports/cve.sarif

Each CVE maps to a SARIF rule with severity level (error/warning/note), help URI pointing to the NVD advisory, and security-severity property for GitHub's severity classification.

VEX import

Filter scan results using a VEX (Vulnerability Exploitability eXchange) document. Supports OpenVEX and CSAF VEX formats:

cve-watchdog watch --vex product-vex.json

CVEs declared as not_affected or fixed in the VEX document are removed from the active findings and reported separately. This is useful for CRA/IEC 62443 compliance where you need to document why certain CVEs do not apply to your product.

Example OpenVEX document:

{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@type": "OpenVEX",
  "statements": [
    {
      "vulnerability": {"name": "CVE-2024-0001"},
      "status": "not_affected",
      "justification": "vulnerable_code_not_present",
      "products": [{"@id": "pkg:generic/our-firmware@2.1.0"}]
    }
  ]
}

Custom risk scoring weights

Override the default risk model weights in your watchlist YAML:

settings:
  risk_weights:
    cvss_weight: 0.35
    epss_weight: 0.30
    kev_weight: 0.20
    exploit_weight: 0.15

Weights should sum to 1.0. If they don't, a warning is logged but scoring proceeds.

Offline mode

Run scans using only cached NVD and KEV data without network access:

cve-watchdog watch --offline

This is useful in air-gapped environments or CI pipelines where network access is restricted. Make sure to populate the cache with a normal scan first.

Suppression rules

Suppress known false positives or accepted risks with a YAML file:

suppressions:
  - cve_id: CVE-2024-0001
    reason: "False positive; code path not compiled in our build."
    expires: "2026-07-01"

  - cve_id: CVE-2024-0002
    reason: "Accepted risk; mitigated by network segmentation."
    component: busybox

Usage: cve-watchdog watch --suppress suppress.yaml

See suppress_example.yaml for a full example.

Notification channels

Severity routing

Findings are routed to notification channels based on severity:

Severity Default channels
CRITICAL PagerDuty + Slack
HIGH Slack + Teams
MEDIUM Email
LOW Logged only

Routing is fully configurable in the YAML settings.

Supported channels

  • Slack incoming webhooks with Block Kit formatting
  • Microsoft Teams webhook connectors
  • PagerDuty Events API v2 (individual events for CRITICAL, batch for others)
  • Email via SMTP with TLS support
  • Generic webhook for Discord or custom endpoints

Daemon mode

Run continuously with state tracking:

cve-watchdog watch --daemon --daemon-interval 4 --check-epss --check-kev

The daemon:

  • Scans every N hours (default 6)
  • Tracks which CVEs have been reported (state file)
  • Only sends notifications for new findings
  • Records scan history (last 100 runs)
  • Handles SIGINT/SIGTERM for graceful shutdown

SBOM integration

Import components directly from an SBOM:

cve-watchdog watch --sbom firmware-sbom.cdx.json --check-epss --check-kev

Supported formats: CycloneDX JSON (>=1.4), SPDX JSON (>=2.3).

CI/CD integration

GitLab CI

cve_scan:
  image: python:3.12-slim
  script:
    - pip install ./tools/cve-watchdog
    - cve-watchdog watch -c watchlist.yaml --check-epss --check-kev --format json --json reports/cve-report.json
  artifacts:
    paths:
      - reports/cve-report.json

cve_scan_sarif:
  image: python:3.12-slim
  script:
    - pip install ./tools/cve-watchdog
    - cve-watchdog watch -c watchlist.yaml --check-epss --check-kev --format sarif > gl-sast-report.sarif
  artifacts:
    reports:
      sast: gl-sast-report.sarif

GitHub Actions

jobs:
  cve-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install ./tools/cve-watchdog
      - run: |
          cve-watchdog watch -c watchlist.yaml \
            --check-epss --check-kev \
            --format sarif > results.sarif
      - uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

NVD API key

The NVD public API allows 5 requests per 30 seconds. With an API key (free), the limit increases to 50 requests per 30 seconds. Get one at nvd.nist.gov/developers/request-an-api-key.

Set via config (nvd_api_key), environment variable (NVD_API_KEY), or both.

Architecture

cve_watchdog/
  cli.py              CLI entry point with argparse
  config.py           YAML config loader with all settings
  matcher.py          CPE/version matching, transitive deps
  risk.py             Composite risk scoring model v2 (tunable weights)
  sarif.py            SARIF 2.1.0 output for GitHub Code Scanning
  vex_import.py       OpenVEX / CSAF VEX document import
  display.py          Rich terminal UI (tables, badges, dashboard)
  notifier.py         Multi-channel notifications (Slack, Teams, PD, email)
  daemon.py           Daemon loop with scheduling
  state.py            Persistent state tracking
  suppress.py         Suppression rule engine
  report.py           JSON/console report generation
  sbom_import.py      CycloneDX/SPDX SBOM parser
  sources/
    nvd.py            NVD API 2.0 client (with API key, batch support)
    osv.py            OSV.dev API client
    ghsa.py           GitHub Advisory Database client
    epss.py           FIRST.org EPSS API client
    kev.py            CISA KEV catalog checker
    exploitdb.py      Exploit-DB / PoC cross-reference

FAQ

What is a CVE?

CVE (Common Vulnerabilities and Exposures) is a public database of known security bugs. Every discovered vulnerability gets a unique ID, e.g., CVE-2024-3094 (the xz-utils backdoor). NVD (National Vulnerability Database) is the US government's database containing all CVEs with severity scores (CVSS, 0-10 scale).

Why do I need to monitor CVEs?

Your gateway uses OpenSSL 3.1. Someone discovers a critical bug in OpenSSL 3.1 → a CVE is published → you must know immediately to release a patch before attackers exploit it in your 4000 deployed gateways.

Who requires this?

  • EU Cyber Resilience Act (CRA) — manufacturers MUST monitor CVEs in their products
  • IEC 62443 — requires vulnerability management processes
  • NIS2 Directive — critical infrastructure operators must respond to known vulnerabilities
  • Enterprise clients — large energy/manufacturing companies ask "what's your vulnerability management process?"

How to use it?

cve-watchdog watch --config watchlist.yaml              # one-time scan
cve-watchdog watch --config watchlist.yaml --daemon      # continuous monitoring, alerts to Slack
cve-watchdog watch --config watchlist.yaml --check-kev   # flag CISA Known Exploited Vulnerabilities

License

MIT. Copyright (c) 2026 isecwire GmbH.

About

CVE monitor for embedded components — queries NVD/OSV/GHSA, enriches with EPSS+KEV scores, exports SARIF/VEX. Built for CRA & IEC 62443.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages