Zum Hauptinhalt springen

Scannen von Container-Images

Das Scannen von Container-Images beantwortet die Frage Was ist in diesem Image verwundbar? und ergänzt damit die Frage der SBOM, Was steckt in diesem Image?. Befunde kommen aus zwei Quellen, und beide werden neben dem Image oder Commit angezeigt, den sie beschreiben.

So funktioniert es

Der tägliche Durchlauf über die Registry

fremforge scannt die Container-Images in der Paket-Registry deiner Organisation selbst. Du musst deinen Workflows nichts hinzufügen.

  • Was gescannt wird. Jedes Tag jedes Container-Images. Jedes Tag wird zu seinem Manifest-Digest aufgelöst, und das Image wird mit trivy image auf Schwachstellen und geleakte Secrets gescannt, einschließlich Betriebssystempaketen und Sprachabhängigkeiten in jedem Layer.
  • Wann. Einmal täglich, nachdem die Schwachstellendatenbank von Trivy aktualisiert wurde. Ein Digest, der bereits gegen dieselbe Datenbankversion gescannt wurde, wird übersprungen, sodass eine neu veröffentlichte CVE auch gegen ein unverändertes Image gemeldet wird.
  • Wo Befunde gespeichert werden. Unter dem Image-Digest (frem.sh/<org>/<image>@sha256:<digest>). Verwerfungen bleiben erhalten, wenn ein Image erneut gescannt wird.
  • Grenzen. Bei einem Multi-Architektur-Image wird nur die Variante linux/amd64 gescannt. Ein Image, das größer als 2 GiB ist (die Summe seiner Layer, wie sie in der Registry gespeichert sind), wird nicht gezogen und daher nicht gescannt.

Derselbe Durchlauf scannt auch den Standard-Branch jedes Repositorys (trivy fs und trivy config).

Derselbe Image-Pull wird außerdem mit der OSV-Liste bösartiger Pakete abgeglichen. Diese Prüfung ist unter Bösartige Pakete beschrieben; ein Pull eines Images mit einem offenen Befund wird abgewiesen, unabhängig von der Sperraktion unten. Seit dem 2026-10-11 gilt dasselbe für Images, die über den Dependency-Proxy zwischengespeichert werden (siehe unten).

Über den Dependency-Proxy zwischengespeicherte Images

Images, die deine Organisation über den Dependency-Proxy zieht, werden genauso gescannt wie die Images, die du pushst.

  • Wann. Ein Image wird für seinen ersten Scan eingereiht, wenn deine Organisation es zum ersten Mal zwischenspeichert, und wird normalerweise innerhalb von 15 Minuten gescannt. Danach wird es bei jeder Änderung der Schwachstellendatenbank von Trivy erneut gescannt, im selben täglichen Durchlauf wie deine eigenen Images, solange es in deinem Cache bleibt.
  • Was gescannt wird. Das Einzelplattform-Image, das dein Client gezogen hat (bei einem Multi-Architektur-Tag die Variante, die du gezogen hast), mit trivy image auf Schwachstellen und geleakte Secrets.
  • Wo Befunde gespeichert werden. Unter dem Schlüssel frem.sh/<org>/dependency-proxy/<upstream>/<repository>@sha256:<digest>, zum Beispiel .../dependency-proxy/docker.io/library/alpine@sha256:…, neben deinen eigenen Images unter Admin → Sicherheit › Befunde › Container-Images und in der Findings-API. Verwerfungen bleiben erhalten, wenn das Image erneut gescannt wird.
  • Sperraktion. block_pull gilt auch für Images über den Proxy: Ein Pull über den Proxy für ein Image mit einem nicht verworfenen hohen oder kritischen Befund wird mit 403 DENIED abgewiesen.
  • Bösartige Pakete. Die Sprachpakete in einem zwischengespeicherten Image werden wie bei gepushten Images mit den OSV-Advisories für bösartige Pakete abgeglichen, und ein Treffer ist ein Befund im Tab Malware. Seit dem 2026-10-11 wird ein Pull eines zwischengespeicherten Images mit einem offenen Befund abgewiesen, wie bei einem gepushten Image; siehe Dependency-Proxy.
  • Geteilte Scans. Ein Image wird über seinen Digest identifiziert. Speichern mehrere Organisationen dasselbe Image zwischen, wird es einmal gescannt, und jede Organisation erhält ihre eigene Kopie der Befunde. Du siehst immer nur Befunde für Images in deinem eigenen Cache.
  • Wenn ein Image deinen Cache verlässt. Seine Befunde bleiben, solange das Image noch genutzt wird: in den letzten 30 Tagen von deiner Organisation über den Proxy gezogen oder in einem deiner Deployments genannt (das Zugriffsprotokoll der Registry und die Deployment-Datensätze zeigen das). Danach werden sie beim nächsten Durchlauf entfernt. Ein Image, das erneut gezogen wird, nachdem seine Befunde entfernt wurden, erhält sie beim nächsten Durchlauf aus dem Scan seines Digests zurück.

