Scanner les images conteneurs avant de les déployer est un contrôle indispensable, mais ce n'est qu'un maillon de la supply chain security. Un scanner compare les composants d'une image avec des bases de données de CVE pour identifier les failles connues, mais il ne prouve pas l'intégrité, la provenance ou la conformité de l'image.
Ce guide poursuit deux objectifs : montrer les commandes et concepts autour du scan de vulnérabilités dans l'écosystème Kubernetes, et expliquer pourquoi je privilégie Grype comme scanner principal en production depuis l'incident majeur ayant touché l'écosystème Trivy en mars 2026. Trivy reste néanmoins très présent dans l'écosystème et dans les préparations à l'examen CKS.
Une mise au point s'impose avant d'aller plus loin, parce qu'elle change la recommandation de ce guide. En mars 2026, l'écosystème Trivy a subi une compromission majeure : binaire malveillant publié, tags GitHub Actions réécrits en masse, images Docker Hub compromises, le tout à partir d'identifiants volés. Aqua a publié le détail et les remédiations.
Position retenue ici : je ne recommande plus Trivy comme scanner principal en production. Ses commandes restent dans ce guide, parce qu'elles servent à lire des pipelines existants et qu'elles font partie de la culture du domaine, mais le chemin recommandé passe désormais par Grype. L'ironie est instructive et vaut d'être retenue : un outil de sécurité de la chaîne d'approvisionnement fait partie de cette chaîne, et se compromet comme le reste.
Prérequis
Section intitulée « Prérequis »- Docker ou containerd installé
- Accès à des images à scanner
- Bases en sécurité : notions de CVE, CVSS, EPSS (→ guide CVE)
Comprendre une CVE
Section intitulée « Comprendre une CVE »Avant de scanner, il faut savoir lire les résultats. Une CVE (Common Vulnerability and Exposure) est un identifiant unique pour une faille de sécurité connue. Format : CVE-ANNÉE-NUMÉRO.
Les scores de sévérité CVSS v3 :
| Score | Sévérité | Action recommandée |
|---|---|---|
| 9.0 – 10.0 | CRITICAL | Corriger immédiatement, bloquer le déploiement |
| 7.0 – 8.9 | HIGH | Traiter en priorité, dans les 48h |
| 4.0 – 6.9 | MEDIUM | Planifier dans le sprint suivant |
| 0.1 – 3.9 | LOW | Backlog, corriger lors des mises à jour régulières |
Grype, mon choix principal pour le scan de vulnérabilités d'images
Section intitulée « Grype, mon choix principal pour le scan de vulnérabilités d'images »Grype est développé par Anchore. Je le privilégie pour le scan d'images et de SBOM, pour son intégration avec Syft, et parce qu'il prend en compte EPSS, KEV et OpenVEX : autrement dit, il priorise le risque réellement exploité.
Installation
Section intitulée « Installation »Installez Grype depuis un binaire officiel vérifié par empreinte SHA256,
jamais par un script curl | sh : récupérez l'archive et les checksums des
GitHub Releases, contrôlez l'empreinte, puis extrayez le binaire.
GRYPE_VERSION=0.118.0cd /tmpbase="https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}"curl -sSfLO "${base}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz"curl -sSfLO "${base}/grype_${GRYPE_VERSION}_checksums.txt"grep "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" "grype_${GRYPE_VERSION}_checksums.txt" | sha256sum -c -sudo tar -C /usr/local/bin -xzf "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" grypegrype versionCommandes essentielles
Section intitulée « Commandes essentielles »Grype prend en argument une référence d'image, un chemin local ou un SBOM, et choisit sa source d'analyse selon le préfixe utilisé. Sans option, il affiche toutes les vulnérabilités trouvées et se termine avec un code de retour 0 : c'est --fail-on qui transforme le scan en verrou de pipeline.
# Scanner une image depuis une registrygrype nginx:1.25.3
# Ne montrer que les CVE ayant un fix disponiblegrype nginx:1.25.3 --only-fixed
# Faire échouer si CVE CRITICAL détectée (CI/CD)grype nginx:1.25.3 --fail-on critical
# Sortie JSON pour parsinggrype nginx:1.25.3 -o json > report.json
# Depuis un SBOM Syftgrype sbom:/path/to/sbom.jsonCe qu'un rapport réel contient
Section intitulée « Ce qu'un rapport réel contient »Les chiffres ci-dessous viennent d'un scan de nginx:1.25.3, une image
volontairement ancienne. Ils ne valent pas pour la vôtre, mais leurs
proportions se retrouvent partout.
| Sévérité | Nombre |
|---|---|
| Critical | 48 |
| High | 238 |
| Medium | 240 |
| Low | 41 |
| Negligible | 129 |
| Unknown | 28 |
| Total | 724 |
Sept cent vingt-quatre lignes, personne ne les traite. Le chiffre qui rend le rapport exploitable est ailleurs :
grype nginx:1.25.3 --only-fixed320 vulnérabilités sur 724, soit 44 %, ont un correctif disponible. Les
quatre cent autres ne se règlent pas en montant une version : il n'y a rien à
monter. Commencer par --only-fixed trie donc entre ce sur quoi vous pouvez
agir aujourd'hui et ce qu'il faudra suivre ou accepter.
Le code de retour vaut 2, pas 1
Section intitulée « Le code de retour vaut 2, pas 1 »Détail qui casse un pipeline écrit de mémoire :
| Commande | Code de retour |
|---|---|
grype nginx:1.25.3 | 0 |
grype nginx:1.25.3 --fail-on critical | 2 |
Sans --fail-on, grype réussit quel que soit le nombre de vulnérabilités
trouvées : un job qui se contente de lancer la commande passera toujours au
vert. Avec l'option, le code est 2. Un test écrit if [ $? -eq 1 ] ne
déclenchera donc jamais ; testez la non-nullité.
Exemple de sortie
Section intitulée « Exemple de sortie »La sortie par défaut est un tableau, à raison d'une ligne par couple paquet et vulnérabilité. Un même identifiant apparaît donc plusieurs fois s'il touche plusieurs paquets de l'image, comme CVE-2024-0727 ci-dessous sur libssl3 et openssl.
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITYlibssl3 3.0.11 3.0.13 deb CVE-2024-0727 MEDIUMopenssl 3.0.11 3.0.13 deb CVE-2024-0727 MEDIUMexpat 2.5.0 (none) deb CVE-2023-52425 MEDIUMzlib1g 1:1.2.13 (none) deb CVE-2023-45853 MEDIUMLa colonne FIXED-IN est la plus importante : vide, elle signifie qu'aucun correctif n'est référencé pour ce paquet dans les sources du scanner. Il faut alors arbitrer : mise à jour applicative, changement d'image de base, suppression du composant, ou acceptation du risque, documentée et datée.
Workflow SBOM + Grype
Section intitulée « Workflow SBOM + Grype »Séparer l'inventaire de l'analyse change la façon de travailler : Syft produit la liste des composants une fois pour toutes, Grype la confronte aux bases de vulnérabilités du jour. L'image n'a plus besoin d'exister ni d'être téléchargée pour être réévaluée six mois plus tard.
# 1. Générer l'inventaire (SBOM)syft nginx:1.25.3 -o cyclonedx-json > sbom.json
# 2. Scanner le SBOM pour les CVEgrype sbom:sbom.json --fail-on high
# Avantage : le SBOM peut être archivé et re-scanné plus tard# quand de nouvelles CVE sont publiées sans rebuilder l'imageCe détour ne coûte rien en exhaustivité, et c'est ce qu'il fallait vérifier avant de le recommander. Sur la même image, le scan direct et le scan du SBOM rendent exactement le même nombre de correspondances, 724. Le SBOM produit par syft inventorie 3 863 composants pour environ 1,3 Mo de JSON : un artefact modeste, archivable avec la version, et rescannable dans six mois sans que l'image existe encore.
→ Voir le guide SBOM complet et le guide Syft.
Trivy, outil très présent dans l'écosystème, que je n'intègre plus comme scanner principal
Section intitulée « Trivy, outil très présent dans l'écosystème, que je n'intègre plus comme scanner principal »Trivy d'Aqua Security est un outil très fréquent dans les préparations CKS et dans la littérature autour de l'examen. Je conserve ses commandes pour la culture générale et pour lire des pipelines existants. En revanche, pour un nouveau pipeline de production, je préfère désormais Grype pour le scan de vulnérabilités d'images.
Installation
Section intitulée « Installation »L'installation passe par le dépôt APT signé d'Aqua : la clé publique est convertie en trousseau binaire dans /usr/share/keyrings/, puis référencée par signed-by= dans la source. Ce mécanisme limite la confiance accordée à cette clé au seul dépôt Trivy, contrairement à l'ancien apt-key add qui l'autorisait pour tous les dépôts du système.
# Debian/Ubuntusudo apt-get install wget apt-transport-https gnupg lsb-releasewget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/nullecho "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.listsudo apt-get update && sudo apt-get install trivyCommandes essentielles CKS
Section intitulée « Commandes essentielles CKS »Trivy s'organise par sous-commandes selon la cible : image pour un conteneur, fs pour un répertoire de code, config pour un manifeste. L'examen CKS attend surtout la maîtrise du filtrage par sévérité et de --exit-code, qui décide si le scan bloque ou non la chaîne.
# Scanner une imagetrivy image nginx:1.25.3
# Ignorer les CVE sans fix disponibletrivy image --ignore-unfixed nginx:1.25.3
# Filtrer par sévéritétrivy image --severity CRITICAL,HIGH nginx:1.25.3
# Code de sortie 1 si CVE critique (CI/CD)trivy image --exit-code 1 --severity CRITICAL nginx:1.25.3
# Générer un SBOM CycloneDXtrivy image --format cyclonedx nginx:1.25.3 > sbom.json
# Scanner le filesystem local (dépendances applicatives)trivy fs --scanners vuln .
# Scanner un manifest Kubernetestrivy config deployment.yamlComparer Grype vs Trivy
Section intitulée « Comparer Grype vs Trivy »Les deux outils font le même travail de base, la comparaison porte donc sur leur périmètre et sur leur façon de hiérarchiser les résultats. Trivy couvre plus de types de cibles (configuration, IaC, cluster), Grype se concentre sur les images et les SBOM en poussant plus loin la priorisation par le risque réel d'exploitation. Le tableau ci-dessous sert à trancher selon ce que vous avez à couvrir, pas à désigner un vainqueur absolu.
| Critère | Grype | Trivy |
|---|---|---|
| Cible principale | Images, file systems, SBOM | Images + config/IaC + Kubernetes + SBOM |
| EPSS / priorisation | Fort accent : EPSS, KEV, risk score, OpenVEX | Couverture plus large, priorisation moins mise en avant |
| SBOM | Scan de SBOM existants | Génération et exploitation natifs |
| Kubernetes natif | Pas d'operator officiel équivalent | Trivy Operator |
| Position dans ce guide | Mon choix principal pour la prod | Conservé pour culture écosystème / préparation |
Trivy Operator, Scan continu dans Kubernetes
Section intitulée « Trivy Operator, Scan continu dans Kubernetes »Le Trivy Operator installe un CRD dans le cluster et scanne automatiquement toutes les images à intervalle régulier. Les résultats sont stockés comme ressources Kubernetes.
# Installation via Helmhelm install trivy-operator aquasecurity/trivy-operator \ --namespace trivy-system \ --create-namespace \ --set trivy.ignoreUnfixed=true
# Voir les rapports de vulnérabilitéskubectl get vulnerabilityreports -A
# Détail d'un rapportkubectl describe vulnerabilityreport <name> -n <namespace>Intégration CI/CD
Section intitulée « Intégration CI/CD »Un scan n'a d'effet que s'il peut arrêter la chaîne. Le principe est identique sur toutes les plateformes : construire l'image, la scanner, et laisser le code de retour non nul du scanner faire échouer le job. Reste à décider du seuil de blocage, sachant qu'un --fail-on critical trop strict sur une image de base ancienne bloque tout le monde dès le premier jour.
Les exemples qui suivent montrent la mécanique minimale d'un scan, pas un pipeline de production. La différence tient en un point : ne téléchargez pas votre scanner depuis Internet à chaque exécution. C'est exactement le chemin par lequel l'incident de mars 2026 s'est propagé. Construisez une image de job interne, versionnée et validée, embarquant le scanner et ses bases.
GitHub Actions
Section intitulée « GitHub Actions »L'action de checkout est épinglée par SHA et non par tag, seule protection contre la réécriture d'un tag Git dans le dépôt de l'action, exactement le mode opératoire observé lors de l'incident de mars 2026. Le permissions: {} du workflow retire tout droit par défaut au GITHUB_TOKEN, et le job ne reprend que la lecture du dépôt.
name: scan-imageon: [push]
permissions: {}
jobs: scan: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: Build image run: docker build -t myapp:${{ github.sha }} . - name: Scanner avec Grype run: | GRYPE_VERSION=0.118.0 base="https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}" curl -sSfLO "${base}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz" curl -sSfLO "${base}/grype_${GRYPE_VERSION}_checksums.txt" grep "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" "grype_${GRYPE_VERSION}_checksums.txt" | sha256sum -c - sudo tar -C /usr/local/bin -xzf "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" grype grype myapp:${{ github.sha }} --fail-on criticalGitLab CI
Section intitulée « GitLab CI »Le job télécharge l'archive Grype et son fichier de sommes, vérifie l'empreinte avec sha256sum -c avant d'extraire quoi que ce soit. Cette séquence est le strict minimum pour un binaire récupéré depuis Internet, et elle échoue bruyamment si l'archive a été altérée.
scan: stage: test image: docker:24@sha256:9b17a9f25adf17b88d0a013b4f00160754adf4b07ccbe9986664a49886c2c98e services: - docker:24-dind@sha256:9b17a9f25adf17b88d0a013b4f00160754adf4b07ccbe9986664a49886c2c98e script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . - apk add --no-cache curl tar - | GRYPE_VERSION=0.118.0 base="https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}" curl -sSfLO "${base}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz" curl -sSfLO "${base}/grype_${GRYPE_VERSION}_checksums.txt" grep "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" "grype_${GRYPE_VERSION}_checksums.txt" | sha256sum -c - tar -C /usr/local/bin -xzf "grype_${GRYPE_VERSION}_linux_amd64.tar.gz" grype grype $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --fail-on criticalDépannage
Section intitulée « Dépannage »La difficulté du scan n'est presque jamais technique : elle tient au volume de résultats et à leur interprétation. Un premier scan sur une image applicative remonte couramment plusieurs centaines de lignes, dont une majorité sans correctif disponible ni exploitabilité dans votre contexte. Les symptômes ci-dessous couvrent les cas rencontrés lors de la mise en place, du bruit initial aux divergences entre outils.
| Symptôme | Cause probable | Solution |
|---|---|---|
| Trop de CVE MEDIUM/LOW | Pas de filtrage | Utiliser --severity HIGH,CRITICAL ou --fail-on high |
CVE sans FIXED-IN | Pas de correctif référencé | Évaluer alternatives : MAJ applicative, changement de base, suppression du composant |
| Faux positifs répétés | CVE non applicable à votre contexte | Créer .grype.yaml avec ignore: ou formaliser avec OpenVEX |
| Trivy DB outdated | Base locale expirée | trivy image --download-db-only |
| Scan lent en CI/CD | Téléchargement de la DB à chaque run | Mettre la DB en cache dans le pipeline |
| Image distroless / Wolfi | Moins de paquets OS | Scanner quand même, bibliothèques applicatives restent visibles |
| Résultats différents entre deux scanners | Sources et règles de matching différentes | Normal, définissez un scanner de référence et documentez les écarts |
| CVE non exploitable dans votre contexte | Faux positif contextuel | Formaliser l'acceptation de risque avec OpenVEX ou .grype.yaml |
Deux scanners ne rendent pas le même résultat, et c'est normal
Section intitulée « Deux scanners ne rendent pas le même résultat, et c'est normal »L'écart est mesurable. Sur nginx:1.25.3, grype et Trivy rendent
respectivement 450 et 459 CVE distinctes, dont 444 communes. Six
n'apparaissent que chez grype, quinze que chez Trivy.
Autrement dit 97 % de recouvrement, et une vingtaine de divergences. Aucun des deux ne se trompe : les bases de données, les règles de correspondance et les métadonnées de paquets diffèrent. La conséquence pratique est qu'il faut choisir un scanner de référence et s'y tenir, sinon chaque changement d'outil produit une vague d'alertes nouvelles qui ne correspond à aucun changement dans vos images. Et documenter les exceptions par rapport à ce scanner, pas dans l'absolu.
Ignorer les faux positifs avec Grype
Section intitulée « Ignorer les faux positifs avec Grype »Le fichier .grype.yaml placé à la racine du projet retire des résultats les vulnérabilités listées sous ignore:. Chaque exception doit porter une raison écrite : une exclusion sans justification devient invérifiable dès que son auteur quitte l'équipe, et personne n'ose plus la supprimer.
ignore: # CVE ne s'applique pas à notre build (Java non utilisé) - vulnerability: CVE-2023-XXXXX reason: "Not applicable - Java runtime not present" # Toutes les CVE LOW pour ce package - package: name: libcurl fix-state: not-fixed severity: lowLimites du scan de vulnérabilités
Section intitulée « Limites du scan de vulnérabilités »Un scanner répond à une seule question : les composants présents dans cette image figurent-ils dans une base de vulnérabilités connues. Il ne dit rien de ce que fait le code, ni de qui l'a construit, ni de ce que l'image exécutera une fois déployée. Confondre l'absence de CVE critiques avec la sûreté de l'image est l'erreur la plus fréquente, et l'incident Trivy de mars 2026 en donne un exemple direct.
La limite la plus importante à retenir de ce guide : une image sans CVE connue n'est pas une image sûre. Elle peut avoir été altérée, provenir d'une source compromise, ou transporter du code malveillant qu'aucune base de vulnérabilités ne référence. L'incident Trivy en est lui-même la démonstration.
Le scan couvre une question, « quelles failles connues contient cette image », et une seule. Trois autres mécanismes répondent aux questions voisines : la signature avec Cosign établit l'intégrité, la provenance au sens SLSA établit l'origine du build, la politique d'admission décide de ce qui entre dans le cluster, et la surveillance au runtime détecte ce qui s'écarte du comportement attendu. Aucun ne remplace les autres.
Testez vos Connaissances
Section intitulée « Testez vos Connaissances »Les questions portent sur ce qui vient d'être expliqué : lecture d'un résultat de scan, priorisation par CVSS et EPSS, et périmètre exact de ce qu'un scanner prouve.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »- Grype : mon choix principal pour scanner images et SBOM en production, avec focus sur EPSS, KEV et OpenVEX
- Trivy : outil toujours très connu et fonctionnellement large, que je ne retiens plus comme scanner principal après l'incident de mars 2026
- Un scan d'image ne suffit pas : compléter avec signature, provenance, politiques d'admission et contrôles runtime
- EPSS + CVSS : croiser les deux signaux pour prioriser, l'EPSS seul ne suffit pas
- En CI/CD : évitez de télécharger les scanners à la volée ; préférez des images de job internes et figées
FIXED-INvide : pas de correctif référencé à cet instant, évaluer les alternatives plutôt qu'attendre- SBOM : générer avec Syft, scanner avec Grype = séparation claire des responsabilités
Mettre en pratique
Section intitulée « Mettre en pratique »Remplacer une image vulnérable demande de prouver que le remplacement vaut mieux, pas qu'il est plus récent. Ce lab vous fait analyser avec Trivy une image vieille de trois ans, choisir son remplacement, et la validation analyse les deux en exigeant strictement moins de failles critiques que l'original.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Falco : La détection des comportements suspects qu'un scan d'image ne voit pas.
- Runtime Sandboxes : L'isolation gVisor ou Kata pour les images auxquelles on ne fait pas confiance.