Skip to content

SamJUK/cve-watcher

Repository files navigation

CVE Watcher

Monitors CVEProject/cvelistV5 for new and updated CVEs matching your vendor/product rules. Sends alerts to Discord, Slack, email, and STDOUT.

No git binary required for watch mode — uses the GitHub API and parallel HTTP fetches from raw.githubusercontent.com.

How it works

  1. Polls the GitHub API for new commits to CVEProject/cvelistV5
  2. Compares the previous and current commit SHAs to get changed CVE files (GitHub compare API, fast path)
  3. If the compare file list is capped at 300, falls back to paginating the commits API
  4. Fetches changed CVE JSON files concurrently (configurable worker pool)
  5. Matches each CVE against your monitor rules (vendor/product/version)
  6. Sends matching CVEs to configured notifiers and prints to STDOUT

Installation

Docker

Images are published to GitHub Container Registry on every tagged release:

docker pull ghcr.io/samjuk/cve-watcher:latest

DockerHub is also supported — configure DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets in your fork to publish there.

Run as daemon (default watch mode):

docker run -d \
  -v $(pwd)/config.yaml:/config.yaml:ro \
  -v $(pwd)/state.json:/state.json \
  --name cve-watcher \
  ghcr.io/samjuk/cve-watcher:latest

Run oneshot (e.g. from a CronJob or external scheduler):

docker run --rm \
  -v $(pwd)/config.yaml:/config.yaml:ro \
  -v $(pwd)/state.json:/state.json \
  ghcr.io/samjuk/cve-watcher:latest \
  watch -oneshot

Run a one-off scan:

docker run --rm \
  -v $(pwd)/config.yaml:/config.yaml:ro \
  -v $(pwd)/cve-scan:/cve-scan \
  ghcr.io/samjuk/cve-watcher:latest \
  scan -config /config.yaml -dir /cve-scan -keep

Mount a host directory to -dir if you want to reuse the cloned repo across runs. Without -keep the clone is discarded when the container exits.

Pre-built binaries

Download the latest binary for your platform from Releases.

Build from source

Requires Go 1.22+.

git clone https://github.com/SamJUK/cve-watcher
cd cve-watcher
make build
./cve-watcher watch -config config.yaml

Build Docker image locally

make docker-build          # builds cve-watcher:<version>
make docker-run            # build + run with local config.yaml

Configuration

Copy config.yaml.example to config.yaml and edit:

discord_webhook_url: "https://discord.com/api/webhooks/..."
slack_webhook_url: "https://hooks.slack.com/services/..."
github_token: ""        # optional; 5000 req/hr vs 60/hr unauthenticated
check_interval: 300     # seconds between polls (default: 300)
workers: 20             # concurrent CVE fetch goroutines (default: 20)

# smtp:
#   host: "localhost"
#   port: 25
#   from: "cve-watcher@example.com"
#   to:
#     - "security@example.com"

monitors:
  - vendor: "adobe"
    product: "adobe commerce"
    # versions: ["2.4.6"]  # omit to match all versions

  - vendor: "f5"
    product: "nginx"

  - vendor: "elastic"
    product: "elasticsearch"
    versions: ["8.12", "8.11"]

All notifiers are optional — omit any section to disable it. STDOUT is always active.

Vendor/product matching is case-insensitive and substring-based in both directions — "nginx" matches "NGINX Open Source", "adobe" matches "Adobe Commerce".

Version matching (when versions is set):

  • Exact: "2.4.6-p3" matches only 2.4.6-p3
  • Prefix: "8.2" matches 8.2.0, 8.2.5, etc.
  • Semver range: matches CVE lessThan/lessThanOrEqual range fields numerically

Omit versions to match all affected versions of a product.

Commands

watch (default)

Polls for new CVEs and sends alerts.

./cve-watcher watch -config config.yaml -state state.json
# or (backward-compatible):
./cve-watcher -config config.yaml -state state.json
Flag Default Description
-config config.yaml Path to configuration file
-state state.json Path to state file (stores last processed commit SHA)
-oneshot false Poll once and exit instead of looping

On first run, the watcher records the current HEAD as its baseline and exits without alerting. Subsequent runs detect new commits and process only the changed CVE files.

Daemon mode (default): loops indefinitely, sleeping check_interval seconds between polls. Suits long-running containers or systemd services.

Oneshot mode (-oneshot): polls once and exits with code 0. No sleep, no loop. Recommended for external schedulers — cron, systemd timers, Kubernetes CronJobs — where the scheduler owns the interval.

# cron — every 5 minutes
*/5 * * * * /usr/local/bin/cve-watcher watch -oneshot -config /etc/cve-watcher/config.yaml -state /var/lib/cve-watcher/state.json
# systemd timer
[Unit]
Description=CVE Watcher

[Service]
Type=oneshot
ExecStart=/usr/local/bin/cve-watcher watch -oneshot -config /etc/cve-watcher/config.yaml -state /var/lib/cve-watcher/state.json
[Unit]
Description=CVE Watcher timer

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min

[Install]
WantedBy=timers.target

scan

Clones the entire CVEProject/cvelistV5 repo and scans all CVEs against your config. Useful for validating monitor rules before deploying.

./cve-watcher scan -config config.yaml
./cve-watcher scan -config config.yaml -dir /tmp/cve-scan -keep
Flag Default Description
-config config.yaml Path to configuration file
-dir temp dir Directory to clone repo into
-keep false Keep cloned repo after scan (reuses it on next run)
-notify false Send matches to configured notifiers (default: STDOUT only)

Requires git to be installed.

Alert format

STDOUT:

[CVE-WATCHER] MATCH: CVE-2024-20719 | Adobe/Adobe Commerce | HIGH (8.1) | https://www.cve.org/CVERecord?id=CVE-2024-20719
  Description: Adobe Commerce versions 2.4.6-p3 and earlier are affected by...

Discord / Slack: Rich embed/attachment with severity colour, vendor/product, matched rule, and description.

Email: Plain-text message with CVE ID, vendor, severity, link, matched rule, and description.

GitHub token

Without a token the GitHub API allows 60 requests/hour. For production use with a 5-minute poll interval, set github_token to a personal access token (no scopes required — read-only public API).

Development

make test         # go test -race ./...
make cover        # test + open HTML coverage report
make vet          # go vet ./...
make build        # build binary to ./cve-watcher
make run          # build + run with config.yaml
make clean        # remove binary and coverage files
make docker-build # build Docker image
make docker-run   # build + run Docker image with local config.yaml

Project layout

cmd/cve-watcher/    entry point (main.go)
internal/
  alert/            notifier interface, Discord, Slack, SMTP, STDOUT
  config/           YAML config loading and Monitor rules
  cve/              CVE JSON parsing and vendor/product/version matching
  github/           GitHub API client (commits, compare, raw fetch)
  scanner/          full-repo clone, vendor pre-filter, disk-based scan
  state/            SHA state persistence (state.json)
Dockerfile
.github/workflows/
  build.yml         CI: vet + test + build + Docker smoke test on push/PR
  release.yml       Release: cross-compile binaries + Docker push to DockerHub/GHCR on tag

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages