Cette page vous montre comment construire un pipeline CI/CD (intégration et déploiement continus) réellement sécurisé : quatre piliers concrets (security gates, secrets, signature, isolation) plus les attestations de provenance. Elle s'adresse aux équipes DevOps/DevSecOps qui ont déjà un pipeline fonctionnel mais veulent combler ses angles morts avant qu'un attaquant ne les trouve. En 2025, deux attaques ont rappelé pourquoi ce sujet reste urgent : la compromission de tj-actions/changed-files (23 000 dépôts touchés) et le ver Shai-Hulud sur npm. Les deux ont exploité exactement les failles décrites ici.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Identifier pourquoi le pipeline CI/CD est devenu une cible de choix pour les attaquants.
- Mettre en place des security gates qui bloquent le code vulnérable avant la production.
- Gérer les secrets sans jamais les exposer dans le code ou les logs.
- Signer et vérifier des artefacts avec Sigstore/cosign pour garantir leur intégrité.
- Isoler les runners pour limiter l'impact d'une compromission.
- Générer des attestations de provenance (SLSA, GitHub Attestations) pour prouver comment un artefact a été construit.
Anatomie d'un pipeline sécurisé
Section intitulée « Anatomie d'un pipeline sécurisé »Analogie : imaginez une chaîne de production automobile. Vous ne laissez pas n'importe qui entrer dans l'usine (isolation). Vous vérifiez chaque pièce avant assemblage (security gates). Vous protégez les plans secrets (gestion des secrets). Et vous apposez une plaque VIN unique pour prouver l'origine de chaque voiture (signature). Si un seul maillon est défaillant, toute la chaîne est compromise.
Un pipeline CI/CD est votre usine logicielle : il transforme du code source en artefacts déployés en production. Chaque étape est une opportunité pour un attaquant d'injecter du code malveillant, de voler des credentials, de substituer un artefact ou de pivoter vers vos systèmes internes.
Pourquoi le pipeline est une cible privilégiée ? Parce qu'il concentre trois caractéristiques irrésistibles pour un attaquant :
- Accès aux secrets : le pipeline détient des tokens pour déployer en production, des clés API, des credentials AWS/Azure/GCP.
- Privilèges élevés : il peut modifier le code, publier des artefacts, déployer des infrastructures.
- Exécution automatique : compromettre le pipeline transforme chaque commit en vecteur d'attaque automatisé.
Deux incidents illustrent ce risque à des échelles différentes. En 2020, l'attaque SolarWinds a compromis 18 000 organisations en injectant du code malveillant dans le processus de build d'un logiciel légitime : les clients téléchargeaient une version backdoorée sans le savoir. En mars 2025, l'action GitHub tj-actions/changed-files a été détournée après le vol des identifiants d'un mainteneur : l'attaquant a redirigé les tags de version (v1 à v44.5.1) vers un commit malveillant qui exfiltrait les secrets directement dans les logs CI de plus de 23 000 dépôts. Unit 42 de Palo Alto Networks a relié cet incident à une compromission chez Coinbase. La méthode a changé, la cible reste la même : le pipeline.
Les quatre piliers de la défense
Section intitulée « Les quatre piliers de la défense »Voici les fondations non négociables d'un pipeline sécurisé :
| Pilier | Objectif | Menace adressée |
|---|---|---|
| Security gates | Bloquer le code vulnérable avant déploiement | Code malveillant, vulnérabilités connues |
| Gestion des secrets | Protéger les credentials et tokens | Fuite de secrets, accès non autorisé |
| Signature d'artefacts | Garantir l'intégrité et la provenance | Tampering, supply chain attack |
| Isolation | Limiter l'impact d'une compromission | Mouvement latéral, élévation de privilèges |
Pourquoi ces quatre piliers sont indissociables ? Imaginez un pipeline avec des security gates parfaits mais qui expose ses secrets en clair dans les logs : les gates détecteront les CVE, mais l'attaquant aura déjà volé vos clés AWS. C'est exactement le mode opératoire de tj-actions : l'action modifiée ne cassait aucun test, elle se contentait de dumper les secrets dans les logs, invisible aux gates classiques. La sécurité est une chaîne : le maillon le plus faible détermine la solidité de l'ensemble.
Security gates : les points de contrôle
Section intitulée « Security gates : les points de contrôle »Analogie : dans un aéroport, vous passez plusieurs contrôles avant d'embarquer : vérification d'identité au comptoir, contrôle de sécurité, vérification à la porte. Si vous échouez à n'importe quelle étape, vous ne montez pas dans l'avion. Chaque contrôle est un gate qui bloque la progression si les critères ne sont pas remplis.
Les security gates appliquent ce principe au pipeline CI/CD. Ce sont des points de décision automatisés : si le code contient des vulnérabilités critiques, des secrets exposés, ou échoue aux tests de sécurité, le pipeline s'arrête. Pas de déploiement en production.
Le principe du fail fast : pourquoi bloquer tôt plutôt que de corriger en production ? Parce que corriger une vulnérabilité en prod coûte largement plus cher qu'en développement (rollback, gestion d'incident, communication client, perte de confiance). Un gate qui bloque un commit vulnérable vous évite des semaines de travail correctif.
Architecture des gates : défense en profondeur
Section intitulée « Architecture des gates : défense en profondeur »Un pipeline robuste place des gates à chaque transition, comme des sas de sécurité successifs. Si un gate est contourné (erreur de config, bug de l'outil), le gate suivant rattrape l'erreur.
Voici l'architecture de référence, avec le rôle précis de chaque niveau :
| Gate | Position | Ce qu'il vérifie | Action si échec |
|---|---|---|---|
| Pre-commit | Avant le commit | Secrets, formatage | Bloque le commit |
| PR Gate | À l'ouverture de PR | SAST, SCA, tests | Bloque le merge |
| Build Gate | Après compilation | Scan d'image, signature | Bloque la publication |
| Deploy Gate | Avant déploiement | Approbation, checks finaux | Bloque le déploiement |
Chaque gate a un rôle spécifique dans le cycle de vie du code. Le pre-commit donne un feedback immédiat au développeur (quelques secondes), avant même de polluer l'historique Git. Le PR Gate apporte une validation collaborative : l'équipe voit les problèmes avant le merge et protège la branche main. Le Build Gate scanne les artefacts finaux (image Docker), car certaines vulnérabilités n'apparaissent qu'après compilation. Le Deploy Gate est la dernière ligne de défense, avec vérification manuelle si nécessaire.
Stratégie de seuils : bloquer intelligemment
Section intitulée « Stratégie de seuils : bloquer intelligemment »Le dilemme : si vous bloquez le pipeline pour chaque alerte, y compris les faux positifs et les vulnérabilités mineures, les développeurs perdront confiance et chercheront à contourner les gates. Si vous ne bloquez rien, les gates sont inutiles. Il faut trouver l'équilibre entre sécurité et vélocité.
Une stratégie de seuils graduée répond à trois questions : quelle sévérité bloque (CRITICAL toujours, HIGH parfois, MEDIUM presque jamais) ; quel gate bloque quoi (pre-commit bloque peu, deploy gate bloque beaucoup) ; quelle tolérance au faux positif appliquer (les secrets sont binaires, présent ou absent, les CVE nécessitent du contexte).
# Exemple de stratégie de seuilssecurity_gates: pre-commit: block_on: [secrets] # Seuls les secrets bloquent (feedback en quelques secondes) warn_on: [high_cvss] # Avertir, mais ne pas bloquer (trop tôt)
pr_gate: block_on: [critical_cvss, secrets, high_cvss_with_exploit] warn_on: [high_cvss, medium_cvss] # Visible dans la PR, pas bloquant
deploy_gate: block_on: [any_critical, missing_signature] # Tolérance zéro require_approval_for: [high_cvss] # Humain décide si contexte complexeExemple concret : une CVE HIGH est détectée sur une dépendance Node.js. Le PR gate avertit (commentaire dans la PR : "cette dépendance a une CVE HIGH, mettre à jour vers la version corrigée"). Le développeur peut merger si urgent, mais le deploy gate bloquera le déploiement tant que la correction n'est pas mergée. Résultat : pas de friction inutile, mais garantie que la prod reste saine.
Implémentation GitHub Actions
Section intitulée « Implémentation GitHub Actions »Cet exemple applique trois réflexes issus de la compromission tj-actions : permissions minimales au niveau workflow, actions pinnées par SHA de commit (pas par tag mutable) et persist-credentials: false sur le checkout pour ne pas laisser traîner de token exploitable.
name: Security Gates
on: pull_request: branches: [main]
permissions: {} # Rien par défaut, chaque job déclare ce dont il a besoin
jobs: # Gate 1 : Secrets secrets-scan: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 # Historique complet pour scanner les commits persist-credentials: false
- name: Detect secrets uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Gate 2 : SAST sast: runs-on: ubuntu-latest permissions: contents: read container: image: semgrep/semgrep@sha256:59fbed6127ea7c5dde3ba6a85142733bb20ea9aaa36120c953904f1539aaf66e # 1.168.0 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false
- name: SAST scan run: semgrep scan --config p/security-audit --config p/owasp-top-ten --error
# Gate 3 : SCA sca: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false
- name: Dependency scan uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: scan-type: 'fs' scan-ref: '.' exit-code: '1' severity: 'CRITICAL'
# Gate final : tous les checks doivent passer security-gate: needs: [secrets-scan, sast, sca] runs-on: ubuntu-latest steps: - run: echo "All security gates passed"Le point d'attention le plus souvent oublié : returntocorp/semgrep-action, longtemps recommandée, est dépréciée depuis 2024 au profit d'un appel direct au binaire Semgrep. Vérifiez toujours qu'une action tierce est activement maintenue avant de la pinner dans un pipeline critique.
Gestion des secrets
Section intitulée « Gestion des secrets »Scénario réel : un développeur a committé par erreur une clé API AWS dans un dépôt GitHub public. En quelques minutes, un bot automatisé a détecté la clé, l'a utilisée pour créer des instances EC2 de minage de cryptomonnaie, et a généré une facture de plusieurs dizaines de milliers d'euros en une nuit. Le développeur a découvert le problème en recevant une alerte de dépassement de budget AWS.
Les secrets (API keys, tokens, passwords, certificats) sont le nerf de la guerre du pipeline : ils donnent accès à vos systèmes de production, vos bases de données, vos fournisseurs cloud. Un seul secret exposé peut suffire à une compromission complète.
Pourquoi les secrets fuient-ils si souvent ? Parce qu'ils sont partout : dans le code (hardcodés), dans les variables d'environnement (loggées), dans les fichiers de config (versionnés), dans les containers (visibles avec docker inspect). Chaque endroit est une opportunité de fuite. En septembre 2025, le ver Shai-Hulud a exploité exactement cette surface : propagé sur plus de 500 paquets npm compromis, il scannait automatiquement les environnements CI à la recherche de tokens GitHub, credentials AWS/GCP/Azure et clés npm, puis se republiait lui-même via les comptes de mainteneurs volés pour se propager plus loin. L'histoire ne s'est pas arrêtée là : une seconde vague (« Shai-Hulud 2.0 ») a touché près de 800 paquets supplémentaires fin novembre 2025, preuve que ce type de ver auto-propagateur reste une menace active plutôt qu'un incident isolé.
Les erreurs classiques (et comment elles se produisent)
Section intitulée « Les erreurs classiques (et comment elles se produisent) »Voici les patterns d'erreur les plus fréquents, avec des exemples concrets. Ce tableau sert de checklist rapide avant chaque revue de pipeline : chaque ligne correspond à une fuite réellement observée en production, pas à un risque théorique.
| Erreur | Risque | Fréquence | Exemple concret |
|---|---|---|---|
| Secret en variable d'environnement non masquée | Visible dans les logs | Très courante | echo $DATABASE_PASSWORD apparaît dans les logs CI, potentiellement indexé publiquement |
| Secret hardcodé dans le code | Exposé dans l'historique Git | Courante | const API_KEY = "sk_live_abc123", même supprimé, reste dans l'historique Git |
| Secret partagé entre environnements | Compromission transversale | Courante | Même token AWS pour dev/prod : compromission dev devient compromission prod |
| Secret sans rotation | Exploitation prolongée après fuite | Très courante | Clé API créée il y a des années, jamais changée : si elle fuite, elle reste valide indéfiniment |
| Secret avec privilèges excessifs | Impact amplifié | Courante | Token admin au lieu de read-only : vol du token égale contrôle total |
Pourquoi ces erreurs persistent-elles ? Parce que les secrets sont invisibles jusqu'à ce qu'ils fuient. Un développeur qui hardcode une clé API "temporairement" oublie souvent de la retirer. Une variable non masquée passe inaperçue jusqu'au jour où un attaquant fouille vos logs publics. La sécurité des secrets exige de la discipline et de l'automatisation.
Principes de gestion sécurisée : les cinq règles d'or
Section intitulée « Principes de gestion sécurisée : les cinq règles d'or »Prémisse : la gestion des secrets repose sur une règle absolue, le code ne doit jamais contenir de valeurs secrètes, seulement des références à des secrets stockés ailleurs. Un secret dans le code est une bombe à retardement.
Voici les cinq principes non négociables, avec la justification de chacun :
-
Jamais de secrets dans le code : externaliser systématiquement
Pourquoi ? Le code est versionné dans Git. Même si vous supprimez le secret dans un commit ultérieur, il reste dans l'historique. N'importe qui avec accès au dépôt (anciens employés, forks publics, mirrors) peut fouiller l'historique et extraire le secret.
Utilisez toujours un gestionnaire de secrets externe. Le code ne doit contenir que des références.
# JAMAISenv:DATABASE_PASSWORD: "SuperSecret123!"# TOUJOURSenv:DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }} -
Moindre privilège : limiter le blast radius
Pourquoi ? Si un secret fuite, et cela arrivera, l'impact doit être minimal. Un token
adminpermet de tout faire : supprimer des dépôts, modifier des secrets, créer des backdoors. Un tokenread-onlypermet juste de lire du code.Exemple concret : votre pipeline doit publier un package npm. Créez un token avec uniquement la permission
publish, pasadmin. Si le token fuite, l'attaquant peut publier des versions malveillantes (détectable), pas supprimer votre compte npm (irréversible).# Trop permissifpermissions:contents: write # Peut modifier le codepackages: writeactions: write # Peut modifier les workflows# Minimalpermissions:contents: read # Juste lire le codepackages: write # Uniquement publier des packages -
Isolation par environnement : cloisonner les contextes
Pourquoi ? Un développeur qui teste localement ou en dev ne devrait jamais avoir besoin des secrets de production. Si vous partagez les mêmes secrets, une erreur en dev (logger le secret, commit accidentel) compromet la prod.
Utilisez des secrets différents pour dev, staging et production, jamais de secret prod en dev :
jobs:deploy-dev:environment: developmentsteps:- name: Deployenv:API_KEY: ${{ secrets.DEV_API_KEY }} # Token avec accès au sandboxdeploy-prod:environment: production # Environnement GitHub avec validation manuellesteps:- name: Deployenv:API_KEY: ${{ secrets.PROD_API_KEY }} # Token prod, jamais accessible en devDans GitHub, les environnements
productionpeuvent exiger une approbation manuelle avant déploiement, une double sécurité simple à activer. -
Rotation automatique : limiter la fenêtre d'exploitation
Pourquoi ? Vous ne saurez jamais avec certitude quand un secret fuite. La rotation réduit la fenêtre d'exploitation : si vous changez vos secrets tous les 90 jours, un secret volé il y a 91 jours est inutilisable.
Les secrets doivent être renouvelés régulièrement, idéalement automatiquement via un gestionnaire dédié :
Type de secret Fréquence de rotation Justification API keys 90 jours Compromis entre sécurité et complexité opérationnelle Tokens d'accès 24h, préférer des tokens éphémères Minimiser l'impact d'une fuite Certificats Avant expiration, automatisé Ex. Let's Encrypt renouvelé via Cert-Manager Passwords de service 180 jours Changement manuel plus complexe, à automatiser progressivement Préférez les tokens éphémères générés à la demande (OIDC, IAM Roles for Service Accounts) plutôt que des secrets statiques. GitHub Actions peut s'authentifier sur AWS/Azure/GCP via OIDC sans aucun secret stocké.
-
Audit et monitoring
Chaque accès à un secret doit être loggé et auditable. C'est ce qui a permis, dans plusieurs incidents récents, de détecter une fuite en quelques heures plutôt qu'en plusieurs mois.
Solutions de gestion de secrets : choisir selon la maturité
Section intitulée « Solutions de gestion de secrets : choisir selon la maturité »Le dilemme : il existe des dizaines d'outils de gestion de secrets. Commencez simple, complexifiez au besoin. Ne déployez pas un outil enterprise complexe si vous avez deux secrets et un seul pipeline GitHub.
Voici un guide de sélection pragmatique, du plus simple au plus sophistiqué :
| Solution | Cas d'usage | Complexité | Quand l'utiliser |
|---|---|---|---|
| GitHub Secrets | Projets simples, GitHub-centric | Faible | Petite équipe, infrastructure sur GitHub Actions uniquement |
| SOPS | Secrets versionnés avec le code (chiffrés) | Moyenne | Besoin de versionner les secrets avec le code (GitOps), multi-cloud |
| HashiCorp Vault | Enterprise, multi-cloud, rotation auto | Élevée | Grande organisation, conformité stricte, rotation automatique essentielle |
| Infisical | Alternative open source à Vault | Moyenne | Budget limité, besoin de features Vault sans le coût |
Le choix dépend surtout de votre volume de secrets et de votre surface multi-cloud : moins de dix secrets sur une seule plateforme CI, restez sur les secrets natifs. Des besoins de versionnement GitOps orientent vers SOPS. Une exigence d'audit strict et de rotation automatique pousse vers Vault ou une alternative open source comme Infisical.
Signature d'artefacts : prouver l'origine et l'intégrité
Section intitulée « Signature d'artefacts : prouver l'origine et l'intégrité »Analogie : quand vous achetez un médicament, vous vérifiez le sceau inviolable du bouchon. Ce sceau prouve deux choses, personne n'a ouvert le flacon (intégrité), et il provient bien du laboratoire officiel (provenance). Si le sceau est brisé ou absent, vous ne consommez pas le produit.
La signature cryptographique d'artefacts logiciels repose sur le même principe. Elle garantit l'intégrité (l'artefact n'a pas été modifié depuis sa création) et la provenance (l'artefact provient bien du pipeline autorisé, pas d'un attaquant).
Scénario d'attaque sans signature : vous buildez une image Docker pour une API de facturation et la publiez dans un registre. Un attaquant compromet vos identifiants (mot de passe faible, pas de 2FA). Il remplace votre image par une version identique en apparence, mais avec un backdoor. Votre pipeline déploie l'image en production : vous venez de déployer le malware de l'attaquant, en contact direct avec des données de paiement. Avec une signature, ce scénario devient impossible : le pipeline vérifie la signature avant déploiement, et l'image substituée n'étant pas signée avec votre clé, le déploiement est refusé.
Pourquoi la signature n'est-elle pas encore généralisée partout ? Historiquement, parce que c'était compliqué : gérer des clés privées, distribuer des clés publiques, maintenir une infrastructure PKI. Sigstore a changé la donne en popularisant la signature keyless : plus de clé privée à gérer, juste votre identité OIDC (GitHub, GitLab, Google).
Le problème de la confiance
Section intitulée « Le problème de la confiance »Dans une architecture moderne, les artefacts traversent de nombreuses étapes :
Code → Build → Registry → Deploy → ProductionÀ chaque transition, un attaquant pourrait intercepter et modifier l'artefact. La signature crée une chaîne de confiance vérifiable à chaque étape, plutôt qu'une confiance implicite dans le registre ou le réseau.
Sigstore : l'écosystème de signature moderne
Section intitulée « Sigstore : l'écosystème de signature moderne »Sigstore est devenu le standard de facto pour la signature d'artefacts dans l'écosystème cloud-native (adopté par Kubernetes, Google, Red Hat, GitHub). Il résout le problème historique de la signature : la gestion des clés privées.
Avant Sigstore : pour signer, vous deviez générer une paire de clés GPG/RSA, la stocker de manière sécurisée (HSM, KMS), distribuer la clé publique, gérer la rotation, révoquer si compromise. Résultat : l'immense majorité des projets ne signaient rien.
Avec Sigstore : vous vous authentifiez via OIDC (GitHub Actions, GitLab CI), Sigstore émet un certificat éphémère valide quelques minutes, vous signez, le certificat expire. Pas de clé privée à gérer, mais une traçabilité complète via un log de transparence public, Rekor.
Voici les trois composants de l'écosystème :
| Composant | Rôle | Analogie | Ce qu'il fait concrètement |
|---|---|---|---|
| Cosign | Signer et vérifier les images/artefacts | Le stylo qui signe | CLI utilisée pour cosign sign, cosign verify |
| Fulcio | CA qui émet des certificats éphémères | Le notaire qui certifie l'identité | Vérifie l'identité OIDC, émet un certificat X.509 de quelques minutes |
| Rekor | Log de transparence (audit trail) | Le registre public des signatures | Enregistre chaque signature dans un ledger immuable, vérifiable par tous |
Pourquoi Rekor est-il crucial ? Parce qu'il rend les attaques détectables : si un attaquant compromet votre CI et signe un artefact malveillant, la signature est enregistrée publiquement dans Rekor avec l'identité utilisée. Impossible de signer en secret. Depuis octobre 2025, Rekor v2 est disponible en production avec un nouveau backend basé sur Trillian-Tessera, plus scalable pour les gros volumes de signatures ; les versions récentes de cosign basculent automatiquement dessus.
Workflow de signature
Section intitulée « Workflow de signature »-
Build : créer l'artefact
Fenêtre de terminal docker build -t registry.entreprise.io/api-facturation:v1.0.0 . -
Push : publier dans le registry
Fenêtre de terminal docker push registry.entreprise.io/api-facturation:v1.0.0 -
Sign : signer avec Cosign
Fenêtre de terminal # Signature keyless (recommandée)cosign sign registry.entreprise.io/api-facturation:v1.0.0# Cosign utilise l'identité OIDC (GitHub Actions, GitLab CI)# Pas de clé privée à gérer -
Verify : vérifier avant déploiement
Fenêtre de terminal cosign verify \--certificate-identity "https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main" \--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \registry.entreprise.io/api-facturation:v1.0.0
Intégration GitHub Actions
Section intitulée « Intégration GitHub Actions »Voici comment enchaîner les quatre étapes du workflow, build, publication, installation de Cosign, puis signature, dans un seul job GitHub Actions. Le point clé est le champ id-token: write : c'est lui qui permet à Cosign d'obtenir un jeton OIDC auprès de GitHub pour la signature keyless, sans jamais manipuler de clé privée.
name: Build and Sign
on: push: branches: [main]
jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write # Nécessaire pour la signature keyless
steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false
- name: Build image run: | docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
- name: Login to GHCR uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Push image run: | docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Install Cosign uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Sign image run: | cosign sign --yes ghcr.io/${{ github.repository }}:${{ github.sha }}Alternative native GitHub : si votre pipeline tourne exclusivement sur GitHub Actions, l'action officielle actions/attest-build-provenance (généralement disponible depuis juin 2024) génère une attestation de provenance directement rattachée à l'artefact, sans installer Cosign séparément. Elle repose sur le même socle Sigstore en coulisses, mais simplifie l'intégration pour un usage 100 % GitHub. Le détail de son fonctionnement est repris dans la section suivante sur les attestations.
Isolation et moindre privilège : limiter le blast radius
Section intitulée « Isolation et moindre privilège : limiter le blast radius »Le principe du blast radius (rayon de l'explosion) : en sécurité, on part du principe que tout système finira par être compromis. La question n'est pas "si", mais "quand". L'isolation répond à une autre question : quand mon pipeline sera compromis, quel sera l'impact réel ?
Scénario sans isolation : un runner self-hosted tourne sur un serveur avec accès au réseau interne de l'entreprise. Un attaquant injecte du code dans un workflow qui scanne ce réseau, vole les credentials d'une base de données, exfiltre des données clients. Le blast radius couvre toute l'infrastructure interne.
Scénario avec isolation : le runner tourne dans un container éphémère, dans un réseau isolé, sans accès au réseau interne. Le workflow ne peut atteindre qu'Internet et les services explicitement autorisés. Le blast radius se réduit à un container jetable.
Le ver Shai-Hulud (septembre 2025) illustre pourquoi l'isolation compte autant que la détection : il n'exploitait pas une faille technique nouvelle, il profitait simplement de runners qui avaient accès en clair à des credentials AWS, GCP et npm en mémoire pendant l'exécution du job. Un runner correctement isolé, sans accès réseau superflu et détruit après usage, aurait limité sa capacité d'exfiltration.
Principes d'isolation : les quatre niveaux
Section intitulée « Principes d'isolation : les quatre niveaux »Voici comment cloisonner un pipeline pour minimiser l'impact d'une compromission. Chaque niveau agit sur un axe différent de l'attaque, et c'est leur combinaison qui construit une défense réellement robuste, pas un seul d'entre eux isolément.
| Niveau | Mesure | Bénéfice | Exemple concret |
|---|---|---|---|
| Réseau | Runners dans un réseau isolé | Pas d'accès aux ressources internes | Runner dans un réseau dédié, sans route vers la prod |
| Compute | Runners éphémères (détruits après usage) | Pas de persistance d'une compromission | Container détruit après chaque job, le malware ne persiste pas |
| Permissions | Token avec moindre privilège | Limite les actions possibles | Token read au lieu de write : vol du token égale lecture seule |
| Environnements | Séparation dev/staging/prod | Compromission dev différente de compromission prod | Clusters et comptes cloud séparés |
Pourquoi chaque niveau compte-t-il ? Un runner éphémère avec des permissions excessives peut quand même exfiltrer des secrets pendant sa courte vie. Un runner avec des permissions minimales mais permanent peut servir de point d'entrée pour des attaques futures. La combinaison des quatre crée une défense robuste.
Runners éphémères : effacer les traces
Section intitulée « Runners éphémères : effacer les traces »Le problème des runners permanents : un runner self-hosted qui tourne en continu ressemble à un ordinateur partagé dans un cybercafé. Chaque job laisse des traces, fichiers temporaires, historique shell, credentials en mémoire. Le job suivant hérite de cet environnement pollué.
La solution : runners éphémères. Un runner éphémère est créé pour un seul job, puis détruit complètement : filesystem effacé, mémoire libérée, aucune persistance. Le job suivant part d'un environnement vierge, ce qui coupe court à ce type d'attaque en chaîne.
jobs: build: runs-on: ubuntu-latest # Runner GitHub hébergé, détruit après le job (recommandé)
# Ou pour self-hosted (si vous devez gérer vos propres runners) sensitive-deploy: runs-on: [self-hosted, ephemeral] # Label pour runners éphémèresLes runners GitHub hébergés (ubuntu-latest, windows-latest) sont toujours éphémères. C'est gratuit dans les limites du plan, sécurisé, et sans gestion d'infrastructure. Préférez-les sauf besoin spécifique (GPU, architecture ARM, conformité imposant un hébergement dédié).
Permissions GitHub Actions
Section intitulée « Permissions GitHub Actions »Réduire les permissions au strict minimum limite ce qu'un token compromis peut réellement faire, même si un attaquant parvient à l'exfiltrer via un job malveillant.
# Permissions par défaut restrictivespermissions: contents: read # Lecture du code seulement
jobs: build: # Permissions spécifiques au job si nécessaire permissions: contents: read packages: write # Uniquement pour ce jobAttestations et provenance : prouver le processus de build
Section intitulée « Attestations et provenance : prouver le processus de build »Le problème : une signature prouve que l'artefact est intègre et provient de votre pipeline, mais elle ne dit rien sur comment l'artefact a été construit. Quelles dépendances ? Quel commit Git ? Quel runner ? Quelles variables d'environnement ?
Exemple : votre image Docker est signée, parfait. Mais un développeur malveillant a modifié le Dockerfile pour installer un backdoor avant le build. L'image signée contient le backdoor. La signature est valide, mais l'artefact est compromis dès l'origine.
Les attestations résolvent ce problème en ajoutant des métadonnées vérifiables sur le processus de build : commit source, dépendances utilisées, runner qui a exécuté le build, horodatage. Ces métadonnées sont signées avec l'artefact. Résultat : vous pouvez vérifier comment l'artefact a été construit, pas juste qu'il l'a été.
SLSA : le framework de référence pour la provenance
Section intitulée « SLSA : le framework de référence pour la provenance »SLSA (Supply-chain Levels for Software Artifacts, prononcé "salsa") définit des niveaux de maturité progressifs pour la sécurité de la supply chain. Chaque niveau ajoute des garanties techniques qui rendent la falsification plus difficile. Depuis la spécification v1.0, le framework est organisé en pistes (tracks) : la piste Build, la plus utilisée en CI/CD, compte quatre niveaux allant de L0 à L3 (l'ancien niveau 4 des versions v0.1 a été retiré lors de cette restructuration).
Pourquoi des niveaux ? Parce que viser directement le niveau maximal est irréaliste pour la majorité des organisations. SLSA permet de progresser étape par étape, avec un objectif atteignable à chaque palier.
| Niveau (Build track) | Exigences | Ce que ça prouve | Difficulté de falsification |
|---|---|---|---|
| L0 | Aucune | Aucune garantie | Triviale |
| L1 | Provenance existante | Comment le build a été produit, informations de base | Facile (peut être falsifiée par un humain) |
| L2 | Provenance générée par une plateforme de build hébergée, signée | Build reproductible, non modifié après coup | Moyenne (nécessite de compromettre la plateforme CI) |
| L3 | Build isolé, secrets de signature inaccessibles au code utilisateur | Intégrité garantie même si un développeur est malveillant | Difficile (nécessite de compromettre la plateforme CI elle-même) |
Objectif pragmatique : visez L2 au minimum (atteignable nativement avec GitHub Actions ou GitLab CI), et L3 pour les applications critiques (nécessite une isolation renforcée du build, comme celle qu'offrent les générateurs SLSA officiels).
Génération d'attestations
Section intitulée « Génération d'attestations »Deux approches coexistent aujourd'hui. Pour une conformité SLSA L3 stricte, le générateur officiel du projet SLSA reste la référence, car il isole le build dans une infrastructure dédiée hors de votre contrôle direct :
- name: Generate SLSA provenance uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0 with: image: ghcr.io/${{ github.repository }} digest: ${{ steps.build.outputs.digest }}Pour un usage plus simple, centré sur GitHub, l'action officielle actions/attest (successeur de attest-build-provenance) génère une attestation directement liée à l'artefact publié, sans infrastructure supplémentaire :
jobs: build: runs-on: ubuntu-latest permissions: id-token: write contents: read attestations: write packages: write
steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false
- name: Build and push image id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Generate artifact attestation uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: trueCette seconde approche suffit à atteindre un niveau proche de SLSA L2 sans opérer d'infrastructure de build dédiée, ce qui la rend accessible à la majorité des équipes qui débutent sur ce sujet.
Checklist pipeline sécurisé
Section intitulée « Checklist pipeline sécurisé »Cette checklist sert de fil conducteur pour évaluer où en est votre pipeline aujourd'hui, sans viser une perfection immédiate : elle est volontairement organisée en trois paliers de maturité croissante, du socle indispensable jusqu'aux pratiques réservées aux équipes DevSecOps avancées.
Basique (à avoir immédiatement)
Section intitulée « Basique (à avoir immédiatement) »Ces quatre points forment le minimum vital : sans eux, un incident de sécurité n'est pas une question de "si" mais de "quand". Ils se mettent en place en quelques jours sur un pipeline existant, sans réorganisation profonde.
- Secrets dans un gestionnaire (pas dans le code)
- Scan de secrets en pre-commit
- SAST sur les PR
- SCA sur les dépendances
Intermédiaire (à planifier)
Section intitulée « Intermédiaire (à planifier) »Ce palier demande davantage de coordination avec les équipes infrastructure (runners, environnements GitHub) mais reste atteignable en quelques semaines pour une équipe qui maîtrise déjà le socle basique.
- Runners éphémères
- Permissions minimales
- Environnements séparés (dev/staging/prod)
- Signature des images
Avancé (maturité DevSecOps)
Section intitulée « Avancé (maturité DevSecOps) »Ces pratiques ciblent les organisations avec des exigences de conformité fortes ou des artefacts particulièrement sensibles : elles demandent un investissement outillage plus consistant, mais réduisent fortement la surface d'attaque résiduelle.
- Attestations SLSA ou GitHub Attestations
- SBOM pour tous les artefacts
- Vérification des signatures avant déploiement
- Rotation automatique des secrets
À retenir
Section intitulée « À retenir »- Security gates : points de contrôle automatiques à chaque étape (pre-commit, PR, build, deploy), avec le principe du fail fast.
- Secrets : jamais dans le code, rotation régulière, moindre privilège, tokens éphémères de préférence aux secrets statiques.
- Signature : Cosign/Sigstore (ou l'action native GitHub) pour garantir l'intégrité et la provenance des artefacts.
- Isolation : runners éphémères, permissions minimales, environnements séparés, quatre niveaux complémentaires.
- Attestations : SLSA (L0 à L3 sur la piste Build) ou attestations GitHub pour prouver comment un artefact a été construit.
- Pinner par SHA, pas par tag : la compromission tj-actions de 2025 a démontré qu'un tag mutable comme
@v4peut être redirigé vers un commit malveillant. - La supply chain est désormais un risque de premier plan : l'OWASP Top 10:2025 lui consacre une catégorie dédiée, aux côtés des vulnérabilités applicatives classiques.