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.
- Polls the GitHub API for new commits to CVEProject/cvelistV5
- Compares the previous and current commit SHAs to get changed CVE files (GitHub compare API, fast path)
- If the compare file list is capped at 300, falls back to paginating the commits API
- Fetches changed CVE JSON files concurrently (configurable worker pool)
- Matches each CVE against your monitor rules (vendor/product/version)
- Sends matching CVEs to configured notifiers and prints to STDOUT
Images are published to GitHub Container Registry on every tagged release:
docker pull ghcr.io/samjuk/cve-watcher:latestDockerHub 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:latestRun 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 -oneshotRun 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 -keepMount 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.
Download the latest binary for your platform from Releases.
Requires Go 1.22+.
git clone https://github.com/SamJUK/cve-watcher
cd cve-watcher
make build
./cve-watcher watch -config config.yamlmake docker-build # builds cve-watcher:<version>
make docker-run # build + run with local config.yamlCopy 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 only2.4.6-p3 - Prefix:
"8.2"matches8.2.0,8.2.5, etc. - Semver range: matches CVE
lessThan/lessThanOrEqualrange fields numerically
Omit versions to match all affected versions of a product.
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.targetClones 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.
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.
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).
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.yamlcmd/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