Images, die deine Helm-Charts deployen

Die Images, die ein Helm-Chart in deinem Repository deployt, werden auf Schwachstellen gescannt, und die Befunde werden im Check fremforge/image-scan des Repositorys gemeldet.

  • Was gelesen wird. Jedes Chart im Repository, also jedes Verzeichnis mit einer Chart.yaml. In values.yaml: ein image: mit einer Referenz (image: nginx:1.25.3) und die aufgeteilte Form, die Charts am häufigsten verwenden (image: mit registry, repository, tag und digest darunter). Ein aufgeteiltes Image ohne Tag oder mit tag: "" ist die appVersion des Charts, also das, was {{ .Values.image.tag | default .Chart.AppVersion }} deployt. In templates/: eine image:-Zeile mit einer literalen Referenz. Schlüssel wie initImage oder sidecarImage zählen ebenfalls; imagePullPolicy und imagePullSecrets nicht.
  • Was nicht gelesen wird. Templates werden nicht gerendert, und es läuft kein Chart-Code. Ein durch Template-Logik zusammengesetztes Image, andere Values-Dateien als values.yaml (ein Overlay, das du bei der Installation wählst) und abhängige Charts, die nicht im Repository liegen, sind daher nicht abgedeckt. Eine Referenz ohne Registry ist Docker Hub (nginx ist docker.io/library/nginx), und eine ohne Tag ist latest.
  • Wie das Image geholt wird. Über den Dependency-Proxy deiner Organisation, so wie ein Pull von dir: Es gelten deine Upstream-Zugangsdaten, deine Paketrichtlinie, der Malware-Scan und der Cache. Hat deine Organisation keinen Dependency-Proxy, holt der eigene Proxy von fremforge es, anonym und nur von Registrys, die das erlauben (zum Beispiel ghcr.io, quay.io, registry.k8s.io). Ein Image, das dein Proxy abweist (etwa eines, das deine Richtlinie blockiert), wird nicht gescannt. Das linux/amd64-Image wird mit trivy image auf Schwachstellen gescannt.
  • Docker Hub braucht deine eigenen Zugangsdaten. Docker-Hub-Images werden nur mit den eigenen Docker-Hub-Zugangsdaten deiner Organisation gezogen, nie anonym und nie mit einem Konto von fremforge. Ohne sie wird ein Docker-Hub-Image in einem Chart nicht gescannt: Der Status fremforge/image-scan sagt das („N Docker Hub image(s) in Helm charts not scanned: add your Docker Hub credential“), und Sicherheit › Befunde › Container-Images listet die Images mit einem Link auf, um die Zugangsdaten unter Upstream-Zugangsdaten hinzuzufügen. Sobald du sie hinzufügst, werden die Images ab dem nächsten Scan des Repositorys gescannt; Befunde, die für eine andere Organisation gescannt wurden, werden deiner nie angezeigt.
  • Wann. Ein Image wird eingereiht, wenn ein Scan es zum ersten Mal nennt, und wird normalerweise innerhalb von 15 bis 30 Minuten gescannt; seine Befunde erscheinen beim nächsten Scan des Repositorys. Ein Image, das weiterhin genannt wird, wird alle paar Tage erneut gescannt, weil sich ein Tag verschieben kann und neue Advisories veröffentlicht werden.
  • Wie ein Befund aussieht. Das Ziel ist die Chart-Datei und das Image, zum Beispiel charts/web/values.yaml: docker.io/library/nginx:1.19.0, mit dem Paket, seiner Version und der korrigierten Version. Die Befunde durchlaufen die Schwelle und die Verwerfungen des Image-Scans, und ein Pull Request wird nur durch Befunde blockiert, die er hinzufügt.

Neuere Tags für Upstream-Images

