Trivy i CI/CD-pipelines 2026: Containerscanning, SBOM og IaC

Sådan integrerer du Trivy i GitHub Actions og GitLab CI: containerscanning, SBOM-generering, IaC-checks og pragmatisk fail-policy med KEV-filter og OpenVEX-suppression.

Trivy CI/CD 2026: Containere, SBOM & IaC

Opdateret: 27. juli 2026

Trivy er open source-scanneren fra Aqua Security, der finder sårbarheder, hemmeligheder, licensproblemer og fejlkonfigurationer i containerbilleder, filsystemer, IaC-manifester og Kubernetes-klynger, alt sammen i én binær og med én kommando. I 2026 er Trivy blevet den de facto standard for pipelinescanning på Linux, fordi den understøtter SBOM-generering (SPDX og CycloneDX), VEX-suppression og direkte opslag i CISA KEV-kataloget. Denne guide viser, hvordan du integrerer Trivy i GitHub Actions og GitLab CI, hærder policyerne og filtrerer støjen fra, så bygninger kun fejler, når det faktisk betyder noget.

  • Trivy 0.58+ (juni 2026) understøtter SBOM-attest med Cosign, VEX-dokumenter i OpenVEX-format og indbygget KEV-berigelse.
  • En pragmatisk CI-policy fejler kun på CRITICAL-CVE'er, der findes i KEV-kataloget eller har en tilgængelig patch, ikke på hver eneste rød linje.
  • Trivy Operator giver klyngen konstant scanning uden sidecar-injektion; den skriver VulnerabilityReport-CRD'er direkte til etcd.
  • Trivy IaC-scanner dækker Terraform, CloudFormation, Kubernetes YAML, Helm og Dockerfiles i samme binær og med én cache.
  • SBOM'er bør genereres én gang i build-fasen og genbruges i deploy og runtime, ikke rescannes fra bunden i hver pipeline.
  • Grype, Snyk og Docker Scout dækker overlappende felter. Trivy vinder på licensering, IaC-bredde og ingen call-home.

Hvad er Trivy, og hvorfor pipeline-først?

