Skip to content

Vulnerability Scanning (Trivy)

Portalcrane embeds Trivy as a persistent server process (trivy-server, listening on 127.0.0.1:4954), so scans don't pay a cold-start cost. It's used in two places: the Staging Pipeline, where it gates a push based on severity, and on-demand scans triggered from the image browser.

Master switch

TRIVY_ENABLED=true   # default

Setting TRIVY_ENABLED=false (also accepts 0, no, off) fully disarms Trivy, whether running locally or remotely:

  • supervisord never starts the embedded trivy-server process.
  • The vulnerability database updater is skipped.
  • Every scan endpoint (GET /api/trivy/scan, /api/trivy/db, …) returns 503 Service Unavailable.
  • The staging pipeline silently skips the scan step regardless of VULN_SCAN_ENABLED.

Use this on resource-constrained hosts (e.g. a Raspberry Pi) where you'd rather trade vulnerability scanning for a smaller memory footprint.

Local vs. remote Trivy server

TRIVY_SERVER_URL=http://localhost:4954   # default — embedded server

Portalcrane always talks to Trivy in client/server mode (trivy image --server <url>). By default TRIVY_SERVER_URL points at the trivy-server process embedded in the same container. Pointing it at a different host instead runs the scan against a Trivy server running in another container — useful to share one Trivy instance (and its CVE database) across several Portalcrane deployments, or to keep the vulnerability DB off the main container entirely:

TRIVY_ENABLED=true
TRIVY_SERVER_URL=http://trivy:4954
# docker-compose.yml sketch
services:
  trivy:
    image: aquasec/trivy
    command: server --listen 0.0.0.0:4954
    volumes:
      - trivy-cache:/root/.cache/trivy

  portalcrane:
    image: ghcr.io/cyr-ius/portalcrane
    environment:
      TRIVY_ENABLED: "true"
      TRIVY_SERVER_URL: "http://trivy:4954"

When TRIVY_SERVER_URL no longer targets localhost/127.0.0.1:

  • The embedded trivy-server is not autostarted by supervisord — no point running a local server nobody talks to.
  • The local vulnerability database updater is skipped too; the remote server manages its own DB.
  • GET /api/trivy/db and POST /api/trivy/db/update report the DB as managed remotely instead of reading the (now-empty) local cache — check the remote server's own status for its DB freshness.
  • Actual scanning (GET /api/trivy/scan, the staging pipeline, transfers) is unaffected — it just talks to the remote server instead of the local one.

TRIVY_ENABLED stays the single switch for turning scanning on or off in either mode; TRIVY_SERVER_URL only decides where Trivy runs.

Scan policy

Variable Description Default
VULN_SCAN_ENABLED Enable the CVE scan step in the staging pipeline true
VULN_SCAN_SEVERITIES Comma-separated severities that block a staging push CRITICAL,HIGH
VULN_IGNORE_UNFIXED Ignore CVEs that have no available fix yet false
VULN_SCAN_TIMEOUT Timeout for a single Trivy scan 5m

These four values can be overridden globally, for all users, from Settings → Vulnerability Scanning — the override is persisted to DATA_DIR/vuln_override.json and takes precedence over the environment variables on the next request, no restart needed.

curl -X PUT http://<host>:8000/api/trivy/override \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
        "vuln_scan_enabled": true,
        "vuln_scan_severities": "CRITICAL,HIGH,MEDIUM",
        "vuln_ignore_unfixed": true,
        "vuln_scan_timeout": "10m"
      }'

Remove the override with DELETE /api/trivy/override to fall back to the environment-variable defaults.

Per-job overrides

The Staging Pipeline's pull request (POST /api/staging/pull) also accepts vuln_scan_enabled_override and vuln_severities_override fields, letting a user relax or tighten the policy for a single pull without touching the global setting.

On-demand scanning

Any authenticated user can scan an already-pushed image directly:

curl -G http://<host>:8000/api/trivy/scan \
  -H "Authorization: Bearer <token>" \
  --data-urlencode "image=production/redis:7.2" \
  --data-urlencode "severity=HIGH" \
  --data-urlencode "severity=CRITICAL" \
  --data-urlencode "ignore_unfixed=false"

The image reference must include an explicit tag or digest (e.g. production/redis:7.2 or production/redis@sha256:…) — a bare repository name is rejected with 400.

Database maintenance (admin)

The Trivy vulnerability database is cached under DATA_DIR/cache/trivy/ and updates automatically in the background. Admins can check its freshness and force a refresh from Settings → System:

# Freshness / metadata
curl http://<host>:8000/api/trivy/db -H "Authorization: Bearer <admin-token>"

# Force an immediate update
curl -X POST http://<host>:8000/api/trivy/db/update -H "Authorization: Bearer <admin-token>"

Reading a scan result

Scan results are grouped and include CVSS scores, so the UI can sort by severity and show a fix-available indicator per vulnerability. A staging or transfer job whose scan surfaces a blocking severity is flagged in the job's vuln_result and the push is refused server-side (403 on POST /api/staging/push; the transfer stops at scan_vulnerable without pushing) — retag away the offending layer, choose a patched base image, or abandon the job.

An admin can grant specific accounts a standing exception to this block — see Vulnerability-block bypass in the users & permissions guide.