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.
On each scan cycle, for every monitored image:
docker scout cves <image> --format sarifis executed against the registry reference.- The returned CVE IDs are diffed against the last known baseline persisted in
state.json. - If new CVEs are detected, a single Telegram message is sent summarizing them (sorted by CVSS, capped at
OI_MAX_CVES_PER_MESSAGE). - 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.
-
Create a
.envnext 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_...
-
Adjust
OI_MONITORED_IMAGESin docker-compose.yml to the images you want to track. -
Start it:
docker compose up -d docker compose logs -f cve-scanner
State persists in ./data/state.json (mounted as /data inside the container).
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. |
- 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 containsstate.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_INTERVALand run the container from a host-level scheduler (cron, systemd timer, k8s CronJob, etc.) for a single-shot scan-and-exit.
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
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 varsThe scanner shells out to docker scout, so a local install of the Docker CLI plus the scout plugin is required for non-containerized runs.
.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.