Trivy er en enkelt Go-binær, der scanner otte artefakttyper (containerbilleder, filsystemer, git-repos, Kubernetes-klynger, VM-billeder, IaC-manifester, SBOM'er og AWS-konti) mod en samlet sårbarhedsdatabase, som opdateres to gange dagligt af Aqua Security. Fra og med Trivy 0.58 (juni 2026) er databasen delt i tre: trivy-db for OS-pakker, trivy-java-db for Maven-artefakter og trivy-checks for IaC-policy. Alle tre distribueres som OCI-artefakter og kan speiles i et privat registry.

Ærligt talt vinder Trivy pipelineslaget mod Grype og Snyk af tre grunde, sådan som jeg har oplevet det i produktion. For det første er den gratis for kommerciel brug (Apache 2.0), uden bruger- eller repo-loft. For det andet dækker den alle lag, jeg rører i en typisk DevSecOps-pipeline: docker build, Terraform-plan, Helm-chart og runtime-klynge. Og for det tredje kalder den ikke hjem. Al scanning sker lokalt, hvilket betyder, at jeg kan køre den i air-gapped miljøer uden PII-lækage. Det er også derfor, jeg placerer Trivy før images-push i registryet, ikke bagefter: en fejlet CVE-check her koster et par sekunder, mens en tilbagerulning i produktion koster en hel eftermiddag.

Pipeline-først betyder også, at scanning ikke er en engangsopgave. Jeg kører Trivy i mindst tre faser: pull-request (blokerer på kritiske), nightly (fanger nye CVE'er i uændrede images) og runtime via Trivy Operator (opdager drift efter deployment). Kombineret med signering via Cosign og Sigstore-workflowet giver det en beviskæde fra kildekode til klynge. Det er sådan set hele pointen.

Installation og opdatering af Trivy i 2026

På Debian, Ubuntu, RHEL og Fedora er det officielle repository den nemmeste vej. Trivy 0.58.1 (juni 2026) understøtter Ubuntu 24.04, Debian 13 (trixie), RHEL 9/10 og Fedora 40+. Undgå snap-installationen; den halter typisk 2-3 versioner bagud og misser KEV-berigelsen.

# Ubuntu / Debian
sudo apt install -y wget gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | \
  sudo gpg --dearmor -o /usr/share/keyrings/trivy.gpg
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] \
  https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | \
  sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt update && sudo apt install -y trivy

# RHEL / Fedora
sudo tee /etc/yum.repos.d/trivy.repo <<'EOF'
[trivy]
name=Trivy repository
baseurl=https://aquasecurity.github.io/trivy-repo/rpm/releases/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://aquasecurity.github.io/trivy-repo/rpm/public.key
EOF
sudo dnf install -y trivy

# Verificér version og databasen
trivy --version
trivy image --download-db-only

I CI foretrækker jeg det officielle container-image aquasec/trivy:0.58.1 med en fast tag, ikke latest. Det gør builds reproducerbare og undgår, at et pludseligt DB-skema-brud lukker hele pipelinen. Cache databasen i en persistent volume (/root/.cache/trivy) for at spare 200-400 MB download per job.

Scanning af containerbilleder på Linux

Grundscanning af et image tager typisk 3-8 sekunder efter første DB-download. Trivy læser OS-pakker (dpkg, rpm, apk) og sprogafhængigheder (npm, pip, go.sum, Cargo.lock, pom.xml, Gemfile.lock) i samme kørsel. Den afgørende hemmelige sauce er --severity-filteret kombineret med --exit-code. Det er faktisk det, der får pipelinen til at fejle på det rigtige tidspunkt.

# Grundscanning — outputter til stdout
trivy image nginx:1.27-alpine

# Kun HIGH/CRITICAL, fejl builden hvis der findes noget
trivy image \
  --severity HIGH,CRITICAL \
  --exit-code 1 \
  --ignore-unfixed \
  nginx:1.27-alpine

# JSON-output til videre parsing i pipeline
trivy image \
  --format json \
  --output nginx-scan.json \
  --scanners vuln,secret,license \
  nginx:1.27-alpine

# SARIF for GitHub Code Scanning
trivy image \
  --format sarif \
  --output trivy-results.sarif \
  --severity HIGH,CRITICAL \
  nginx:1.27-alpine

Flaget --ignore-unfixed er det, jeg altid slår til i CI. Det springer sårbarheder over, hvor upstream ikke har frigivet en patch endnu, og der er ingen grund til at fejle en build på noget, jeg ikke kan reagere på. --scanners begrænser til de scanninger, du har brug for; på et minimalt Alpine-image kan du slippe med vuln,secret og barbere 40% af køretiden af.

# CI-klar snippet: scan lokalt bygget image før push
IMAGE_TAG="${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}"
docker build -t "$IMAGE_TAG" .
trivy image \
  --severity CRITICAL \
  --ignore-unfixed \
  --exit-code 1 \
  --timeout 10m \
  --scanners vuln \
  "$IMAGE_TAG"
docker push "$IMAGE_TAG"

Trivy i GitHub Actions med KEV-filter

GitHub-actionen aquasecurity/[email protected] (2026) skifter automatisk til det officielle image og uploader SARIF til Code Scanning-fanen. Det nye i 2026 er --pkg-relationships, som filtrerer indirekte transitive afhængigheder fra. Det reducerer typisk støj med 30-50%.

name: Container Security Scan
on:
  pull_request:
    paths: ["Dockerfile", "src/**", "package*.json"]

permissions:
  contents: read
  security-events: write

jobs:
  trivy-scan:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t app:${{ github.sha }} .

      - name: Trivy image scan
        uses: aquasecurity/[email protected]
        with:
          image-ref: "app:${{ github.sha }}"
          format: sarif
          output: trivy-results.sarif
          severity: HIGH,CRITICAL
          ignore-unfixed: true
          pkg-relationships: direct
          scanners: vuln,secret
          exit-code: "0"          # upload først, fejl bagefter
          timeout: 10m
        env:
          TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db:2
          TRIVY_DISABLE_VEX_NOTICE: "true"

      - name: Upload to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

      - name: Fail på KEV-CVE'er
        run: |
          jq -e '.Results[]?.Vulnerabilities[]?
            | select(.Status=="fixed" and (.PublishedDate | fromdateiso8601 < (now - 30*86400)))
            | select(.CVSS.nvd.V3Score >= 9.0)' \
            trivy-results.json \
            && { echo "Blokerende CVE fundet"; exit 1; } \
            || echo "OK"

Trikket her er separationen. SARIF uploades først, så udviklere altid kan se scanningen i Security-fanen, selv når builden fejler. Blokerings-tjekket er et efterfølgende job, der kun fejler på patchbare kritiske CVE'er, som har været offentlige i mere end 30 dage. Det matcher CISA KEV-baselinen, som de fleste compliance-frameworks nu peger på. For yderligere hærdning af selve container-runtimen, se guiden om RunC-sårbarheder og runtime-beskyttelse.

Trivy i GitLab CI med cache og SARIF-upload

GitLab har indbygget understøttelse af Trivy-rapporter via container_scanning-artefakter siden GitLab 16.5. I 2026 anbefaler jeg fortsat at køre Trivy direkte, fordi det giver mere kontrol over policy og caching end den indbyggede skabelon.

trivy-container:
  stage: security
  image:
    name: aquasec/trivy:0.58.1
    entrypoint: [""]
  variables:
    TRIVY_NO_PROGRESS: "true"
    TRIVY_CACHE_DIR: ".trivycache/"
    TRIVY_DB_REPOSITORY: "${CI_REGISTRY}/mirrors/trivy-db:2"
  cache:
    key: trivy-db-v2
    paths:
      - .trivycache/
    policy: pull-push
  script:
    - trivy --version
    - trivy image --download-db-only
    - trivy image
        --exit-code 0
        --format template
        --template "@contrib/gitlab.tpl"
        --output gl-container-scanning-report.json
        "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
    - trivy image
        --exit-code 1
        --severity CRITICAL
        --ignore-unfixed
        --ignorefile .trivyignore
        "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
  artifacts:
    when: always
    reports:
      container_scanning: gl-container-scanning-report.json
    paths:
      - gl-container-scanning-report.json
    expire_in: 1 week

Spejlingen af trivy-db til det interne GitLab-registry er ikke pynt. Det er, hvad der får jobs til at gennemføre pålideligt i et VPN-isoleret miljø. En cron-job puller opstrøms hver 12. time og pusher til det interne registry.

SBOM-generering og signering med Cosign

SBOM (Software Bill of Materials) er ikke længere valgfrit i 2026. Det er et krav i EU's Cyber Resilience Act og NIS2 for produkter, der sælges kommercielt. Trivy kan producere både SPDX 2.3 og CycloneDX 1.6 i JSON eller XML. Anbefalingen: generér SBOM'en én gang i build-fasen, signér den med Cosign, og genbrug den derefter i alle downstream-scanninger.

# Generér SBOM i CycloneDX-format
trivy image \
  --format cyclonedx \
  --output sbom.cdx.json \
  "$IMAGE_TAG"

# Attester SBOM til imaget via Cosign (keyless, OIDC)
cosign attest \
  --predicate sbom.cdx.json \
  --type cyclonedx \
  "$IMAGE_TAG"

# Senere: scan direkte fra SBOM'en, ~10x hurtigere
trivy sbom \
  --severity CRITICAL \
  --ignore-unfixed \
  sbom.cdx.json

Fordelen ved SBOM-baseret scanning er dobbelt: den er hurtigere (ingen filsystem-inspektion), og den bevarer et historisk snapshot af, hvad der var i imaget på build-tidspunktet, selv hvis basisimaget senere overskrives. Det er nøglen til at kunne besvare "var vi sårbare over for CVE-2026-XXXX i uge 22?" et halvt år efter kendsgerningen. Jeg har selv haft præcis den samtale med en revisor, og et attesteret SBOM sparede os for en uges arkæologi. Se også vejledningen om signering af container-images med Cosign for hele attesteringskæden.

Kan Trivy scanne Terraform-filer?

Ja. Trivy erstattede sit gamle IaC-modul i version 0.35 med den bredere config-scanner, som deler engine med tfsec og cfsec. Fra 0.58 er dækningen: Terraform (HCL), Terraform Plan JSON, CloudFormation, Kubernetes YAML, Helm charts, Dockerfiles, containerd og Azure ARM. Reglerne er skrevet i Rego via det åbne trivy-checks-repo og opdateres uafhængigt af selve binæren.

# Scan hele repoet for IaC-fejlkonfigurationer
trivy config .

# Kun HIGH+, JSON-output, med filter-fil
trivy config \
  --severity HIGH,CRITICAL \
  --format json \
  --output iac-scan.json \
  --config-check-bundle-repository ghcr.io/aquasecurity/trivy-checks:1 \
  --skip-dirs "**/vendor/**,**/node_modules/**" \
  ./terraform

# Kombineret filesystem-scan: kode, deps OG IaC på én gang
trivy fs \
  --scanners vuln,secret,misconfig,license \
  --severity HIGH,CRITICAL \
  --ignorefile .trivyignore \
  .

Et lille footgun: trivy config vs trivy fs --scanners misconfig. De to producerer samme fund, men trivy fs udnytter fælles cache, hvis du allerede scanner for sårbarheder og hemmeligheder. På en stor monorepo er den forskel 30-60 sekunder per job. Ikke dramatisk, men det tæller op over hundredvis af PR'er om ugen. For en dybere gennemgang af IaC-hærdning, se guiden om CIS Benchmark-compliance med OpenSCAP.

Hvordan adskiller Trivy sig fra Grype?

Grype er Anchores modstykke og deler faktisk en del data med Trivy via NVD og GitHub Security Advisories. Forskellene ligger primært i bredden, licens og operationel model. Denne tabel opsummerer, hvor jeg placerer dem i en typisk DevSecOps-stak i 2026:

FunktionTrivy 0.58Grype 0.86Docker Scout
LicensApache 2.0Apache 2.0Proprietær (gratis niveau)
Containerimage-scanningJaJaJa
IaC / TerraformJa (indbygget)NejNej
Hemmeligheds-scannerJaNej (kræver Syft+ext)Delvist
SBOM-genereringSPDX, CycloneDXKræver SyftSPDX
VEX / OpenVEXNativeNativeNative
KEV-berigelseIndbyggetVia ekstern feedJa
Kubernetes OperatorTrivy OperatorNej officieltNej
Ringer hjemNejNejJa (opt-out)
Startup-tid (koldt)2-4 s1-2 s5-10 s

Kort sagt: Grype er fremragende, hvis du allerede har standardiseret på Syft til SBOM'er og kun har brug for CVE-matching. Trivy er det rigtige valg, når du vil dække image, IaC, hemmeligheder og licenser i én binær, og især når air-gap-krav udelukker Docker Scout.

Hvordan fejler jeg en build kun på kritiske sårbarheder?

At fejle på hver MEDIUM-CVE er den hurtigste vej til udviklertræthed (jeg har prøvet det, det virker ikke). Min anbefaling siden 2024 har været en tre-lags policy: rapportér alt, fejl på patchbare kritiske, og bloker deploy på KEV-listede. Det holder pipelinen anvendelig og fokuserer opmærksomheden på ægte risici.

# .trivy.yaml — projekt-niveau konfiguration
severity:
  - HIGH
  - CRITICAL

ignore-unfixed: true

vulnerability:
  ignore-status:
    - end_of_life
    - will_not_fix

scan:
  scanners:
    - vuln
    - secret
    - misconfig
  skip-dirs:
    - node_modules
    - vendor
    - .git

misconfiguration:
  include-non-failures: false

Med denne fil i repo-roden behøver CI-kommandoen kun være trivy image --exit-code 1 $IMAGE. Al policy er versioneret sammen med koden, hvilket også betyder, at PR-anmeldere kan se, når nogen forsøger at slække på en regel.

# CI-klar Bash: to-lags gate — advar, så bloker
trivy image --severity HIGH --exit-code 0 "$IMAGE"     # rapport
trivy image --severity CRITICAL \
            --ignore-unfixed \
            --pkg-relationships direct \
            --exit-code 1 "$IMAGE"                     # blokér

Undertrykkelse af falske positiver med VEX og .trivyignore

Selv med et stramt filter dukker der falske positiver op. Klassikeren: en CVE i libxml2, som kun rammer XSLT-parsing, mens vores app kun bruger DOM. To metoder til at håndtere det renligt: .trivyignore for hurtige undertrykkelser og OpenVEX-dokumenter for auditbare beslutninger.

# .trivyignore: enkel, kommenteret, versioneret
# libxml2 XSLT-hul, vi bruger kun DOM
# Verificeret af sikkerhed 2026-05-14, gennemgåes hvert kvartal
CVE-2025-12345

# Falsk positiv i k8s-vendored code
# Reference: https://github.com/aquasecurity/trivy/discussions/8421
CVE-2026-00892 exp:2026-12-31

VEX går et lag dybere: du erklærer eksplicit hvorfor en CVE ikke er relevant (not_affected, fixed, under_investigation). Trivy læser .vex/-mappen automatisk fra 0.55 og fremad.

{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://ourorg.example/vex/2026-libxml2",
  "author": "[email protected]",
  "timestamp": "2026-07-27T09:00:00Z",
  "statements": [{
    "vulnerability": {"name": "CVE-2025-12345"},
    "products": [{"@id": "pkg:oci/ourapp@sha256:abc123"}],
    "status": "not_affected",
    "justification": "vulnerable_code_not_in_execute_path",
    "impact_statement": "App bruger kun DOM-parser, ikke XSLT."
  }]
}

Trivy Operator til kontinuerlig klyngescanning

Trivy Operator er en Kubernetes-controller, som scanner alle images, konfigurationer og RBAC-tilladelser i klyngen, og skriver resultaterne som CRD'er (VulnerabilityReport, ConfigAuditReport, ExposedSecretReport). Version 0.24 fra maj 2026 tilføjede indbygget SBOM-generering og en gauge-metric per CVE-alvor, som Prometheus kan alarmere på.

helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm install trivy-operator aqua/trivy-operator \
  --namespace trivy-system --create-namespace \
  --set trivy.ignoreUnfixed=true \
  --set trivy.severity=HIGH\,CRITICAL \
  --set operator.metricsVulnIdEnabled=true \
  --set operator.builtInTrivyServer=true \
  --version 0.24.1

# Se sårbarheder på tværs af klyngen
kubectl get vulnerabilityreports -A -o wide

# Prometheus-alert (uddrag)
- alert: KubeCriticalCVE
  expr: sum(trivy_image_vulnerabilities{severity="Critical"}) by (namespace, image) > 0
  for: 15m
  labels: {severity: page}

Operatoren komplementerer runtime-kontrollen fra Tetragon med eBPF-baseret kørselssikkerhed. Trivy fanger sårbarheder i imaget, mens Tetragon fanger, hvad imaget faktisk gør, når det kører. Sammen dækker de før-deploy- og efter-deploy-hullerne i en typisk klynge.

# CI-klar snippet: valider Kubernetes-manifester mod Trivy-checks
trivy config \
  --severity HIGH,CRITICAL \
  --config-check-bundle-repository ghcr.io/aquasecurity/trivy-checks:1 \
  --exit-code 1 \
  ./k8s/manifests/

Ofte stillede spørgsmål

Hvor ofte opdateres Trivys sårbarhedsdatabase?

Aqua Security opdaterer trivy-db hver 6. time, mens trivy-java-db opdateres dagligt, og trivy-checks opdateres ved behov (typisk en gang om ugen). I CI cacher jeg databasen i 12 timer for at balancere friskhed og downloadstøj.

Er Trivy gratis til kommerciel brug?

Ja. Trivy er udgivet under Apache 2.0-licensen uden bruger-, repo- eller image-loft. Aqua Security tilbyder et betalt tillæg (Aqua Enterprise), som tilføjer et centralt dashboard, RBAC og SLA, men selve scannerens funktionalitet forbliver open source.

Understøtter Trivy scanning bag en proxy eller i air-gap?

Ja. Sæt HTTPS_PROXY for netværk gennem en proxy, eller spejle trivy-db, trivy-java-db og trivy-checks til et internt OCI-registry og peg på dem via TRIVY_DB_REPOSITORY-miljøvariablerne. Alle Trivy-kommandoer kan derefter køres offline.

Hvad er forskellen på Trivy fs og Trivy image?

trivy image scanner et bygget containerbillede (fra registry eller lokalt), mens trivy fs scanner et lokalt filsystem eller git-repo. Førstnævnte fanger runtime-lag som base image; sidstnævnte fanger, hvad der er i den kode, du er ved at bygge, før imaget eksisterer.

Kan Trivy generere en SBOM uden at scanne for sårbarheder?

Ja. Kør trivy image --format cyclonedx --skip-vulnerabilities --output sbom.json IMAGE. Det udskriver kun komponentlisten, hvilket er hurtigere og nyttigt, når SBOM'en skal signeres og gemmes for compliance uden aktiv sårbarhedsberigelse.

Hvordan integrerer Trivy med Cosign og attesteringer?

Generér SBOM'en med trivy image --format cyclonedx, og attester derefter med cosign attest --predicate sbom.cdx.json --type cyclonedx IMAGE. Attesteringen gemmes ved siden af imaget i registryet og kan verificeres i deploy-fasen med cosign verify-attestation.

Raj Patel
Om Forfatteren Raj Patel

DevSecOps engineer who's gradually turning every CI pipeline he sees into a security-checking machine.