Gå til hovedindhold

Scanning af container-images

Scanning af container-images svarer på spørgsmålet hvad i dette image er sårbart og supplerer SBOM’en, der svarer på hvad er der i dette image. Fund kommer fra to kilder, og begge vises ved det image eller den commit, de beskriver.

Sådan virker det

Den daglige gennemgang af registry’et

fremforge scanner selv container-images i din organisations pakke-registry. Du skal ikke tilføje noget til dine workflows.

  • Hvad der scannes. Hvert tag af hvert container-image. Hvert tag slås op til sin manifest-digest, og imaget scannes med trivy image for sårbarheder og lækkede secrets, herunder OS-pakker og sprogafhængigheder i hvert lag.
  • Hvornår. En gang om dagen, efter at Trivys sårbarhedsdatabase er opdateret. En digest, der allerede er scannet mod den samme databaseversion, springes over, så en nyligt offentliggjort CVE rejses mod et image, der ikke har ændret sig.
  • Hvor fund gemmes. Under imagets digest (frem.sh/<org>/<image>@sha256:<digest>). Afvisninger følger med, når et image scannes igen.
  • Begrænsninger. For et image til flere arkitekturer scannes kun varianten linux/amd64. Et image større end 2 GiB (summen af dets lag, som de ligger i registry’et) hentes ikke og scannes derfor ikke.

Den samme gennemgang scanner også hvert repositorys standardbranch (trivy fs og trivy config).

Det samme image-pull matches også mod OSV’s liste over ondsindede pakker. Det tjek er beskrevet i Ondsindede pakker; et pull af et image med et åbent fund afvises, uanset blokeringshandlingen nedenfor. Siden 2026-10-11 gælder det samme for images, der caches gennem dependency-proxyen (se nedenfor).

Images cachet gennem dependency-proxyen

Images, som din organisation henter gennem dependency-proxyen, scannes på samme måde som de images, du pusher.

  • Hvornår. Et image sættes i kø til sin første scanning, når din organisation cacher det første gang, og scannes normalt inden for 15 minutter. Derefter scannes det igen, hver gang Trivys sårbarhedsdatabase ændres, i den samme daglige gennemgang som dine egne images, så længe det ligger i din cache.
  • Hvad der scannes. Det image til én platform, som din klient hentede (for et tag til flere arkitekturer den variant, du hentede), med trivy image for sårbarheder og lækkede secrets.
  • Hvor fund gemmes. Under frem.sh/<org>/dependency-proxy/<upstream>/<repository>@sha256:<digest>, for eksempel .../dependency-proxy/docker.io/library/alpine@sha256:…, ved siden af dine egne images under Administration → Sikkerhed › Fund › Container-images og i fund-API’et. Afvisninger følger med, når imaget scannes igen.
  • Blokeringshandling. block_pull gælder også for images via proxyen: et pull gennem proxyen af et image med et ikke-afvist high- eller critical-fund afvises med 403 DENIED.
  • Ondsindede pakker. Sprogpakkerne i et cachet image matches mod OSV’s advisories om ondsindede pakker, som for pushede images, og et match er et fund på fanen Malware. Siden 2026-10-11 afvises et pull af et cachet image med et åbent fund, som for et pushet image; se Afhængighedsproxy.
  • Delte scanninger. Et image identificeres ved sin digest, så når flere organisationer cacher det samme image, scannes det én gang, og hver organisation får sin egen kopi af fundene. Du ser kun fund for images i din egen cache.
  • Når et image forlader din cache. Dets fund bliver, så længe imaget stadig er i brug: hentet gennem proxyen af din organisation eller nævnt i en af dine deployments inden for de seneste 30 dage (registry’ets adgangslog og deployment-registreringerne viser hvilke). Derefter fjernes de ved næste gennemgang. Et image, der hentes igen, efter at dets fund er fjernet, får dem tilbage ved næste gennemgang, fra scanningen af dets digest.

Images, som dine Helm-charts deployer

