Vulnerability Policy Gates: CI-Builds bei kritischen CVEs aktiv blockieren
AI generated
FROM
RUN
Docker · Security · CI/CD
Vulnerability Policy Gates
CI-Builds bei kritischen CVEs wirklich blockieren, nicht nur warnen

Ein Scan-Report, den niemand liest, schuetzt kein System. Ein echtes Vulnerability Policy Gate stoppt den Build-Prozess aktiv, sobald ein Container-Image kritische Schwachstellen enthaelt, mit klaren Schwellenwerten und einem definierten Umgang mit False Positives, statt Sicherheitswarnungen als optionale Fussnote im CI-Log zu behandeln.

17 Min. Lesezeit Trivy Exit-Code CVE-Schwellenwerte False-Positive-Handling

1. Der Unterschied zwischen Melden und Blockieren

Viele Teams richten einen Container-Scan in ihrer Pipeline ein und halten das faelschlicherweise bereits fuer ausreichende Sicherheit. Ein Scan, der lediglich einen Report generiert und der Pipeline unabhaengig vom Ergebnis mit Exit-Code 0 signalisiert, dass alles in Ordnung ist, hat aber keine tatsaechliche Schutzwirkung, weil niemand gezwungen ist, den Report vor dem Deploy zu lesen. In der Praxis verschwinden solche Reports in Build-Logs, die routinemaessig ignoriert werden, sobald der grosse gruene Haken der Pipeline erscheint.

Ein echtes Policy Gate unterscheidet sich fundamental: Der Scan-Schritt selbst entscheidet aktiv ueber Erfolg oder Misserfolg des Builds, indem er bei Ueberschreitung eines definierten Schwellenwerts einen Exit-Code ungleich null zurueckgibt, wodurch die gesamte Pipeline fehlschlaegt und der nachfolgende Deploy-Schritt gar nicht erst ausgefuehrt wird. Dieser Unterschied zwischen passiver Sichtbarkeit und aktiver Durchsetzung ist der Kern jeder wirksamen Vulnerability-Policy in CI/CD.

2. Exit-Code-basiertes Blockieren mit Trivy

Trivy, einer der verbreitetsten Open-Source-Scanner fuer Container-Images, bietet mit den Parametern --exit-code und --severity genau diesen Mechanismus direkt eingebaut. Wird --exit-code 1 zusammen mit --severity CRITICAL gesetzt, gibt Trivy Exit-Code 1 zurueck, sobald mindestens eine Schwachstelle mit dem Schweregrad CRITICAL gefunden wird, was in fast jedem CI-System automatisch zum Fehlschlagen des Jobs fuehrt. Ohne explizit gesetzten --exit-code gibt Trivy standardmaessig immer Exit-Code 0 zurueck, unabhaengig davon, wie viele oder wie schwerwiegende Schwachstellen gefunden wurden, ein Detail, das in vielen Pipeline-Konfigurationen uebersehen wird.

Ein haeufiger Konfigurationsfehler ist, den Scan zwar mit korrektem Exit-Code laufen zu lassen, aber gleichzeitig continue-on-error: true oder ein aehnliches Flag im CI-System zu setzen, meist aus der berechtigten Sorge heraus, dass ein einzelner falsch-positiver Fund die gesamte Pipeline lahmlegt. Genau dieses Flag hebt aber die gesamte Schutzwirkung des Gates wieder auf und sollte nur in einer separaten, klar gekennzeichneten Report-Only-Stufe verwendet werden, nie in der eigentlichen Blocking-Stufe.


# Blockierender Scan: schlaegt fehl bei kritischen Schwachstellen
trivy image --exit-code 1 --severity CRITICAL myapp:latest

# Kombinierter Schwellenwert: HIGH und CRITICAL blockieren
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest

# Nur fixierbare Schwachstellen beruecksichtigen (ignoriert CVEs ohne verfuegbaren Patch)
trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed myapp:latest

3. Integration als eigener Pipeline-Schritt