Für ein Image, das du von Upstream ziehst, gibt es kein eigenes Dockerfile zu korrigieren. Deshalb sucht fremforge nach einem neueren Tag, das seine Befunde behebt. Für jedes zwischengespeicherte Image mit offenen hohen oder kritischen Befunden, höchstens einmal täglich:

  1. Das Tag, das du gezogen hast. Der Digest wird dem Tag zugeordnet, über das deine Organisation ihn gezogen hat, über den Multi-Architektur-Index, wenn es einen gibt.
  2. Der Kandidat. Das ist das neueste Tag derselben Linie, das dieselbe Form und dieselben Zahlen bis auf die letzte hat: 3.12.4-slim zu 3.12.10-slim oder 16.4 zu 16.10. Es ist nie eine andere Major- oder Minor-Version, denn das wäre eine Migration und keine Korrektur. Ein Tag ohne eine solche Linie, etwa 3.12-slim, 16 oder pg17, wird stattdessen auf einen Neubau geprüft: dasselbe Tag, das upstream neu gebaut wurde, seit du es gezogen hast.
  3. Was er behebt. Der Kandidat wird gezogen und mit demselben Scanner gescannt. Jeder deiner offenen Befunde, den der Kandidat nicht mehr hat, zählt als behoben.

Der Befund zeigt dann eines davon:

AngezeigtBedeutung
Neueres Tag <tag> behebt N der Befunde dieses ImagesWechsle zu diesem Tag. PR mit <tag> öffnen öffnet den unten beschriebenen Pull Request
Upstream neu gebaut: <tag> erneut ziehen behebt NDasselbe Tag ist upstream neuer. Ziehe es erneut; in deinem Repository ändert sich nichts, sofern keine Referenz einen Digest pinnt
Upstream-Image, kein neueres korrigiertes TagKein Tag derselben Linie und kein Neubau hat weniger dieser Befunde
Upstream-Image: neuere Tags noch nicht geprüftNoch nicht geprüft, oder die Registry gehört nicht zu denen, von denen fremforge Tags liest (das sind Docker Hub, GHCR, Quay, MCR, ECR Public und registry.k8s.io)

Dasselbe steht in der Findings-API als upstream_update.

Der Pull Request. PR mit <tag> öffnen oder POST /orgs/{org}/dependency-updates/security-fixes/image-ref-requests mit image_finding_id durchsucht die Repositorys deiner Organisation nach Referenzen auf das Image mit dem Tag, das du gezogen hast. Das umfasst Docker-Compose-Dateien, Kubernetes-Manifeste, Helm-Values mit repository: und tag: in getrennten Zeilen sowie CI-Einträge image: und container:, unter jedem der Namen, unter denen das Image bekannt ist:

  • frem.sh/<org>/dependency-proxy/docker.io/library/python:3.12.4-slim
  • docker.io/library/python:3.12.4-slim
  • python:3.12.4-slim

Jedes Repository mit einer Referenz erhält einen Pull Request für eine Sicherheitskorrektur, der alle seine Referenzen verschiebt. Ein Digest-Pin wechselt zum Digest des Kandidaten. Es gelten der übliche Branch, das Label, die Limits und die Merge-Einstellung. Ein FROM im Dockerfile wird hier nicht geändert: Das ist die Basis-Image-Korrektur. Referenziert kein Repository das Image, wird nichts geöffnet, und der Befund zeigt weiterhin das Tag, das ihn behebt. Pro Anfrage werden bis zu 60 Repositorys und 400 YAML-Dateien gelesen.

Aus deinem eigenen Workflow scannen

Du kannst auch in der CI scannen und die Ergebnisse einsenden. Das Runner-Image von fremforge enthält Trivy. Dieses Beispiel scannt das Dateisystem des Repositorys (trivy fs: Abhängigkeiten und geleakte Secrets) und die IaC-Konfiguration (trivy config: Dockerfiles, Compose-Dateien) und sendet die Befunde dann per POST:

- name: Scan + push findings
  run: |
    trivy fs --format json --output trivy-fs.json .
    trivy config --format json --output trivy-config.json .
    curl -sS --fail \
      -H "Authorization: Bearer ${{ secrets.FREMFORGE_API_TOKEN }}" \
      -H 'Content-Type: application/json' \
      -d @<(jq -c '{
        image_ref: "'"${GITHUB_REPOSITORY}@${GITHUB_SHA}"'",
        commit_sha: "'"${GITHUB_SHA}"'",
        repo_full_name: "'"${GITHUB_REPOSITORY}"'",
        findings: [.Results[].Vulnerabilities[] | {
          cve_id: .VulnerabilityID,
          severity: (.Severity|ascii_downcase),
          package_name: .PkgName,
          installed_version: .InstalledVersion,
          fixed_version: .FixedVersion,
          title: .Title,
          target: .PkgPath
        }]
      }' trivy-fs.json) \
      "https://frem.sh/_app/api/v1/image-scans/findings"

Der PAT braucht den Scope findings:write.

Richtlinienschalter

