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.
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
pip install .Or install in development mode:
pip install -e ".[dev]"Requires Python 3.9 or later.
cve-watchdog initThis creates a watchlist.yaml with example embedded components. Edit it to match your product.
cve-watchdog watchcve-watchdog watch --check-epss --check-kev --check-exploitscve-watchdog watch --daemon --daemon-interval 4cve-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
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| 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 |
| 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 |
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)
Generate SARIF 2.1.0 output compatible with GitHub Code Scanning, GitLab SAST, and other SARIF consumers:
cve-watchdog watch --format sarif > results.sarifUpload 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.sarifEach 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.
Filter scan results using a VEX (Vulnerability Exploitability eXchange) document. Supports OpenVEX and CSAF VEX formats:
cve-watchdog watch --vex product-vex.jsonCVEs 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"}]
}
]
}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.15Weights should sum to 1.0. If they don't, a warning is logged but scoring proceeds.
Run scans using only cached NVD and KEV data without network access:
cve-watchdog watch --offlineThis 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.
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: busyboxUsage: cve-watchdog watch --suppress suppress.yaml
See suppress_example.yaml for a full example.
Findings are routed to notification channels based on severity:
| Severity | Default channels |
|---|---|
| CRITICAL | PagerDuty + Slack |
| HIGH | Slack + Teams |
| MEDIUM | |
| LOW | Logged only |
Routing is fully configurable in the YAML settings.
- 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
Run continuously with state tracking:
cve-watchdog watch --daemon --daemon-interval 4 --check-epss --check-kevThe 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
Import components directly from an SBOM:
cve-watchdog watch --sbom firmware-sbom.cdx.json --check-epss --check-kevSupported formats: CycloneDX JSON (>=1.4), SPDX JSON (>=2.3).
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.sarifjobs:
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.sarifThe 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.
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
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).
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.
- 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?"
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 VulnerabilitiesMIT. Copyright (c) 2026 isecwire GmbH.