Ein Policy Gate gehoert als eigenstaendiger Schritt zwischen den Image-Build und den Push in die produktive Registry, niemals danach. Wird der Scan erst nach dem Push ausgefuehrt, ist das verwundbare Image bereits verfuegbar und koennte theoretisch schon deployt worden sein, bevor das Scan-Ergebnis vorliegt. Die richtige Reihenfolge ist: Image lokal in der Pipeline bauen, Image scannen und bei Ueberschreitung des Schwellenwerts abbrechen, erst danach in die Registry pushen und weiterverarbeiten.

In GitHub Actions laesst sich das direkt als sequenzieller Schritt nach dem Build abbilden, wobei ein separater Build-Schritt ohne Push notwendig ist, damit das Image lokal fuer den Scan verfuegbar ist, bevor es tatsaechlich in die Registry gelangt. Alternativ kann man mit load: true in docker/build-push-action arbeiten, um das gebaute Image in den lokalen Docker-Daemon des Runners zu laden, ohne es bereits zu pushen.


jobs:
  build-and-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build image locally (no push yet)
        uses: docker/build-push-action@v6
        with:
          context: .
          load: true
          tags: myapp:scan-target

      - name: Scan for critical vulnerabilities
        uses: aquasecurity/trivy-action@0.28.0
        with:
          image-ref: myapp:scan-target
          severity: CRITICAL
          exit-code: '1'
          ignore-unfixed: true

      - name: Push only if scan passed
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/myorg/myapp:latest

4. Schwellenwert-Strategien: nicht jede Severity gleich behandeln

Eine Policy, die bei jeder Schwachstelle jeder Severity-Stufe blockiert, einschliesslich LOW und MEDIUM, fuehrt in der Praxis fast immer dazu, dass Teams das Gate umgehen oder deaktivieren, weil kaum ein realistisches Basis-Image, insbesondere mit vielen Betriebssystem-Paketen, vollstaendig frei von LOW- oder MEDIUM-Findings ist. Die uebliche und praktikablere Strategie ist eine gestufte Policy: CRITICAL blockiert den Build sofort und ausnahmslos, HIGH blockiert ebenfalls, aber mit einem definierten Ausnahmeprozess fuer dokumentierte, akzeptierte Risiken, waehrend MEDIUM und LOW lediglich reportet werden, ohne den Build zu stoppen.

Diese Abstufung sollte nicht willkuerlich gewaehlt werden, sondern sich am tatsaechlichen Risikoprofil der Anwendung orientieren. Ein Image, das direkt mit dem Internet exponiert ist, etwa ein oeffentlich erreichbarer Webserver, rechtfertigt eine strengere Policy als ein internes Batch-Verarbeitungssystem ohne Netzwerkzugriff von aussen. Manche Teams fuehren deshalb zwei verschiedene Policy-Profile, ein strenges fuer extern erreichbare Services und ein moderateres fuer rein interne Systeme.


# Gestufte Policy als zwei separate Scan-Schritte
# Schritt 1: CRITICAL und HIGH blockieren den Build hart
trivy image --exit-code 1 --severity CRITICAL,HIGH --ignore-unfixed myapp:latest

# Schritt 2: MEDIUM und LOW nur reportieren, Build laeuft trotzdem weiter
trivy image --exit-code 0 --severity MEDIUM,LOW --format json --output report.json myapp:latest

5. Umgang mit False Positives ohne das Gate auszuhoehlen

Jedes Policy Gate wird frueher oder spaeter mit einem False Positive konfrontiert: einer CVE, die formal auf eine im Image enthaltene Paketversion zutrifft, aber aus konkreten Gruenden nicht ausnutzbar ist, etwa weil die betroffene Funktion im tatsaechlichen Anwendungsfall nie aufgerufen wird oder ein Upstream-Patch bereits inoffiziell zurueckportiert wurde. Der entscheidende Unterschied zu einem schlecht gefuehrten Gate ist, wie mit diesem Fall umgegangen wird: Statt das gesamte Gate global abzuschalten, sollte jede Ausnahme einzeln, nachvollziehbar und mit Ablaufdatum dokumentiert werden.