Sicherheit › Richtlinien › Image-Richtlinien enthält diese Einstellungen (bis zum 2026-10-15 lagen sie unter Befunde › Container-Images):

  • Aktiviert: Ist es aus, antwortet der Ingest mit 202 {accepted: 0, status: 'disabled'} (die Daten werden weiterhin geparst, aber verworfen). Nützlich für Teams vor dem Start, die den Scan laufen lassen wollen, ohne dass er etwas blockiert.
  • Sperraktion: none | warn | block_pull:
    • none: Befunde speichern, keinen Commit-Status setzen. Nur Erkennung auf der Build-Seite. Pulls werden nicht geprüft.
    • warn: Befunde speichern, Commit-Status success mit einer Beschreibung der Anzahl setzen. Der PR-Merge wird nicht blockiert. Pulls werden geprüft, aber nie abgewiesen: Ein Pull, den block_pull abweisen würde, wird bedient und als would_block im Zugriffsprotokoll der Registry sowie als Audit-Ereignis registry.pull.would_block erfasst (siehe Audit-Log und SIEM).
    • block_pull: Befunde speichern, Commit-Status failure setzen, wenn mindestens ein nicht verworfener Befund ab deiner Sperrschwelle existiert. Bei einem Pull Request zählen nur Befunde, die der Pull Request einführt (siehe Pull Requests: Nur neue Befunde blockieren). Ein Branch-Schutz mit dem erforderlichen Status-Check fremforge/image-scan blockiert dann den Merge. Außerdem blockiert es Pulls: Ein docker pull (per Tag oder Digest) eines Images mit einem solchen Befund wird mit 403 DENIED abgewiesen, und die Meldung nennt die Schwelle und verlinkt die Befunde. Verwirf oder behebe die Befunde, um es freizugeben. Jede Abweisung ist das Audit-Ereignis registry.pull.blocked.
  • Signierte Images verlangen: Ist es an, nimmt der Ingest zusätzlich ein Feld signature_status an (signed / unsigned / unknown). Nicht signierte Images (oder signierte Images eines Ausstellers, der nicht auf der Allowlist steht) erhalten einen synthetischen kritischen Befund, sodass derselbe Block-Pull-Weg die Signaturrichtlinie durchsetzt. Die Signaturseite ist unter Cosign beschrieben.

Sperrschwelle

Blockiert wird von Befunden ab einem Schweregrad, der pro Organisation festgelegt wird. Es ist block_severity im Eintrag image_scan deiner organisationsweiten Scan-Richtlinie, dieselbe Einstellung für Merges und für Pulls:

block_severityBlockiert bei
criticalkritisch
high (Standard)hoch und kritisch
mediummittel, hoch und kritisch
lowjedem Befund

Setze sie mit PUT /_app/api/v1/orgs/<org>/policy:

curl -X PUT -H "Authorization: Bearer ${FREMFORGE_PAT}" -H 'Content-Type: application/json' \
  -d '{"scan_policy": {"image_scan": {"mode": "enforce", "block_severity": "critical"}}}' \
  "https://frem.sh/_app/api/v1/orgs/acme/policy"

oder in der Konfiguration als Code als scan_policy = { image_scan = { mode = "enforce", block_severity = "critical" } }. null oder kein Eintrag image_scan bedeutet high, was jede Organisation hatte, bevor sich die Schwelle einstellen ließ. Jeder andere Wert wird mit 400 abgewiesen. Der Token braucht policy:write. Die Konsole hat dafür noch kein Bedienelement.

Nur der organisationsweite Eintrag zählt. Eine Übersteuerung der Scan-Richtlinie pro Repository kann ein block_severity tragen, aber ein Image-Pull ist an kein einzelnes Repository gebunden, daher lesen die Image-Gates es nicht.

Pull Requests: Nur neue Befunde blockieren

Der Check fremforge/image-scan an einem Pull Request schlägt nur bei Befunden fehl, die der Pull Request einführt. fremforge vergleicht die Befunde des Pull Requests ab deiner Sperrschwelle mit dem letzten Scan des Branches, in den er gemergt wird. Ein Befund, den dieser Branch bereits hat, blockiert nicht, sodass ein Pull Request, der nichts Verwundbares ändert, in einem Repository mit offenen Befunden gemergt werden kann. Der Status lautet dann zum Beispiel No new high/critical findings; 7 already on main.

  • Ein Befund ist derselbe, wenn seine Advisory, sein Paket und seine Datei gleich sind; das ist die Identität, über die auch eine Verwerfung übernommen wird. Dieselbe Advisory in einer anderen Datei ist ein neuer Befund.
  • Ein Pull Request, der einem Repository mit vielen bestehenden Befunden einen neuen hohen Befund hinzufügt, schlägt fehl: 1 new high/critical finding(s) …; merge blocked; 30 already on main.
  • Ein Pull Request, der einige Befunde behebt und keine hinzufügt, besteht.
  • Bestehende Befunde werden nicht versteckt. Sie werden aufgelistet, auf dein SLA angerechnet und sperren Image-Pulls wie zuvor.