De images, som et Helm-chart i dit repository deployer, scannes for sårbarheder, og fundene rapporteres på repositoryets tjek fremforge/image-scan.

  • Hvad der læses. Hvert chart i repositoryet, det vil sige hver mappe med en Chart.yaml. I values.yaml: et image: med en reference (image: nginx:1.25.3) og den opdelte form, som charts oftest bruger (image: med registry, repository, tag og digest under sig). Et opdelt image uden tag, eller med tag: "", er chartets appVersion, hvilket er det, {{ .Values.image.tag | default .Chart.AppVersion }} deployer. I templates/: en image:-linje med en bogstavelig reference. Nøgler som initImage eller sidecarImage tæller også med; imagePullPolicy og imagePullSecrets gør ikke.
  • Hvad der ikke læses. Templates renderes ikke, og ingen chart-kode køres, så et image, der sammensættes af template-logik, andre values-filer end values.yaml (et overlay, du vælger ved installationen) og afhængige charts, der ikke ligger i repositoryet, er ikke dækket. En reference uden registry er Docker Hub (nginx er docker.io/library/nginx), og en uden tag er latest.
  • Sådan hentes imaget. Gennem din organisations dependency-proxy, som dit eget pull ville blive det: dine upstream-legitimationsoplysninger, pakkepolitik, malware-scanning og cache gælder. Hvis din organisation ikke har en dependency-proxy, henter fremforges egen proxy det, anonymt og kun fra registries, der tillader det (for eksempel ghcr.io, quay.io, registry.k8s.io). Et image, som din proxy afviser (for eksempel et, din politik blokerer), scannes ikke. linux/amd64-imaget scannes med trivy image for sårbarheder.
  • Docker Hub kræver dine egne legitimationsoplysninger. Docker Hub-images hentes kun med din organisations egne Docker Hub-legitimationsoplysninger, aldrig anonymt og aldrig med en fremforge-konto. Uden dem bliver et Docker Hub-image i et chart ikke scannet: statussen fremforge/image-scan siger det (“N Docker Hub image(s) in Helm charts not scanned: add your Docker Hub credential”), og Sikkerhed › Fund › Container-images viser imagesne med et link til at tilføje legitimationsoplysningerne under Upstream-legitimationsoplysninger. Imagesne scannes, når du har tilføjet dem, fra repositoryets næste scanning; fund, der er scannet for en anden organisation, vises aldrig for din.
  • Hvornår. Et image sættes i kø første gang, en scanning nævner det, og scannes normalt inden for 15 til 30 minutter; dets fund vises ved repositoryets næste scanning. Et image, der stadig nævnes, scannes igen med få dages mellemrum, fordi et tag kan flytte sig, og der udgives nye advisories.
  • Sådan ser et fund ud. Målet er chart-filen og imaget, for eksempel charts/web/values.yaml: docker.io/library/nginx:1.19.0, med pakken, dens version og den rettede version. Fundene går gennem image-scanningens tærskel og afvisninger, og en pull request blokeres kun af de fund, den tilføjer.

Nyere tags til upstream-images

Et image, du henter fra upstream, har ingen Dockerfile af dine at rette, så fremforge leder efter et nyere tag, der retter dets fund. For hvert cachet image med åbne high- eller critical-fund, højst en gang om dagen:

  1. Det tag, du hentede. Digesten matches til det tag, din organisation hentede den med, via indekset for flere arkitekturer, når der er et.
  2. Kandidaten. Det er det nyeste tag i samme linje, som har samme form og de samme tal bortset fra det sidste: 3.12.4-slim til 3.12.10-slim eller 16.4 til 16.10. Det er aldrig en anden major- eller minor-version, for det er en migrering og ikke en rettelse. Et tag uden en sådan linje, som 3.12-slim, 16 eller pg17, tjekkes i stedet for en genbygning: det samme tag, genbygget upstream, siden du hentede det.
  3. Hvad den retter. Kandidaten hentes og scannes med den samme scanner. Hvert af dine åbne fund, som kandidaten ikke længere har, tæller som rettet.

Fundet siger derefter et af følgende:

VistBetydning
Nyere tag <tag> retter N af imagets fundSkift til det tag. Åbn PR med <tag> åbner pull requesten nedenfor
Genbygget upstream: hent <tag> igen, så rettes N af imagets fundDet samme tag er nyere upstream. Hent det igen; intet ændres i dit repository, medmindre en reference fastlåser en digest
Upstream-image, intet nyere rettet tagIntet tag i samme linje og ingen genbygning har færre af disse fund
Upstream-image: nyere tags er ikke tjekket endnuIkke tjekket endnu, eller registry’et er ikke et, fremforge læser tags fra (det er Docker Hub, GHCR, Quay, MCR, ECR Public og registry.k8s.io)