Trivy und vergleichbare Scanner unterstuetzen dafuer eine .trivyignore-Datei, in der einzelne CVE-IDs mit einer Begruendung als Kommentar aufgefuehrt werden koennen. Wichtig ist, diese Datei regelmaessig zu ueberpruefen, etwa per Kalendererinnerung oder automatisiertem Reminder, denn eine als 'nicht ausnutzbar' markierte CVE kann durch eine spaetere Codeaenderung ploetzlich doch relevant werden, ohne dass die Ignore-Liste automatisch aktualisiert wird. Eine .trivyignore-Datei ohne Ablaufdatum wird in der Praxis fast immer zu einer stetig wachsenden Liste, die niemand mehr hinterfragt.


# .trivyignore
# CVE-2024-12345: Betrifft nur die XML-Parser-Funktion, die in unserem
# Code-Pfad nicht aufgerufen wird. Review-Datum: 2026-11-01, Owner: Team Platform
CVE-2024-12345

# CVE-2023-98765: Upstream-Fix bereits im naechsten Base-Image-Update enthalten,
# geplantes Update: Q4 2026, Owner: Team Platform
CVE-2023-98765

6. Praevention durch Basisimage-Wahl statt reines Nachfiltern

Ein Policy Gate allein loest kein Problem, es verhindert nur, dass ein bereits existierendes Problem produktiv wird. Deutlich effektiver ist eine Praeventionsstrategie, die die Zahl der Findings von vornherein reduziert, allen voran die Wahl eines schlanken Basisimages. Distroless-Images oder Alpine-basierte Varianten enthalten drastisch weniger Betriebssystem-Pakete als ein volles Debian- oder Ubuntu-Image, wodurch es strukturell weniger Angriffsflaeche und weniger potenzielle CVE-Traeger gibt, unabhaengig davon, wie gut das eigentliche Anwendungs-Image gepflegt wird.

Ebenso wichtig ist ein regelmaessiges, automatisiertes Neubauen von Images allein aufgrund eines aktualisierten Basisimages, selbst wenn sich am eigenen Anwendungscode nichts geaendert hat. Viele kritische CVEs entstehen nicht durch eigenen Code, sondern durch Betriebssystem-Pakete im Basisimage, die zum urspruenglichen Build-Zeitpunkt noch unauffaellig waren und erst spaeter als verwundbar bekannt wurden. Ein woechentlicher, geplanter Rebuild-Job, der unabhaengig von Code-Aenderungen laeuft, faengt genau diese Kategorie von Schwachstellen zuverlaessig ab, bevor sie in einem regulaeren Deploy-Zyklus unbemerkt bleiben.


name: Scheduled base image rebuild

on:
  schedule:
    - cron: '0 3 * * 1'   # jeden Montag 03:00 UTC
  workflow_dispatch:

jobs:
  rebuild-and-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Rebuild with latest base image
        run: docker build --pull --no-cache -t myapp:latest .
      - name: Scan rebuilt image
        uses: aquasecurity/trivy-action@0.28.0
        with:
          image-ref: myapp:latest
          severity: CRITICAL,HIGH
          exit-code: '1'

7. Dependency-Scan versus Image-Scan: unterschiedliche Ebenen

Ein haeufiges Missverstaendnis ist, Dependency-Scanning der Anwendungsbibliotheken, etwa per npm audit oder pip-audit, und Image-Scanning des fertigen Containers als redundant zu betrachten und nur eines von beiden einzusetzen. Tatsaechlich decken beide unterschiedliche Ebenen ab: Dependency-Scanning findet Schwachstellen in den direkten und transitiven Abhaengigkeiten der Anwendung, waehrend Image-Scanning zusaetzlich Betriebssystem-Pakete, Systembibliotheken und alles, was im Basisimage mitgebracht wird, erfasst, Bereiche, die reines Dependency-Scanning grundsaetzlich nicht sieht.

Fuer eine vollstaendige Abdeckung gehoeren deshalb beide Scan-Typen in die Pipeline, idealerweise als getrennte Schritte mit jeweils eigenem Schwellenwert, da die typische Menge und Kritikalitaet der Findings zwischen Anwendungsabhaengigkeiten und Betriebssystem-Paketen oft unterschiedlich ausfaellt. Manche Teams setzen fuer Dependency-Scans eine strengere Policy an, weil sie direkten Einfluss auf die gewaehlten Bibliotheken haben, waehrend bei Betriebssystem-Paketen im Basisimage oft nur das Warten auf einen Upstream-Patch bleibt.