Der Branch wird nach einem Push auf deinen Standard-Branch gescannt, höchstens einmal pro Stunde, wobei der neueste Push gewinnt. Ein Befund, der seit dem letzten Scan in den Branch gelangt ist, zählt daher bis zum nächsten Scan weiterhin als Befund des Pull Requests. Umgekehrt passiert das nie: Ein neuer Befund wird nie für einen bestehenden gehalten.

Gibt es keinen Scan des Branches, in den der Pull Request gemergt wird, blockiert wie zuvor jeder Befund ab der Schwelle. Beim Standard-Branch fordert der Scan dieses Pull Requests außerdem einen Scan des Branches an. Sobald der erste Scan des Branches erfasst ist, scannt fremforge die offenen Pull Requests in diesen Branch, deren Check inzwischen fehlgeschlagen ist, von selbst erneut (höchstens 20 pro Repository, die zuletzt aktualisierten), sodass sie grün werden oder nur noch wegen dessen rot bleiben, was sie hinzufügen, ohne erneuten Lauf oder neuen Push. Ein Pull Request in einen anderen Branch zählt immer jeden Befund.

Das Pull-Gate für Images ist unverändert: Ein Pull eines Images mit einem nicht verworfenen Befund ab der Schwelle wird unter block_pull abgewiesen, ob der Befund neu ist oder nicht.

Audit-Log und SIEM

Jede Pull-Entscheidung der Image-Gates wird in dein Audit-Log geschrieben und von dort an dein SIEM weitergegeben:

  • registry.pull.blocked: Ein Pull wurde abgewiesen, durch block_pull (Gate image_scan), durch den Malware-Abgleich (image_malware), durch einen Layer, den ClamAV als infiziert erkannt hat oder nicht scannen konnte (registry_scan, oder registry_blob für einen direkt per Digest abgerufenen Layer), oder durch die Prüfung der Image-Signatur (signature).
  • registry.pull.would_block: Ein Pull wurde bedient, den eines dieser Gates abgewiesen hätte, unter deinem warn oder während ein Gate im Beobachtungsmodus läuft.

Es gibt ein Ereignis pro Image-Digest, Gate und Entscheidung pro Stunde; das nächste Ereignis für denselben Digest trägt die Zahl der vorherigen Stunde.

Wo Befunde erscheinen

Admin → Sicherheit › Befunde › Container-Images listet Scans nach Image-Referenz + Commit + PR auf. Jede Zeile zeigt:

  • Anzahl nach Schweregrad (kritisch / hoch / mittel / niedrig)
  • Anzahl behebbarer Befunde (Befunde mit einer fixed_version, die nicht null ist)
  • Status (success / failure / skipped) + den gesetzten Commit-Status
  • Links zu den Upstream-Advisories (CVE, GHSA, wenn vorhanden)

Wie ein Befund in der Datenbank aussieht

Gespeicherte Felder pro Befund: cve_id, severity, package, installed_version, fixed_version, target (der Dateipfad im Image) und die scan_run_id, die Befunde desselben Laufs verbindet. Die vollständige Trivy-Ausgabe wird NICHT gespeichert, nur die Zeilen pro Befund, also die abfragbare Form.

Aufbewahrung

Befunde folgen der Audit-Aufbewahrungsrichtlinie des Mandanten. Abgelaufene Befunde behalten ihren Hash in der Kette (Manipulationsnachweis), verlieren aber den Inhalt der Zeile.

Siehe auch

  • Bösartige Pakete: Derselbe Image-Pull wird auch mit OSV-Advisories MAL-* abgeglichen; ein Pull eines Images mit einem offenen Befund wird unabhängig von der oben genannten Sperraktion abgewiesen.
  • Cosign: Signieren von Images und Richtlinie für signierte Images.
  • SBOMs: das Paketinventar im Image (unabhängig vom Schwachstellenstatus).
  • OSV-Empfehlungen: die Ergänzung auf der Quellseite (Schwachstellen in deinen Abhängigkeiten, bevor sie ausgeliefert werden).
  • API-Referenz: der Ingest-Endpunkt POST /_app/api/v1/image-scans/findings (über den Runner-PAT auf den Mandanten beschränkt).