Skip to content

open-inc/cve-scanner

Repository files navigation

openinc-cve-scanner

Monitors a list of container images via Docker Scout and sends a Telegram alert when new CVEs appear.

The scanner queries image manifests directly from the registry — it does not need access to a Docker daemon and does not mount the host docker socket.

How it works

On each scan cycle, for every monitored image:

  1. docker scout cves <image> --format sarif is executed against the registry reference.
  2. The returned CVE IDs are diffed against the last known baseline persisted in state.json.
  3. If new CVEs are detected, a single Telegram message is sent summarizing them (sorted by CVSS, capped at OI_MAX_CVES_PER_MESSAGE).
  4. The current CVE list becomes the new baseline.

The first time an image is seen, every CVE counts as "new" and is reported as an initial scan.

Quick start

  1. Create a .env next to docker-compose.yml:

    OI_MONITOR_BOT_TOKEN=123456:ABC...
    OI_MONITOR_CHAT_ID=-1001234567890
    
    # Optional: registry credentials for private repos.
    OI_REGISTRY=
    OI_REGISTRY_USER=your-dockerhub-user
    OI_REGISTRY_PASSWORD=dckr_pat_...
  2. Adjust OI_MONITORED_IMAGES in docker-compose.yml to the images you want to track.

  3. Start it:

    docker compose up -d
    docker compose logs -f cve-scanner

State persists in ./data/state.json (mounted as /data inside the container).

Configuration

All configuration is via environment variables.

Variable Required Default Description
OI_MONITOR_BOT_TOKEN yes Telegram bot token.
OI_MONITOR_CHAT_ID yes Telegram chat ID to send alerts to.
OI_MONITORED_IMAGES yes Comma- or whitespace-separated list of image refs (e.g. repo/img:tag).
OI_SCAN_INTERVAL no unset Loop interval in seconds. If unset, the scanner runs once and exits — useful for cron / systemd timers.
OI_MONITOR_INTERVAL_SECONDS no unset Per-image rate limit. If an image was scanned more recently than this, it's skipped on the current cycle. Unset = scan every loop.
OI_MAX_CVES_PER_MESSAGE no 30 Cap on CVEs listed in a single Telegram message. Excess are summarized as "…and N more".
OI_REGISTRY no "" (Docker Hub) Registry to authenticate against. Set to e.g. ghcr.io or registry.example.com:5000 for non-Hub registries.
OI_REGISTRY_USER no Registry username. Login is skipped entirely if user or password is missing (only public images can be scanned in that case).
OI_REGISTRY_PASSWORD no Registry password / personal access token.
OI_STATE_FILE no /data/state.json Path where CVE baselines are persisted.
DOCKER_SCOUT_HUB_USER no Optional: authenticate scout itself to Docker Hub for advisory lookups. Recommended for production to avoid anonymous rate limits.
DOCKER_SCOUT_HUB_PASSWORD no Companion to DOCKER_SCOUT_HUB_USER.

Deployment notes

  • No docker socket required. Scout reads SBOM/metadata directly from the registry, so this container can run anywhere with HTTPS egress to the registry and api.telegram.org.
  • Private images. Without OI_REGISTRY_USER / OI_REGISTRY_PASSWORD, the scanner only authenticates anonymously and will fail to scan private repositories. The login step writes credentials only to the container's ~/.docker/config.json — the host's docker config is not used.
  • State. The data/ directory in the working tree is bind-mounted at /data. It contains state.json, the CVE baseline per image. Deleting it triggers a fresh "initial scan" alert for every image on the next run.
  • Cron mode. Unset OI_SCAN_INTERVAL and run the container from a host-level scheduler (cron, systemd timer, k8s CronJob, etc.) for a single-shot scan-and-exit.

Telegram alerts

The bot must already be a member of the target chat. Get the chat ID by sending a message in the chat, then querying https://api.telegram.org/bot<TOKEN>/getUpdates and reading result[].message.chat.id. Group/channel IDs are negative.

Messages use Markdown formatting and disable link previews. CVE entries are bucketed by CVSS:

  • 🔴 critical (≥ 9.0)
  • 🟠 high (≥ 7.0)
  • 🟡 medium (≥ 4.0)
  • 🟢 low (> 0)
  • ⚪ unknown / no score

Local development

node --check src/scanner.js   # syntax check
npm install                   # no runtime deps; just sets up package-lock if added
node src/scanner.js           # one-shot scan against env vars

The scanner shells out to docker scout, so a local install of the Docker CLI plus the scout plugin is required for non-containerized runs.

CI / CD

.github/workflows/ci.yml runs syntax checks and npm audit on every push and PR. On pushes to master, it builds the image, scans it with Trivy, and pushes openinc/cve-scanner:<date> and :latest to Docker Hub if the scan passes. CVE suppressions for vendored Go libraries inside the docker scout binary are documented in .trivyignore.

About

Scans for CVE in listed containers and sends alerts via telegram

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors