📋 Sommaire
- Quand un scanner de sécurité devient un voleur de secrets
- Ce qui s’est vraiment passé avec Trivy
- Pourquoi l’incident Trivy concerne directement les pipelines GitLab
- Les mauvaises habitudes qui transforment les pipelines en porte d’entrée
- Bonnes pratiques CI/CD : les bases pour ne plus jouer avec le feu
- Durcir ses pipelines GitLab quand on est freelance ou petite PME
- Exemple concret de .gitlab-ci.yml : avant / après durcissement
- Conclusion : même à petite échelle, la CI est une cible critique
Quand un scanner de sécurité devient un voleur de secrets
Quand un scanner de sécurité censé protéger le code se transforme en voleur de secrets, on réalise brutalement à quel point les pipelines GitLab peuvent trahir la confiance qu’on leur accorde. L’incident Trivy a montré qu’un simple job de sécurité peut suffire à exposer tous les secrets d’un projet en quelques minutes, sans aucune modification visible du YAML.
L’attaque supply chain qui a frappé Trivy en mars 2026 change la façon de voir les pipelines GitLab CI. Cet incident dépasse largement le cas d’un seul outil et pose une question clé : comment sécuriser ses pipelines GitLab pour éviter qu’un composant de confiance ne devienne le point d’entrée principal d’un attaquant ?
Ce qui s’est vraiment passé avec Trivy en mars 2026
En mars 2026, le scanner de vulnérabilités Trivy a été compromis dans une attaque supply chain particulièrement sophistiquée. Des attaquants ont utilisé un compte de service compromis pour publier un binaire malveillant (v0.69.4), pousser des tags GitHub Actions vers des commits piégés et diffuser des images Docker infectées.
Concrètement, ils ont empoisonné les tags des actions GitHub Trivy : jusqu’à 76 tags de version dans le dépôt aquasecurity/trivy-action et tous les tags de aquasecurity/setup-trivy ont été force-pushés vers des commits malveillants, alors que les noms de tags, eux, n’avaient pas changé. Des workflows CI parfaitement propres en apparence continuaient donc d’utiliser des versions “pinnées”, mais exécutaient en réalité du code attaquant avant de lancer le scan attendu.
Le binaire Trivy v0.69.4 et plusieurs images Docker, y compris certaines taguées latest, embarquaient un infostealer spécialisé dans le vol de secrets. Ce code malveillant lisait les variables d’environnement, explorait la mémoire et le filesystem du runner CI, puis exfiltrait des tokens, clés SSH et identifiants cloud vers une infrastructure contrôlée par les attaquants.
De nombreuses organisations ayant exécuté ces artefacts pendant la fenêtre d’exposition ont dû traiter leurs environnements comme potentiellement compromis. Elles ont été contraintes de couper des pipelines, d’inspecter leurs runners et de régénérer en urgence un grand nombre de secrets.
Pourquoi l’incident Trivy concerne directement les pipelines GitLab
Sur le papier, Trivy pourrait sembler être surtout un sujet GitHub Actions. En pratique, l’attaque montre qu’un pipeline qui télécharge ou exécute un binaire externe dans un job de sécurité devient un excellent vecteur d’exfiltration de secrets, quelle que soit la plateforme CI/CD utilisée.
Dans un environnement GitLab CI classique, un job de scan d’image Docker peut par exemple ressembler à ceci :
scan_image:
stage: scan
image: aquasec/trivy:latest
script:
- trivy image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Autre scénario courant : un job qui télécharge un binaire depuis GitHub ou un registre public avant de l’exécuter dans le pipeline. Si l’image ou le binaire de ce job est compromis comme Trivy l’a été, le scanner peut lire toutes les variables CI/CD du projet, y compris les secrets de déploiement lorsqu’ils sont exposés de manière globale.
Ces secrets peuvent inclure des clés SSH_PRIVATE_KEY utilisées pour se connecter aux serveurs, des mots de passe de bases de données de production, des accès back‑office d’outils e‑commerce, des clés API de paiement ou encore des tokens GitLab capables de pousser du code ou de cloner des projets. Avec ce type d’accès, un attaquant peut déployer du code malveillant sur plusieurs environnements, exfiltrer des données sensibles ou prendre le contrôle d’une partie de l’infrastructure.
Sécuriser ses pipelines GitLab n’est donc pas un luxe réservé aux grandes équipes DevOps. C’est une nécessité dès qu’un pipeline concentre plusieurs secrets de production et orchestre des déploiements automatiques.
Les mauvaises habitudes qui transforment les pipelines en porte d’entrée
L’incident Trivy ne met pas seulement en cause un outil de sécurité. Il révèle surtout des patterns dangereux très répandus dans les pipelines CI/CD, y compris sur des projets modestes hébergés sur GitLab. Plusieurs mauvaises habitudes se combinent et ouvrent la voie à des attaques supply chain efficaces.
Confiance aveugle dans les outils de sécurité
Les scanners de sécurité et les jobs “security” sont souvent considérés comme intrinsèquement fiables. Pourtant, un outil de sécurité reste du code exécuté avec un niveau de privilège très élevé dans le pipeline, avec accès à de nombreuses variables et artefacts. L’affaire Trivy rappelle qu’un scanner peut être transformé en voleur de secrets sans que la sortie du job ne paraisse suspecte.
Références mutables : latest et tags réécrits
L’usage de tags mutables comme latest ou des versions v0.xx sur les images, binaires et actions est un autre problème majeur. Les attaquants ont précisément exploité ce point faible en force‑pushant des tags existants vers des commits malveillants, ce qui a changé le code exécuté sans le moindre diff dans le fichier CI. Tant que des pipelines pointent vers des tags faciles à réécrire, du code peut changer silencieusement derrière eux.
Téléchargements à chaud et exécution directe
Le pattern curl | bash ou wget d’un binaire depuis GitHub avant exécution reste omniprésent. Beaucoup de pipelines s’appuient sur des URLs releases/latest ou des endpoints qui tirent systématiquement la dernière version d’un outil, sans vérifier de signature ou de checksum. Dès qu’un attaquant compromet la chaîne de publication, chaque pipeline qui récupère la “dernière release” devient un vecteur potentiel d’attaque.
Scope trop large des secrets CI/CD
Une mauvaise habitude répandue consiste à injecter les mêmes variables sensibles dans tous les jobs, qu’il s’agisse de déploiement, de tests ou de scans. Si un job de sécurité tourne avec SSH_PRIVATE_KEY, PROD_DB_PASSWORD ou des tokens GitLab puissants, le moindre outil compromis peut tout récupérer en une seule exécution.
Runners partagés et isolation insuffisante
L’utilisation de runners partagés pour plusieurs projets, sans isolation forte, ajoute une couche de risque. Un binaire malveillant peut tenter de fouiller les caches, les fichiers temporaires ou des volumes partagés, surtout si la configuration du runner n’impose pas un nettoyage strict entre les jobs.
Bonnes pratiques CI/CD : les bases pour ne plus jouer avec le feu
La bonne nouvelle, c’est qu’une grande partie du risque peut être réduite avec quelques principes simples appliqués de manière systématique dans les pipelines CI/CD. L’incident Trivy a remis en lumière des bonnes pratiques qui devraient devenir réflexes, même pour des projets de taille modeste.
Appliquer le principe du moindre privilège
Chaque job d’un pipeline GitLab doit disposer uniquement des permissions et secrets strictement nécessaires à sa tâche. Les jobs de scan n’ont pas besoin de clés SSH de production, et les jobs de tests n’ont pas besoin de tokens d’admin GitLab. En réduisant le périmètre de chaque job, on limite fortement l’impact potentiel d’un outil compromis.
Pinning strict des versions
Il est recommandé d’éviter au maximum latest et les tags mutables pour les images, binaires et actions. Des versions figées, par exemple des digests d’images Docker ou des SHA de commit, rendent beaucoup plus difficile la substitution silencieuse d’un binaire par un attaquant. Les mises à jour passent alors par des commits explicites, traçables et audités.
Séparer les jobs de scan et de déploiement
Une règle simple, mais extrêmement efficace, consiste à séparer totalement les jobs de scan et les jobs de déploiement. Les jobs de scan ne doivent jamais avoir accès aux secrets de production, tandis que les jobs de déploiement ne devraient pas télécharger ni exécuter de code externe non maîtrisé. Ainsi, même si un scanner est compromis, il ne pourra pas se connecter aux serveurs ou pousser du code.
Gérer finement les secrets CI/CD
Tous les secrets doivent être centralisés dans la gestion des variables CI/CD, et non dans le code ou dans le fichier .gitlab-ci.yml. Il est essentiel de les protéger via l’option “masked” pour éviter qu’ils apparaissent en clair dans les logs, et de les limiter à certains environnements, branches ou jobs. Cette approche réduit fortement la surface d’exposition des informations les plus sensibles.
Réduire la dépendance aux téléchargements externes
Quand c’est possible, il vaut mieux privilégier des artefacts contrôlés (registres internes, artefacts GitLab, fichiers sécurisés) plutôt que des téléchargements directs depuis Internet. Si un téléchargement direct est nécessaire, il est préférable d’épingler une version précise, de vérifier les signatures lorsqu’elles existent et d’éviter les endpoints releases/latest.
Préparer un vrai plan de réponse à incident
L’affaire Trivy montre l’importance de savoir rapidement où un outil compromis est utilisé. Il est nécessaire de pouvoir identifier les pipelines concernés, désactiver les jobs à risque, régénérer les secrets exposés et revenir à une version saine. Même pour une petite équipe, disposer d’une checklist de réponse à incident renforce la résilience globale.
Durcir ses pipelines GitLab quand on est freelance ou petite PME
Côté GitLab CI, plusieurs leviers sont particulièrement efficaces pour les freelances et petites PME qui veulent sécuriser leurs pipelines sans ajouter trop de complexité. L’objectif reste de sécuriser ses pipelines GitLab avec quelques garde‑fous simples, mais bien pensés.
Utiliser correctement les variables CI/CD GitLab
Tous les secrets (API keys, SSH, tokens GitLab, mots de passe BDD) doivent résider dans les variables de projet ou de groupe, jamais dans le dépôt. GitLab permet de marquer ces variables comme “masked” pour qu’elles n’apparaissent pas dans les logs, et “protected” pour qu’elles ne soient injectées que sur les branches ou tags protégés. Certaines variables peuvent aussi être restreintes à un environnement spécifique, comme la production.
Structurer le pipeline par stages
Une structure simple, mais robuste, pour un .gitlab-ci.yml orienté production peut ressembler à ceci :
stages:
- test
- security
- build
- deploy
Les jobs du stage security (scan de dépendances, scan d’images) n’ont pas besoin de PROD_DB_PASSWORD ni de SSH_PRIVATE_KEY. Les variables GitLab peuvent être configurées pour n’être disponibles que sur le stage deploy, ou sur les seuls jobs de déploiement, ce qui limite fortement le risque en cas de scanner compromis.
Choisir les bons runners GitLab
Dès qu’un projet touche à des environnements de production ou à des clients, il devient judicieux de privilégier des runners dédiés par projet ou par groupe. Un runner dédié en exécuteur Docker, avec nettoyage entre les jobs, offre une isolation bien plus propre qu’un runner partagé avec d’autres projets, ce qui réduit le risque de fuites via les caches ou les volumes partagés.
Éviter les téléchargements “à chaud” non maîtrisés
Si un outil externe comme local-php-security-checker doit être utilisé, il est préférable d’éviter les URL releases/latest. Il vaut mieux utiliser une version figée dans l’URL et, si possible, stocker ce binaire dans un registre ou comme “secure file” GitLab pour le réutiliser sans repasser par un téléchargement direct à chaque pipeline.
Adopter des conventions claires pour les secrets de déploiement
Pour les déploiements, l’utilisation de clés SSH dédiées par projet ou par client, plutôt que d’une clé personnelle, améliore la sécurité. Les variables SSH_PRIVATE_KEY, SSH_HOST, SSH_USER et WORK_DIR doivent être limitées aux branches protégées et aux jobs deploy_*, afin d’éviter qu’un job de test ou de sécurité puisse se connecter à un serveur de production.
Exemple concret de .gitlab-ci.yml : avant / après durcissement
Pour rendre les bonnes pratiques plus concrètes, il est utile de comparer un pipeline GitLab avant et après durcissement. L’idée est de montrer comment un job de sécurité mal conçu peut exposer des secrets, puis comment sécuriser ses pipelines GitLab sans tout bouleverser.
Avant : un pattern risqué
Voici un exemple de configuration GitLab CI typique, mais dangereuse pour la sécurité :
stages:
- test
- security
- deploy
security_scan:
stage: security
image: php:latest
script:
- curl -sL https://github.com/sensiolabs/security-checker/releases/latest/download/local-php-security-checker
-o local-php-security-checker
- chmod +x local-php-security-checker
- ./local-php-security-checker composer.lock
deploy_production:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache openssh
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cd $WORK_DIR && git pull && php artisan migrate --force"
only:
- main
Les problèmes sont multiples : usage massif de latest, téléchargement à chaud via releases/latest, et surtout variables de production accessibles à tous les jobs, y compris security_scan. Si l’outil téléchargé est compromis, il peut lire directement les variables SSH_PRIVATE_KEY, SSH_HOST, SSH_USER ou PROD_DB_PASSWORD dans l’environnement du job.
Après : un pipeline GitLab durci
Voici une version durcie qui applique les bonnes pratiques évoquées plus haut :
stages:
- test
- security
- build
- deploy
php_security_check:
stage: security
image: php:8.2-cli
variables:
GIT_STRATEGY: fetch
script:
- curl -sL https://github.com/fabpot/local-php-security-checker/releases/download/v2.1.0/local-php-security-checker_2.1.0_linux_amd64
-o local-php-security-checker
- chmod +x local-php-security-checker
- ./local-php-security-checker composer.lock
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
# Aucun secret de prod n'est injecté ici.
deploy_production:
stage: deploy
image: alpine:3.19
before_script:
- apk add --no-cache openssh-client
script:
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh -o StrictHostKeyChecking=no "$SSH_USER@$SSH_HOST" "cd $WORK_DIR && git pull && php artisan migrate --force"
only:
- main
environment:
name: production
Dans cette version, le job php_security_check utilise une image PHP à version figée, télécharge une version précise de l’outil de scan et n’a accès à aucun secret de production. Le job deploy_production, lui, travaille avec une image Alpine versionnée et ne reçoit que les variables SSH nécessaires au déploiement. Côté GitLab, ces variables sont marquées comme “masked” et “protected” et limitées aux branches protégées.
Conclusion : même à petite échelle, la CI est une cible critique
L’affaire Trivy rappelle une réalité inconfortable : même des pipelines associés à des projets modestes restent des cibles intéressantes dès lors qu’ils concentrent des secrets de plusieurs environnements. Un scanner compromis ou un simple curl mal maîtrisé peut suffire à exposer des accès SSH, des bases de données de production et des tokens GitLab à un attaquant.
La bonne nouvelle, c’est que quelques ajustements bien choisis dans les pipelines GitLab — pinning des versions, séparation des jobs de scan et de déploiement, bonne gestion des variables, runners dédiés — suffisent à réduire drastiquement ce risque sans alourdir le quotidien des équipes. Reste une question : quelles sont les premières mauvaises habitudes que chaque équipe est prête à abandonner pour vraiment sécuriser ses pipelines GitLab après l’incident Trivy ?
Même avec de petits projets, les pipelines GitLab concentrent des secrets critiques (SSH, BDD, API clients). Un outil compromis ou un job mal configuré peut exposer ces accès et impacter directement tes clients.
L’incident montre qu’un scanner de sécurité peut être détourné en voleur de secrets. Il faut donc le traiter comme n’importe quel code externe : version figée, pas de secrets inutiles dans le job et veille active sur les alertes de sécurité.
Commence par éviter <code>latest</code>, séparer clairement les stages <code>security</code> et <code>deploy</code>, limiter les variables sensibles aux jobs de déploiement et marquer toutes les clés et tokens comme “masked” et “protected”.
Ils ne sont pas dangereux par nature, mais ils élargissent la surface d’attaque si tu déploies de la production. Pour des projets clients, il vaut mieux utiliser des runners dédiés, en exécuteur Docker, avec nettoyage strict entre les jobs.
Tu dois identifier les pipelines concernés, désactiver temporairement les jobs qui l’utilisent, régénérer tous les secrets potentiellement exposés (SSH, BDD, API, tokens GitLab) et surveiller les logs de connexion et de déploiement pour détecter toute activité suspecte.