Det samme findes i fund-API’et som upstream_update.

Pull requesten. Åbn PR med <tag>, eller POST /orgs/{org}/dependency-updates/security-fixes/image-ref-requests med image_finding_id, søger i din organisations repositories efter referencer til imaget med det tag, du hentede. Det dækker docker compose-filer, Kubernetes-manifester, Helm-values med repository: og tag: på hver sin linje samt CI-poster med image: og container:, under alle de navne, imaget går under:

  • 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

Hvert repository med en reference får én pull request med en sikkerhedsrettelse, der flytter alle dets referencer. En fastlåst digest flyttes til kandidatens digest. Den har den sædvanlige branch, label, begrænsninger og merge-indstilling. En FROM i en Dockerfile ændres ikke her: det er rettelsen af base-imaget. Når intet repository refererer til imaget, åbnes der intet, og fundet viser fortsat det tag, der retter det. Op til 60 repositories og 400 YAML-filer læses pr. anmodning.

Scanning fra dit eget workflow

Du kan også scanne i CI og sende resultaterne ind. fremforges runner-image indeholder Trivy. Dette eksempel scanner repositoryets filsystem (trivy fs — afhængigheder + lækkede secrets) og IaC-konfigurationen (trivy config — Dockerfiles, Compose-filer) og sender derefter fundene med 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"

PAT’en skal have scopet findings:write.

Politikindstillinger

Sikkerhed › Politikker › Image-politikker har disse indstillinger (indtil 2026-10-15 lå de under Fund › Container-images):

  • Slået til — når den er slået fra, svarer indlæsningen 202 {accepted: 0, status: 'disabled'} (dataene parses stadig, men kasseres). Nyttigt for teams før lancering, der vil have scanningen til at køre uden at blokere.
  • Blokeringshandling — none | warn | block_pull:
    • none — gemmer fund og sætter ingen commit-status. Kun detektion på byggesiden. Pulls tjekkes ikke.
    • warn — gemmer fund og sætter commit-statussen success med en beskrivelse, der nævner antallet. Merge af pull requests blokeres ikke. Pulls tjekkes, men afvises aldrig: et pull, som block_pull ville afvise, leveres og registreres som would_block i registry’ets adgangslog og som revisionshændelsen registry.pull.would_block (se Revisionslog og SIEM).
    • block_pull — gemmer fund og sætter commit-statussen failure, når der findes mindst ét ikke-afvist fund på eller over din blokeringstærskel. På en pull request tæller kun de fund, pull requesten tilføjer (se Pull requests: kun nye fund blokerer). Branch protection med det påkrævede statustjek fremforge/image-scan blokerer så merge. Den blokerer også pulls: et docker pull (via tag eller digest) af et image med et sådant fund afvises med 403 DENIED, og meddelelsen nævner tærsklen og linker til fundene. Afvis eller ret fundene for at fjerne blokeringen. Hver afvisning er revisionshændelsen registry.pull.blocked.
  • Kræv signerede images — når den er slået til, accepterer indlæsningen også et felt signature_status (signed / unsigned / unknown). Usignerede images (eller signerede images fra en udsteder, der ikke er på tilladelseslisten) får et syntetisk critical-fund, så den samme blokering af pulls håndhæver signeringspolitikken. Se Cosign for signeringssiden.

Blokeringstærskel

De fund, der blokerer, er dem på eller over én alvorlighed, som sættes pr. organisation. Det er block_severity i image_scan-posten i din organisationsdækkende scanningspolitik, den samme indstilling for merges og for pulls:

block_severityBlokerer ved
criticalcritical
high (standard)high og critical
mediummedium, high og critical
lowalle fund

Sæt den med 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"

eller i konfiguration som kode som scan_policy = { image_scan = { mode = "enforce", block_severity = "critical" } }. null, eller ingen image_scan-post, betyder high, som er det, alle organisationer havde, før tærsklen kunne sættes. Enhver anden værdi afvises med 400. Tokenet skal have policy:write. Konsollen har endnu ingen indstilling til det.

Kun den organisationsdækkende post tæller. En tilsidesættelse af scanningspolitikken pr. repository kan have en block_severity, men et image-pull er ikke knyttet til ét repository, så image-gatene læser den ikke.

Pull requests: kun nye fund blokerer

