#!/bin/bash # Trivy CVE scanner integration — probed by the WebUI updater generator. # --------------------------------------------------------------------------- # Trivy (containers/trivy) runs as a long-lived server whose vulnerability DB # is cached in ./trivy-cache. On first start — and periodically after — that DB # is downloaded (tens of MB). Until it lands NO scan can produce results, so the # WebUI must not paint a green "no known vulnerabilities" all-clear yet. # # These helpers expose the scanner's state so the updater generator can write an # honest cves.json: # absent — Trivy isn't installed/running (CVE scanning unavailable) # db_updating — installed, but the vulnerability DB is still downloading # ready — DB present; real per-image scans can run # Every helper degrades to the safe answer (absent / [] ) on any error so the # generator never breaks. # Echoes: absent | db_updating | ready trivyScannerState() { # Not in `docker ps` (never installed, stopped, or still starting) -> absent. dockerCommandRun "docker ps --format '{{.Names}}'" 2>/dev/null \ | grep -qx trivy-service || { echo absent; return; } # Running: the DB is ready once `trivy version` reports a VulnerabilityDB # block (null/absent while it's still being fetched on first boot). local ver; ver="$(dockerCommandRun "docker exec trivy-service trivy version -f json" 2>/dev/null)" [ -n "$ver" ] || { echo db_updating; return; } if command -v jq >/dev/null 2>&1; then [ -n "$(printf '%s' "$ver" | jq -r '.VulnerabilityDB // empty' 2>/dev/null)" ] \ && echo ready || echo db_updating else # No jq: DownloadedAt only appears once the DB is present (and, unlike a # nullable VulnerabilityDB key, avoids a false positive when it's null). printf '%s' "$ver" | grep -q '"DownloadedAt"' \ && echo ready || echo db_updating fi } # Echoes the DB's UpdatedAt timestamp (ISO8601) if available, else nothing. trivyDbUpdatedAt() { command -v jq >/dev/null 2>&1 || return 0 local ver; ver="$(dockerCommandRun "docker exec trivy-service trivy version -f json" 2>/dev/null)" [ -n "$ver" ] || return 0 printf '%s' "$ver" | jq -r '.VulnerabilityDB.UpdatedAt // empty' 2>/dev/null } # trivyScanImageCves — scan one image against the cached DB and echo a # JSON array of normalized CVE objects for the WebUI: # [ { id, severity, package, installed, fixed_in, url }, ... ] # Echoes [] on any failure so a single bad image never aborts the whole scan. # Requires jq (the generator already guards on it). trivyScanImageCves() { local image="$1" [ -n "$image" ] || { echo '[]'; return; } command -v jq >/dev/null 2>&1 || { echo '[]'; return; } # Scan the app's LOCAL image via the docker socket mounted into the # container (--image-src docker) so nothing is pulled from a registry — the # image is already present, and a privacy box may have no outbound at all. # Client mode (--server localhost:4954, the container's fixed --listen port) # leaves the vuln DB to the running server; a standalone scan would deadlock # on the server's cache lock. For rootless the daemon socket lives under # /run/user/ (not trivy's default path), so point DOCKER_HOST at it; # rooted's /var/run/docker.sock is found by default. local envs="" if [ "${CFG_DOCKER_INSTALL_TYPE:-}" = "rootless" ] && [ -n "${docker_install_user:-}" ]; then local uid; uid="$(id -u "$docker_install_user" 2>/dev/null)" [ -n "$uid" ] && envs="-e DOCKER_HOST=unix:///run/user/$uid/docker.sock" fi # --quiet keeps the progress spinner out of stdout; we only want CRITICAL..LOW. local raw raw="$(dockerCommandRun "docker exec $envs trivy-service trivy image --server http://localhost:4954 --image-src docker --quiet --scanners vuln --format json --severity CRITICAL,HIGH,MEDIUM,LOW '$image'" 2>/dev/null)" [ -n "$raw" ] || { echo '[]'; return; } # Carry the enclosing Result's Class/Type down onto each vuln: Class # (os-pkgs|lang-pkgs) is what separates an OS-package CVE from an app's own # bundled dependency (Go module, etc.), and Status (fixed|will_not_fix|…) # separates "a fix exists" from "upstream won't fix" — both drive the # triaged Security view. Without binding them before the Vulnerabilities[] # flatten they'd be lost. printf '%s' "$raw" | jq -c ' [ (.Results // [])[] | (.Class // "") as $class | (.Type // "") as $type | (.Vulnerabilities // [])[] | { id: .VulnerabilityID, severity: ((.Severity // "UNKNOWN") | ascii_downcase), package: .PkgName, installed: (.InstalledVersion // ""), fixed_in: (.FixedVersion // ""), status: (.Status // ""), class: $class, type: $type, url: (.PrimaryURL // "") } ] | unique_by(.id + "|" + (.package // "")) ' 2>/dev/null || echo '[]' }