Skip to content

Security CVE Watch

Security CVE Watch is an N8N workflow that scans the VPS Docker images daily for known vulnerabilities (CVE), deduplicates findings, and only fires an alert for new ones or severity escalations.

WorkflowIDNodesRole
CVE Watch (parent)5mHMlcg2HqEa7pWJ18Trigger + image loop + aggregation + notification
CVE Scanner (SW-1)Tz40sFr3CMrw7X816Runs trivy image over SSH on a single image
CVE Processor (SW-2)JCyc4Jkev8fQ1mwL9Dedup, classification, historical comparison

Data Tables

CVE Watch · 18 nodes

CVE Processor · 9 nodes

Lookup cve_notifications

Filter CRITICAL/HIGH

Insert/Update DT

CVE Scanner · 6 nodes

SSH Local VPS

docker exec trivy image <img>

Parse JSON output

Triggers

Cron daily 06:00

Cron weekly Monday 09:00

DIUN webhook · new image

Get Docker Images · docker images

Loop Over Images

Aggregate Findings

Notification Hub

cve_notifications · Tfng3e4kUL0Ya4Lm

cve_scan_history · elUlUt9HzqSSCr7L

About a dozen images of the VPS stacks are scanned: n8nio/n8n, odoo:18, caddy:2-alpine, postgres:16, postgres:17, redis:7-alpine, qdrant/qdrant, crowdsecurity/crowdsec, prom/prometheus, grafana/grafana. The list is derived from docker images --format at runtime, so any new stack is automatically picked up at the next scan.


CVEs published on Docker images don’t trigger anything automatically. A critical vulnerability on postgres:16 can sit unnoticed for weeks if no one explicitly checks the CVE feed or reads the registry changelog.

Symptom without CVE WatchCauseImpact
Vulnerability not detectedNo systematic scanExtended exploitation window
Repeated false positivesNo dedupNoise, alert desensitisation
Slow triggerManual monitoringNo retry mechanism
No traceabilityNo scan historyCan’t prove past state
CriterionTrivySnykDocker Scout
Open sourceYes (Apache 2.0)NoNo
Self-hostedYesPartialNo
CVE sourcesNVD, Red Hat, Alpine, Debian, UbuntuNVD, Snyk DBNVD, Snyk DB
Auth requiredNoToken + quotaDocker Hub login
Offline scanYes (local DB)NoNo
JSON formatStable, documentedStableStable

The Trivy choice aligns with the VPS self-hosted philosophy: no SaaS dependency, no API quota, no leak of the image list to a third party.

TriggerFrequencyUse case
Cron daily 06:00Every day before working hoursCatch CVEs published the previous day
Cron weekly Monday 09:00Once a weekWeekly recap with trends
DIUN webhookEvery image pullScan a new version immediately

The pairing with DIUN closes the loop: when DIUN detects a new image is available, the CVE scan fires before the Telegram approval, so an update introducing a security regression can be rejected.


1. Image inventory — The parent calls docker images --format '{{.Repository}}:{{.Tag}}' over local SSH to obtain the exact list of images loaded on the VPS at scan time.

2. Per-image loop — For each one, the parent calls the CVE Scanner sub-workflow via Execute Workflow.

3. Trivy scan — The Scanner SSHes into the VPS and runs docker exec trivy image --format json --severity CRITICAL,HIGH <image>. The trivy container is pre-installed and its CVE DB cache is mounted on a volume to avoid re-downloading 150 MB on every scan.

4. Result parsing — The Scanner parses the Trivy JSON and returns a normalised array {cve_id, severity, package, installed, fixed, title, cvss}.

5. Dedup and classification — The CVE Processor compares each finding with the cve_notifications table. Three cases:

CaseAction
Unknown CVEInsert with status=new, add to the notification list
Already-notified CVESkip (unless severity escalation)
CVE marked fixedVerify the fix is actually applied, otherwise re-open

6. Aggregation — The parent groups new findings by image and severity.

7. Notification — If at least one CRITICAL is present, the parent fires an immediate alert to the Notification Hub. Otherwise it waits for the weekly run for a digest.

ColumnTypeDescription
cve_idstringCVE-YYYY-NNNNN
imagestringrepo:tag
severitystringCRITICAL / HIGH / MEDIUM / LOW
packagestringVulnerable package
installed_versionstringCurrent version
fixed_versionstringVersion with the fix (or null)
first_seendatetimeFirst detection
notified_atdatetimeLast notification time
statusstringnew / notified / fixed / ignored
ColumnTypeDescription
scan_datedatetimeRun timestamp
images_scannednumberNumber of images analysed
critical_countnumberCRITICAL CVEs detected
high_countnumberHIGH CVEs detected
report_sentbooleanNotification sent?

Immediate CRITICAL alert routed via the Notification Hub:

🚨 CRITICAL CVE DETECTED
Image: postgres:16
CVE: CVE-2026-12345
Severity: CRITICAL (CVSS 9.8)
Package: libpq
Installed: 16.2 → Fixed in: 16.3
Affected stacks:
- n8n-stack (n8n-postgres)
- odoo-stack (odoo-db)
[🔄 Update now] [📖 CVE details] [⏰ Defer]

Weekly digest sent every Monday at 09:00:

📊 CVE Watch · Week 18
12 scans completed
0 CRITICAL · 3 HIGH · 8 MEDIUM (ignored)
FIXES APPLIED:
✅ postgres:16.2 → 16.3 (CVE-2026-12345)
✅ caddy:2.8.4 → 2.8.5 (CVE-2026-12346)
PENDING:
⏳ redis:7.2.3 → 7.2.4 (planned Sunday)

When DIUN detects that a new image version is available, its webhook also triggers CVE Watch on that specific image. The result is folded into the Telegram approval message:

CaseApproval button
New version, no CVE[Update] (green)
New version + resolved CVE[Update fixes CVE-XXX] (green)
New version + new CVEs[Update despite CVE] (orange)

The user makes the call with the risk fully visible.


LimitImpactMitigation
CVE feed lagA CVE published yesterday may not yet be in the Trivy DBPair with NVD RSS feed later
Application-level deps not scannedTrivy scans OS packages, not package.json/requirements.txt deps inside the imageCover with a separate GitHub Dependabot workflow
No contextual scoringA CVSS 9.8 on an unused package is still flagged CRITICALPer-package whitelist via an error_handling_config-like setup (to design)
musl image false positivesTrivy sometimes classifies Debian CVEs on Alpine (musl) imagesIgnore via Trivy --ignore-policy

If the daily cadence becomes insufficient:

  • Drop to hourly scans for CRITICAL images
  • Subscribe CVE Watch to a near-real-time NVD feed
  • Pair with a Trivy server webhook (--server mode)

If notification noise grows:

  • Add a per-CVE accepted whitelist (with justification)
  • Group CVEs by package rather than by CVE
  • Only notify if CVSS ≥ 9.0 in strict mode

If a compliance need shows up:

  • Keep JSON reports in an audit bucket
  • Generate a signed monthly PDF
  • Expose a read-only API for an external dashboard
MetricSourceInterpretation
cve_critical_opencve_notifications WHERE status IN ('new','notified') AND severity='CRITICAL'Must stay at 0
cve_mttrΔ between first_seen and fixed_atMean Time To Resolution
scan_failurescve_scan_history WHERE images_scanned=0Scanner outage indicator