8. SBOM als Grundlage fuer schnellere Reaktion auf neue CVEs

Ein Policy Gate prueft immer nur den Stand zum Zeitpunkt des Builds, es kann keine Aussage darueber treffen, welche bereits produktiv laufenden Images von einer neu veroeffentlichten CVE betroffen sind. Genau dafuer ist eine Software Bill of Materials, kurz SBOM, entscheidend: Sie listet praezise jede im Image enthaltene Paketversion auf und ermoeglicht es, bei einer neuen kritischen CVE innerhalb von Minuten statt Tagen zu beantworten, welche produktiven Images tatsaechlich betroffen sind, ohne jedes Image erneut scannen zu muessen.

Trivy kann eine SBOM direkt beim Image-Build im CycloneDX- oder SPDX-Format erzeugen und als Artefakt neben dem eigentlichen Image ablegen, etwa als signiertes Attestation-Objekt in der Registry. Kombiniert mit einem zentralen SBOM-Archiv laesst sich bei jeder neuen CVE-Veroeffentlichung eine einfache Abfrage durchfuehren, ob die betroffene Paketversion in irgendeinem produktiven Image vorkommt, was die Reaktionszeit auf sogenannte Zero-Day-Situationen erheblich verkuerzt gegenueber dem reaktiven Warten auf den naechsten geplanten Scan.


# SBOM im CycloneDX-Format direkt beim Build erzeugen
trivy image --format cyclonedx --output sbom.json myapp:latest

# Spaeter: gezielt pruefen, ob eine bestimmte Paketversion enthalten ist
trivy sbom sbom.json --severity CRITICAL,HIGH

9. Reporting und Nachverfolgung ueber die Pipeline hinaus

Ein Blocking-Gate allein beantwortet nicht die Frage, wie viele blockierte Builds es insgesamt gab, welche CVEs am haeufigsten auftreten und ob sich der allgemeine Sicherheitszustand ueber die Zeit verbessert oder verschlechtert. Deshalb lohnt es sich, Scan-Ergebnisse zusaetzlich zum reinen Pass/Fail-Signal in ein zentrales Dashboard oder System zu exportieren, etwa im SARIF-Format fuer die GitHub Security-Uebersicht oder in ein dediziertes Vulnerability-Management-Tool, unabhaengig davon, ob der Build besteht oder scheitert.

Diese Historie ist besonders wertvoll fuer die bereits erwaehnten dokumentierten Ausnahmen: Ohne zentrale Nachverfolgung verliert ein Team schnell den Ueberblick, welche Ausnahmen aktiv sind, wann sie ablaufen und ob eine als temporaer akzeptierte Schwachstelle laengst haette behoben werden muessen. Eine regelmaessige, etwa monatliche Durchsicht aller aktiven Ausnahmen zusammen mit dem Security-Verantwortlichen verhindert, dass die .trivyignore-Datei zu einem dauerhaften blinden Fleck wird.

Severity Empfohlene Aktion Ausnahmeprozess Typisches Beispiel
CRITICAL Build sofort blockieren, keine Ausnahme Nur mit dokumentiertem, zeitlich begrenztem Ausnahmefall Remote Code Execution in Kernbibliothek
HIGH Build blockieren Dokumentierte Ausnahme mit Ablaufdatum moeglich Privilege Escalation ohne bekannten Exploit
MEDIUM Nur reportieren, Build laeuft weiter Keine noetig, wird im Dashboard verfolgt Denial-of-Service unter speziellen Bedingungen
LOW Nur reportieren, Build laeuft weiter Keine noetig Informationsleck ohne direkten Schaden

Mironsoft

Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung

Docker-Setups, die im Team und in Produktion tragfähig bleiben?

Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.

Dockerfile-Review

Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.

Security-Audit

Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.

CI/CD-Integration

Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.

10. Zusammenfassung

Vulnerability Policy Gates: Das Wichtigste auf einen Blick

