Les pipelines CI/CD cloud concentrent trois propriétés qui en font une cible prioritaire pour les attaquants en 2026 : privilèges élevés sur les environnements cibles, agrégation de secrets multiples (tokens cloud, credentials registries, clés API), exécution massive de code tiers (GitHub Actions, npm, Docker base images). Les incidents supply chain majeurs 2020-2025 — SolarWinds, Codecov, CircleCI, Ultralytics, xz-utils — ont tous transité par un pipeline CI/CD compromis. La défense moderne se structure autour de 7 chapitres : identité éphémère via OIDC, secrets externalisés en vault, isolation des runners, contrôle strict des plugins tiers, signature d'artefacts et provenance SLSA, protection du code source (branch protection, signed commits), GitOps sécurisé (ArgoCD, Flux). Cet article détaille chaque chapitre avec manifests GitHub Actions et GitLab CI complets, incidents documentés, check-list PR-ready et pièges courants.
La surface d'attaque d'un pipeline cloud en 2026
Un pipeline CI/CD moderne expose sept surfaces d'attaque distinctes, chacune avec des contrôles dédiés.
| Surface | Vecteur d'attaque type | Contrôle principal |
|---|---|---|
| Code source | Commit malveillant direct, PR injecté | Branch protection, signed commits, code review |
| Pipeline definition | Modification du workflow YAML | Workflow approval, permissions minimales |
| Runners CI/CD | Compromission runner partagé | Isolation, runners self-hosted hardened |
| Actions / plugins tiers | Action compromise ou malicieuse | Pin par SHA, allow-list, audit dependency |
| Secrets pipeline | Fuite via log, variable, artefact | Vault externe, masking, OIDC federation |
| Artefacts de build | Substitution, tampering | Signing Cosign, SLSA provenance |
| Cible de déploiement | Persistance via identité pipeline | OIDC éphémère, scopes restreints, audit |
Incidents majeurs de supply chain CI/CD 2020-2025
Comprendre les incidents récents est le meilleur moyen de calibrer ses défenses.
SolarWinds — décembre 2020
- Vecteur : compromission du serveur de build SolarWinds Orion, injection de malware (SUNBURST) dans les artefacts officiels.
- Impact : 18 000 organisations impactées, dont 9 agences fédérales US.
- Leçon : isolation du serveur de build, provenance non-falsifiable (SLSA niveau 3+), monitoring comportemental des builds.
Codecov — avril 2021
- Vecteur : modification du bash uploader (ligne unique altérée) qui exfiltrait les variables d'environnement des CI clients.
- Impact : 29 000 clients potentiellement impactés, rotation massive de secrets cloud dans l'industrie.
- Leçon : pin des outils CI par SHA (pas par tag), signing des exécutables CI, secrets éphémères via OIDC plutôt que variables d'environnement.
Kaseya — juillet 2021
- Vecteur : exploitation de vulnérabilités dans le serveur Kaseya VSA suivi de déploiement de REvil ransomware à tous les clients via le pipeline de mise à jour.
- Impact : 1 500 entreprises, demande de rançon 70 M USD.
- Leçon : hardening du serveur de déploiement central, approbation humaine pour rollouts critiques.
CircleCI — janvier 2023
- Vecteur : compromission du laptop d'un ingénieur via malware dérivant d'un phishing, vol de tokens de session 2FA valides, accès aux secrets clients stockés.
- Impact : tous les secrets clients CircleCI à rotater sous 48 heures, alerte mondiale.
- Leçon : secrets stockés dans le CI = surface de vol ; privilégier OIDC federation qui ne stocke aucun secret.
Ultralytics / PyPI — mars 2024
- Vecteur : compromission du pipeline GitHub Actions du projet ultralytics (> 30M téléchargements/mois), publication de versions malicieuses sur PyPI.
- Leçon : PyPI a accéléré le support OIDC federation et trusted publishing pour éliminer les PyPI API tokens long-terme.
xz-utils backdoor — mars 2024
- Vecteur : ingénierie sociale sophistiquée sur 2+ ans via un contributeur infiltré (« Jia Tan »), insertion progressive d'une backdoor dans la bibliothèque de compression xz utilisée par OpenSSH en Linux.
- Impact : détection in-extremis avant large déploiement production par un ingénieur Microsoft (Andres Freund). Aurait permis RCE pre-auth sur la majorité des serveurs Linux.
- Leçon : review renforcée des contributions externes sur projets critiques, build reproducible, audit des commits binaires (blob) cachés dans les tests.
tj-actions/changed-files compromise — mars 2025
- Vecteur : action GitHub populaire compromise, injection d'un code exfiltrant les secrets CI des workflows consommateurs via PR sur tag
v45. - Leçon : pin systematique des actions par SHA commit plutôt que tag (qui est mutable côté auteur).
1. Identité et OIDC federation
La première ligne de défense est d'éliminer les secrets long-terme côté pipeline.
OIDC federation : principe
Le pipeline CI (GitHub Actions, GitLab CI, Bitbucket Pipelines, CircleCI) génère un token OIDC signé à chaque workflow. Le cloud cible (AWS, Azure, GCP) vérifie ce token et émet des credentials éphémères (5-60 minutes) scopés au contexte exact du workflow (repo, branche, environnement).
Exemple GitHub Actions → AWS via OIDC
name: Deploy to AWS
on:
push:
branches: [main]
permissions:
id-token: write # OIDC token generation
contents: read # checkout repo
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 pinned by SHA
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-prod
aws-region: eu-west-3
role-session-name: github-actions-${{ github.run_id }}
- name: Deploy application
run: |
aws s3 sync ./dist s3://my-app-production/ --delete
aws cloudfront create-invalidation --distribution-id E1234567890ABC --paths "/*"Trust policy AWS associée (Terraform)
resource "aws_iam_role" "github_actions_deploy" {
name = "github-actions-deploy-prod"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:my-org/my-repo:ref:refs/heads/main"
}
}
}]
})
}Le sub restreint l'assumption du rôle à un repo + branche précis. Variantes possibles : environment:production, pull_request, ref:refs/tags/*.
Support OIDC en 2026
| Plateforme CI | Cloud cibles supportés |
|---|---|
| GitHub Actions | AWS, Azure, GCP, Vault, Snowflake, PyPI (trusted publishing), npm |
| GitLab CI | AWS, Azure, GCP, Vault, HashiCorp Cloud |
| Bitbucket Pipelines | AWS, GCP (depuis 2023) |
| CircleCI | AWS, GCP (depuis 2022) |
| Jenkins | Via plugins OIDC Provider |
2. Secrets management
Les secrets restants (API keys tierces, certificats non-OIDC, tokens d'accès) doivent vivre hors du pipeline.
Patterns acceptables en 2026
- Vault externe : HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager. Le pipeline récupère le secret à l'exécution via OIDC, consommation éphémère.
- SOPS (Mozilla) : secrets encryptés dans Git avec GCP KMS, AWS KMS, Azure Key Vault ou GPG. Le pipeline décrypte à l'exécution.
- Sealed Secrets (Bitnami) : uniquement Kubernetes, secrets encryptés dans Git avec clé publique cluster.
- External Secrets Operator (ESO) : pour Kubernetes, synchronise depuis Vault / Secrets Manager vers Secret K8s.
Anti-patterns absolus
- Secrets en variables GitHub Actions / GitLab CI comme valeurs persistantes (disponibles sur tous les workflows).
- Secrets en
env:d'un workflow YAML committé. - Secrets dans des fichiers non-chiffrés de configuration.
- Secrets écrits dans les logs pendant debug (GitHub Actions masking échoue sur plusieurs patterns multi-lignes, base64, JSON).
Scanning obligatoire
- gitleaks ou trufflehog en pre-commit hook et en CI.
- GitHub secret scanning activé sur tous les repos (gratuit sur public, payant GHAS sur private).
- GitGuardian ou équivalent pour alerting central multi-repo.
3. Isolation des runners
Les runners CI exécutent du code tiers en continu. Leur isolation est critique.
GitHub-hosted runners (SaaS GitHub)
- VM éphémère par job, détruite après chaque exécution.
- Pas d'accès persistant, mais exécute tous les workflows sur les mêmes subnets GitHub.
- Adapté aux projets sans exigence de confidentialité forte.
Self-hosted runners
- Contrôle total mais risque d'utilisation pour miner, persister, pivot réseau.
- Règles non négociables : éphémérité (runners détruits après chaque job via Actions Runner Controller ou equivalent), network policies restrictives, pas de runners partagés entre repos publics et privés, OS minimal.
- Actions Runner Controller (ARC) sur Kubernetes est le standard 2026 : pod éphémère par job, auto-scaling, isolation par namespace.
GitHub Enterprise + StepSecurity Harden-Runner
StepSecurity propose un outil open-source qui durcit les runners : détection d'appels réseau anormaux pendant les jobs, file system monitoring, alert en cas de process suspect. Déploiement via une action standard ajoutée en première étape de chaque workflow.
steps:
- name: Harden Runner
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.10.1
with:
egress-policy: audit # ou block pour enforcement
allowed-endpoints: >
github.com:443
objects.githubusercontent.com:443
registry.npmjs.org:4434. Contrôle des actions et plugins tiers
Les pipelines modernes consomment massivement des actions externes. Chaque action tierce est une dépendance exécutable avec les privilèges du pipeline.
Règles 2026
- Pin par SHA commit, jamais par tag. Un tag (v1, v4.2.0) est mutable côté auteur : l'auteur peut repointer le tag vers un commit différent à tout moment. Le SHA est immuable.
- Restreindre les actions autorisées au niveau organisation : Settings → Actions → Allow list. Autoriser uniquement les actions vérifiées GitHub, internes à l'organisation, et une liste explicite de tiers validés.
- Audit régulier des actions utilisées via dependency graph.
- Automatiser les mises à jour via Dependabot ou Renovate avec review systématique.
- Activer GitHub Advanced Security (GHAS) si payant : scan des workflows, détection de secrets, dependency review.
Configuration Dependabot pour mises à jour GitHub Actions
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
labels:
- dependencies
- github-actions
- security5. Signature et provenance (Cosign, SLSA)
Signer les artefacts produits est la garantie que seul un pipeline autorisé a pu les créer et qu'ils n'ont pas été altérés depuis.
SigStore Cosign keyless (standard 2026)
Cosign, projet OpenSSF, combiné à Fulcio (CA éphémère) et Rekor (transparency log immuable) permet de signer sans gérer de clé privée : le pipeline obtient un certificat éphémère lié à son identité OIDC (GitHub Actions workflow + repo + commit).
- name: Install Cosign
uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0
- name: Sign container image keyless
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign sign --yes \
ghcr.io/my-org/my-app:${{ github.sha }}
- name: Generate SBOM
uses: anchore/sbom-action@251a468eed47e5082b105c3ba6ee500c0e65a764 # v0.17.7
with:
image: ghcr.io/my-org/my-app:${{ github.sha }}
format: cyclonedx-json
output-file: sbom.cyclonedx.json
- name: Attach SBOM to signed image
run: |
cosign attach sbom --sbom sbom.cyclonedx.json \
ghcr.io/my-org/my-app:${{ github.sha }}SLSA framework — niveaux 2026
| Niveau | Exigences | Atteinte 2026 |
|---|---|---|
| SLSA 1 | Build documenté, provenance générée | Trivial sur GitHub Actions |
| SLSA 2 | Tamper resistance build service, provenance authentifiée | Natif GitHub Actions + slsa-github-generator |
| SLSA 3 | Source et build hardened, provenance non-falsifiable | Nécessite hermetic builds + isolation |
| SLSA 4 | Retiré en v1.0, intégré aux autres niveaux | — |
Recommandation : SLSA niveau 2 pour tout nouveau projet cloud. SLSA niveau 3 pour les projets critiques (éditeurs, SaaS B2B régulés, fournisseurs OIV).
Générateur SLSA GitHub Actions
jobs:
build:
# ... build steps ...
provenance:
needs: build
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
base64-subjects: ${{ needs.build.outputs.hashes }}
upload-assets: true6. Sécurité du code source
Le code source est le début de la chaîne. Sa protection est prérequis.
Branch protection rules (GitHub / GitLab)
- Merge uniquement via PR approuvée.
- Review obligatoire par 1-2 membres distincts (CODEOWNERS).
- Status checks CI doivent passer avant merge.
- Historique linéaire (pas de force-push sur main).
- Signed commits obligatoires sur main.
- Admins inclus dans les restrictions (pas d'override silencieux).
Exemple configuration GitHub branch protection via API
gh api repos/my-org/my-repo/branches/main/protection \
--method PUT \
--input - <<EOF
{
"required_status_checks": {
"strict": true,
"contexts": ["security-checks", "unit-tests", "build"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 2,
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true
},
"restrictions": null,
"required_linear_history": true,
"required_signatures": true,
"allow_force_pushes": false,
"allow_deletions": false
}
EOFSigned commits
- GPG ou SSH signing supporté par Git depuis 2015 (GPG) et 2022 (SSH).
- GitHub affiche le statut
Verifiedsur les commits signés. - Configuration minimale via 1Password ou YubiKey pour stocker la clé.
CODEOWNERS
Fichier .github/CODEOWNERS qui assigne automatiquement les reviewers selon le chemin modifié.
# Tous les changements infrastructure nécessitent approbation DevSecOps
/terraform/ @my-org/devsecops-team
/kubernetes/ @my-org/devsecops-team
/.github/workflows/ @my-org/devsecops-team @my-org/security-team
/helm-charts/ @my-org/platform-team
# Sécurité
SECURITY.md @my-org/security-team
7. GitOps sécurisé (ArgoCD, Flux)
GitOps fait du repo Git la source de vérité unique pour l'état des clusters. Propriété précieuse mais qui concentre de nouveaux risques.
Patterns de sécurisation ArgoCD / Flux
- Repo Git immutable : aucun
kubectl applydirect en prod (réservé break-glass avec audit). - Secrets encryptés en Git : SOPS, Sealed Secrets, External Secrets Operator.
- Signed commits obligatoires sur la branche GitOps. ArgoCD configuration :
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-prod
namespace: argocd
spec:
project: production
source:
repoURL: https://github.com/my-org/my-gitops-repo.git
targetRevision: main
path: prod/my-app
destination:
server: https://kubernetes.default.svc
namespace: my-app-prod
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- Validate=true
- CreateNamespace=false- Isolation ArgoCD par Project : whitelist destinations (clusters, namespaces autorisés), sourceRepos whitelist, clusterResourceWhitelist strict.
- Admission control en validation : OPA Gatekeeper ou Kyverno rejette les manifests non-conformes même si présents dans le Git.
Exemple de pipeline hardening end-to-end
Workflow GitHub Actions complet combinant les 7 chapitres.
name: Secure CI/CD Pipeline
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
packages: write
attestations: write
jobs:
security-and-deploy:
runs-on: ubuntu-latest
steps:
# Harden runner (step 3 — isolation)
- name: Harden Runner
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6
with:
egress-policy: audit
# Checkout (step 6 — code source)
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
fetch-depth: 0
# Secrets scan (step 2 — secrets)
- name: Scan for secrets
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# SAST (chaptire 4 — plugins tiers contrôlés)
- name: SAST Semgrep
uses: semgrep/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d
with:
config: p/ci p/owasp-top-ten
# SCA + container scan (chapter 4)
- name: Scan filesystem
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0
with:
scan-type: fs
severity: CRITICAL,HIGH
exit-code: 1
ignore-unfixed: true
# Build (step 5 — artefact)
- name: Build Docker image
run: docker build -t ghcr.io/${{ github.repository }}:${{ github.sha }} .
# Sign image (step 5 — signing)
- name: Install Cosign
uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382
- name: Login to GHCR
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push and sign image
env:
COSIGN_EXPERIMENTAL: "true"
run: |
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
cosign sign --yes ghcr.io/${{ github.repository }}:${{ github.sha }}
# SBOM generation + attach (step 5 — provenance)
- name: Generate SBOM
uses: anchore/sbom-action@251a468eed47e5082b105c3ba6ee500c0e65a764
with:
image: ghcr.io/${{ github.repository }}:${{ github.sha }}
format: cyclonedx-json
output-file: sbom.cyclonedx.json
- name: Attach SBOM
env:
COSIGN_EXPERIMENTAL: "true"
run: |
cosign attach sbom --sbom sbom.cyclonedx.json \
ghcr.io/${{ github.repository }}:${{ github.sha }}
# Deploy via OIDC (step 1 — identité)
- name: Configure AWS via OIDC
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-prod
aws-region: eu-west-3
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster prod \
--service my-app \
--force-new-deploymentCheck-list PR-ready pipeline security
À systématiser dans toute PR qui touche un workflow CI/CD.
- Top du workflow :
permissions:minimal (pas dewrite-all). - Toutes les actions tierces pinnées par SHA commit (jamais par tag seul).
- Pas de secrets en variables d'environnement dans le YAML committé.
- OIDC federation pour toute authentification cloud, pas d'access keys.
- Trust policy cloud scopée par
repo+branch+environment. - Branch protection rules activées sur main (reviews, status checks, linear history, signed commits).
- CODEOWNERS défini pour chemins sensibles (workflows, Terraform, Kubernetes).
- Harden-Runner ou équivalent ajouté en première étape.
- Secrets scan (gitleaks) en pre-commit et en CI.
- Dependabot ou Renovate configuré pour actions et dépendances.
- Artefacts signés via Cosign avec OIDC keyless.
- SBOM généré et attaché à chaque release.
- SLSA niveau 2 minimum pour artefacts publiables.
- Pull_request_target évité sauf justification documentée.
- Environnement production avec protection rules (required reviewers).
- Logs CI configurés pour masquer correctement les secrets multi-lignes et base64.
Pièges courants
Utiliser pull_request_target sans précaution. Ce trigger exécute le workflow du repo de destination (pas du fork) avec accès aux secrets. Utilisé sans validation stricte, c'est le vecteur d'attaque n°1 contre les repos open-source. Alternative : pull_request + approvisionnement manuel pour les PR externes.
Copier-coller un workflow depuis un tuto avec contents: write en default. Chaque workflow doit déclarer explicitement le minimum de permissions nécessaires. permissions: read-all est souvent suffisant pour les jobs CI.
Confondre secrets GitHub Actions et secrets vault. Les secrets GitHub Actions sont stockés par GitHub, accessibles à tout workflow du repo, rotation manuelle. Un vault externe + OIDC est plus sûr pour tout secret sensible (credentials cloud, tokens API critiques).
Oublier de vérifier les signatures à l'apply. Signer sans vérifier est inutile. ArgoCD doit vérifier les signatures avant apply (commit.required: true). Kubernetes admission controller (Kyverno ou Gatekeeper) doit rejeter les images non signées via policy.
Ne pas segmenter les environnements GitHub. Dev, staging, prod doivent être des GitHub Environments distincts avec : protection rules, required reviewers, secrets propres, wait timers.
Exécuter des self-hosted runners sur des repos publics sans isolation. Ouvre la porte à l'exécution de code arbitraire via une PR externe. Règle : self-hosted runners uniquement pour repos privés ou en mode ephemeral strict + isolation K8s.
Laisser ArgoCD ou Flux avec cluster-admin. Pratique commune en démarrage mais dangereuse. Scoper strict par Projects + RBAC namespace.
Ne pas auditer les actions utilisées sur plusieurs mois. Un repo peut accumuler 30-50 actions tierces hétérogènes avec historiques variables. Audit annuel minimum : consolider sur un nombre restreint d'actions vérifiées.
Points clés à retenir
- 7 surfaces d'attaque : code source, pipeline definition, runners, actions tierces, secrets, artefacts, cibles de déploiement.
- OIDC federation est le standard 2026 : zéro access key long-terme dans les pipelines cloud.
- Pin des actions par SHA commit, jamais par tag (mutable). Attaques tj-actions mars 2025 ont démontré l'impact.
- Cosign + SigStore keyless pour signer chaque artefact sans gérer de clé privée.
- SLSA niveau 2 cible minimale pour tout projet cloud moderne, niveau 3 pour projets critiques.
- SBOM via Syft ou Trivy, format CycloneDX dominant, obligatoire CRA européen 2027.
- Branch protection rules sur main : reviews, signed commits, linear history, CODEOWNERS.
- GitOps sécurisé : secrets encryptés en Git (SOPS, Sealed Secrets, ESO), signed commits verification, ArgoCD Projects scoped.
- Harden-Runner ou Actions Runner Controller pour isolation runners.
- 16 points check-list PR à systématiser pour tout changement de workflow CI/CD.
Pour aller plus loin
- IAM expliqué simplement — fondation des identités cloud consommées par les pipelines.
- Outils DevSecOps à connaître en 2026 — stack incluant SCA, SAST, SBOM, signing, CSPM cités ici.
- OPA Open Policy Agent : définition, Rego et cas d'usage 2026 — policy-as-code pour valider les artefacts avant déploiement.
- Sécurité Kubernetes pour développeurs — cible de déploiement finale des pipelines GitOps.
- Roadmap Cloud Security 2026 — parcours d'apprentissage cloud security complet.
- Roadmap DevSecOps 2026 — parcours d'apprentissage DevSecOps couvrant pipelines sécurisés.