Tjekket fremforge/image-scan på en pull request fejler kun på fund, som pull requesten tilføjer. fremforge sammenligner pull requestens fund på eller over din blokeringstærskel med den seneste scanning af den branch, den merges ind i. Et fund, som den branch allerede har, blokerer ikke, så en pull request, der ikke ændrer noget sårbart, kan merges i et repository med åbne fund. Statussen lyder så for eksempel No new high/critical findings; 7 already on main.

  • Et fund er det samme, når dets advisory, pakke og fil er de samme, den identitet, som en afvisning følger med efter. Den samme advisory i en anden fil er et nyt fund.
  • En pull request, der tilføjer ét nyt high-fund til et repository med mange eksisterende, fejler: 1 new high/critical finding(s) …; merge blocked; 30 already on main.
  • En pull request, der retter nogle fund og ikke tilføjer nogen, består.
  • Eksisterende fund skjules ikke. De vises, tæller med i din SLA og blokerer image-pulls som før.

Branchen scannes efter et push til din standardbranch, højst en gang i timen, og det nyeste push vinder. Et fund, der er nået ind på branchen siden dens seneste scanning, tæller derfor stadig som pull requestens indtil næste scanning. Det er aldrig omvendt: et nyt fund forveksles aldrig med et eksisterende.

Når der ikke er nogen scanning af den branch, pull requesten merges ind i, blokerer hvert fund på eller over tærsklen, som før. For standardbranchen beder pull requestens scanning også om en scanning af branchen. Når branchens første scanning er registreret, scanner fremforge af sig selv de åbne pull requests ind i den igen, hvis tjek fejlede i mellemtiden (højst 20 pr. repository, de senest opdaterede), så de bliver grønne eller kun forbliver røde på det, de tilføjer, uden en genkørsel eller et nyt push. En pull request ind i en hvilken som helst anden branch tæller altid alle fund.

Gaten for image-pulls er uændret: et pull af et image med et ikke-afvist fund på eller over tærsklen afvises under block_pull, uanset om fundet er nyt eller ej.

Revisionslog og SIEM

Hver pull-afgørelse fra image-gatene skrives i din revisionslog og derfra til dit SIEM:

  • registry.pull.blocked — et pull blev afvist: af block_pull (gate image_scan), af malware-matching (image_malware), af et lag, som ClamAV fandt inficeret eller ikke kunne scanne (registry_scan, eller registry_blob for et lag, der hentes direkte via digest), eller af tjekket af image-signaturer (signature).
  • registry.pull.would_block — et pull blev leveret, som en af disse gates ville have afvist: under din warn, eller mens en gate kører i observationstilstand.

Der er én hændelse pr. image-digest, gate og afgørelse pr. time; den næste hændelse for samme digest bærer den foregående times antal.

Hvor fund vises

Administration → Sikkerhed › Fund › Container-images viser scanninger efter image-ref + commit + PR. Hver række viser:

  • Antal pr. alvorlighed (critical / high / medium / low)
  • Antal, der kan rettes (fund med en fixed_version, der ikke er null)
  • Status (success / failure / skipped) + den commit-status, der blev sat
  • Links til upstream-advisories (CVE, GHSA når den findes)

Sådan ser et fund ud i databasen

Felter, der gemmes pr. fund: cve_id, severity, package, installed_version, fixed_version, target (filstien i imaget) og scan_run_id, der binder de fund sammen, som den samme kørsel har produceret. Det fulde Trivy-output gemmes IKKE — kun rækkerne pr. fund, som er den form, der kan forespørges.

Opbevaring

Fund følger revisionslogens opbevaringspolitik pr. tenant. Fund, der er blevet for gamle, beholder deres hash i kæden (manipulationssikring), men mister indholdet pr. række.

Se også

  • Ondsindede pakker — det samme image-pull matches også mod OSV’s MAL-*-advisories; et pull af et image med et åbent fund afvises uanset blokeringshandlingen ovenfor.
  • Cosign — signering af images + politik for signerede images.
  • SBOM’er — pakkeoversigt for imaget (uafhængigt af sårbarhedstilstanden).
  • OSV-anbefalinger — supplementet på kildesiden (sårbarheder i dine afhængigheder, før de udgives).
  • API-reference — indlæsnings-endpointet POST /_app/api/v1/image-scans/findings (afgrænset til tenanten via runnerens PAT).