Report vs. Gate

Nur ein Exit-Code ungleich null bei Schwellenwertueberschreitung stoppt die Pipeline wirklich.

Gestufte Policy

CRITICAL und HIGH blockieren, MEDIUM und LOW werden nur dokumentiert.

False Positives

Einzelne, begruendete Ausnahmen mit Ablaufdatum statt das gesamte Gate abzuschalten.

Praevention

Schlanke Basisimages und geplante Rebuilds reduzieren Findings, bevor das Gate greifen muss.

11. FAQ: Vulnerability Policy Gates: Das Wichtigste auf einen Blick

1Warum reicht ein reiner Vulnerability-Report ohne Blocking nicht aus?
Ein Report, der die Pipeline unabhaengig vom Ergebnis mit Exit-Code 0 durchlaufen laesst, hat keine erzwingende Wirkung, weil niemand gezwungen ist, ihn vor dem Deploy zu lesen. Solche Reports verschwinden in der Praxis meist ungelesen im CI-Log.
2Wie blockiert Trivy einen Build bei kritischen Schwachstellen?
Mit den Parametern --exit-code 1 und --severity CRITICAL gibt Trivy Exit-Code 1 zurueck, sobald mindestens eine kritische Schwachstelle gefunden wird, wodurch die CI-Pipeline automatisch fehlschlaegt.
3Warum sollte man nicht bei jeder Severity-Stufe blockieren?
Eine Policy, die auch bei LOW und MEDIUM blockiert, fuehrt fast immer dazu, dass Teams das Gate umgehen, weil kaum ein realistisches Image vollstaendig frei von solchen Findings ist. Eine gestufte Policy mit Fokus auf CRITICAL und HIGH ist praktikabler.
4Wie geht man mit einer False-Positive-CVE um, ohne das Gate zu schwaechen?
Ueber eine .trivyignore-Datei mit einzelnen, begruendeten und mit Ablaufdatum versehenen Ausnahmen, statt das gesamte Gate global zu deaktivieren oder continue-on-error zu setzen.
5Warum sollte der Scan vor dem Push in die Registry stattfinden?
Findet der Scan erst nach dem Push statt, ist das verwundbare Image bereits verfuegbar und koennte theoretisch schon deployt worden sein, bevor das Scan-Ergebnis vorliegt. Der Scan gehoert zwischen Build und Push.
6Was ist der Unterschied zwischen Dependency-Scanning und Image-Scanning?
Dependency-Scanning prueft die direkten und transitiven Abhaengigkeiten der Anwendung, Image-Scanning erfasst zusaetzlich Betriebssystem-Pakete und Systembibliotheken aus dem Basisimage. Beide decken unterschiedliche Ebenen ab und sollten kombiniert werden.
7Warum sind geplante Rebuilds ohne Codeaenderung sinnvoll?
Viele kritische CVEs entstehen durch Betriebssystem-Pakete im Basisimage, die erst nach dem urspruenglichen Build als verwundbar bekannt werden. Ein regelmaessiger, unabhaengig von Code-Aenderungen laufender Rebuild-Job faengt diese Kategorie zuverlaessig ab.
8Was bedeutet --ignore-unfixed bei Trivy?
Diese Option blendet Schwachstellen aus, fuer die noch kein Patch verfuegbar ist, weil das Gate in diesem Fall ohnehin nicht handlungsfaehig waere. Sie sollte bewusst und nicht als pauschale Abschwaechung des Gates eingesetzt werden.
9Wie oft sollte die .trivyignore-Datei ueberprueft werden?
Regelmaessig, etwa monatlich, zusammen mit dem Security-Verantwortlichen, um zu pruefen, ob dokumentierte Ausnahmen noch gueltig sind oder abgelaufene beziehungsweise inzwischen relevante CVEs enthalten.
10Sollte fuer alle Services dieselbe Vulnerability-Policy gelten?
Nicht zwingend. Extern erreichbare Services rechtfertigen oft eine strengere Policy als rein interne Systeme ohne Netzwerkzugriff von aussen, weshalb manche Teams zwei unterschiedliche Policy-Profile pflegen.