Sécurité CI/CD : protéger dépôts, runners, secrets et artefacts

Sécurité Renard en origami avec bouclier et clé devant un serveur CI/CD.

Table des matières :

  1. Sécurité CI/CD : la surface d’attaque que tout le monde « découvre » après l’incident
  2. Protéger les dépôts Git : identité, permissions, et intégrité du code (pas juste des reviews)
  3. Sécuriser les runners : l’endroit où le code s’exécute… donc où tout finit par fuir
  4. Secrets : arrêter de les coller dans des variables d’environnement (et d’espérer)
  5. Artefacts et dépendances : signer, attester, vérifier — sinon vous déployez l’inconnu
  6. Opérer la sécurité CI/CD : logs, métriques, alertes… et le jour où ça brûle

Sécurité CI/CD : la surface d’attaque que tout le monde « découvre » après l’incident

La sécurité CI/CD n’est pas un bonus “quand on aura le temps”. C’est la discipline qui empêche votre pipeline de devenir un distributeur automatique de backdoors en production. Un pipeline CI/CD, c’est une chaîne de confiance : dépôt → build → tests → packaging → artefacts → déploiement. Si un maillon est compromis, le reste suit, avec l’enthousiasme naïf d’un curl | bash.

Techniquement, on parle de sécurisation de la supply chain logicielle : garantir l’intégrité du code source, la fiabilité de l’environnement de build, la confidentialité des secrets et l’immutabilité (ou au minimum la traçabilité) des artefacts. Les quatre actifs qui se font le plus régulièrement dépouiller sont exactement ceux du titre : dépôts, runners, secrets, artefacts. Le reste (SAST/DAST, scan conteneurs, IaC scanning) est important, mais c’est de l’hygiène — pas une ceinture de sécurité.

Pour cadrer le sujet, une menace CI/CD “réaliste” ressemble souvent à ça :

  • Prise de compte (phishing, réutilisation de mot de passe, token volé) → push “légitime” sur le dépôt.
  • Modification du pipeline (workflow YAML, script de build, action tierce) → exfiltration de secrets ou injection d’un payload.
  • Empoisonnement des artefacts (image OCI, paquet, binaire) → déploiement “automatique” en prod.
  • Persistance (tag retouché, runner long-lived, cache empoisonné) → réinfection à chaque build.

L’actualité récente a rappelé que l’attaque “par le code” est industrialisée : compromission de mainteneurs, attaques sur dépendances, sabotage de pipeline, empoisonnement d’artefacts. Dans ce contexte, des cadres comme SLSA (Supply-chain Levels for Software Artifacts) existent pour rendre la chaîne de build plus vérifiable et moins “basée sur la foi”. En clair : soit vous rendez votre chaîne de build vérifiable, soit vous faites confiance à des composants que vous ne contrôlez pas (spoiler : c’est rarement une bonne idée).

Enfin, un point souvent oublié quand on parle “GEO” (France/UE) : la CI/CD concentre des données sensibles (code, secrets, journaux d’exécution, identifiants cloud). La question n’est pas seulement “où c’est hébergé”, mais qui y a accès, où partent les logs, et quel est votre modèle d’exploitation (équipes internes, prestataires, multi-sites). Ces éléments influencent directement les choix de durcissement (SSO, segmentation réseau, rétention des logs, etc.).

Protéger les dépôts Git : identité, permissions, et intégrité du code (pas juste des reviews)

Le dépôt est le point de départ de toute compromission CI/CD. On y attaque l’identité (compte développeur), l’autorisation (droits excessifs), et l’intégrité (modification discrète). La base : SSO, MFA/2FA, politiques d’accès minimales, suppression des comptes dormants, et séparation claire entre read, write, admin. C’est vieux comme Git, mais 2026 n’a pas rendu ça automatique par magie.

Quelques contrôles qui changent vraiment la donne (et qui coûtent souvent moins qu’un incident) :

  • Réduction de la surface “identité”
  • Interdire/limiter les PAT longue durée, privilégier des tokens fins et expirables (quand la plateforme le permet).
  • Bloquer les comptes personnels sur les actions d’administration (règle “admin via SSO + MFA + device compliant”).
  • Créer des comptes “machine” dédiés pour les automates, avec scopes minimaux.
  • Branch protection “sérieuse”
  • Interdire force-push et delete branch sur les branches sensibles.
  • Exiger des checks (tests, lint, scan dépendances) avant merge.
  • Imposer une review de CODEOWNERS pour les zones critiques (ex : .github/workflows/, scripts de release, chart Helm, infra).
  • Intégrité et traçabilité
  • Signer les commits/tags et, surtout, rendre la signature significative : par exemple “tout tag de release est signé” et “le build prod doit pointer vers un tag signé”.
  • Rendre les releases reproductibles autant que possible (verrouillage versions, builds déterministes quand c’est réaliste).

Ensuite, il y a la sécurité “qui pique un peu” : branch protection (merge interdit sans reviews, checks obligatoires), CODEOWNERS, et surtout signature des commits/tags. La signature ne vous protège pas d’un mainteneur compromis… mais elle rend les attaques silencieuses plus difficiles, et l’investigation moins ésotérique. Ajoutez une règle : tout release tag est signé, et tout build en prod doit pointer vers un tag signé (et pas “main à 17h48 un vendredi”).

Enfin, sécuriser les dépôts, c’est aussi sécuriser ce qui s’exécute à cause du dépôt : actions, templates, hooks, bots. Sur GitHub Actions, par exemple, le guide officiel insiste sur la réduction des privilèges et le contrôle des actions tierces (Security hardening for GitHub Actions). Concrètement : définissez permissions: au plus bas, épinglez vos actions par SHA (pas juste @v4), et méfiez-vous des événements type pull_request_target qui exposent des secrets à du code non fiable.

Mini-scenario (classique) : un attaquant obtient un accès write sur un repo, modifie un workflow pour ajouter une étape “debug” qui imprime l’environnement, puis déclenche le pipeline sur une branche qui a accès aux secrets. La parade n’est pas magique ; elle est procédurale et technique :

  • protéger .github/workflows/ via CODEOWNERS + review obligatoire,
  • limiter les permissions par défaut du token CI,
  • isoler les jobs déclenchés par PR externes (pas de secrets, runner “untrusted”),
  • alerter sur les changements de fichiers pipeline (cf. section “logs/alertes”).

Pour aller plus loin côté démarche, votre pipeline doit ressembler à un produit sécurisé, pas à un bricolage YAML. L’approche DevSecOps est justement d’intégrer ces contrôles “au fil de l’eau” ; si vous cherchez un cadre opérationnel, le guide interne DevSecOps-as-a-service : intégrer la sécurité au pipeline CI/CD pose de bonnes bases (et évite le syndrome du “scan à la fin”).

En complément, pour évaluer rapidement la “maturité supply chain” d’un dépôt (sans inventer une checklist), OpenSSF Scorecard donne un signal utile sur des pratiques comme la protection des branches, la revue, les signatures, etc. : Security Scorecards

Sécuriser les runners : l’endroit où le code s’exécute… donc où tout finit par fuir

Un runner (ou agent CI) est l’exécuteur de jobs : il récupère le code, installe des dépendances, compile, teste, build des images, publie des artefacts, déploie. Traduction sécurité : c’est l’endroit où du code (parfois non fiable) obtient du CPU, du réseau, et — si vous n’êtes pas vigilant — des secrets. Un runner compromis, c’est une machine de pivot parfaite vers votre cloud, votre registry, votre cluster, et votre SI.

La règle qui fâche les équipes “on optimise les builds” : un runner doit être éphémère. Idéalement un job = une VM (ou un pod) jetable, sans état persistant, reconstruite depuis une image durcie, patchée, et mesurée. Les runners long-lived (VM qui vit des mois) sont des aquariums à malwares : cache non maîtrisé, dépendances persistance, clés SSH oubliées, Docker layer cache empoisonnable, etc. Si vous faites du self-hosted, le durcissement OS compte ; le guide VPS Linux : guide de déploiement, durcissement et maintenance proactive vous rappelle qu’un agent CI n’est pas un poste de dev.

Deux décisions structurantes à poser explicitement (sinon elles se “posent toutes seules” au pire moment) :

Sujet Option “confort” Option “secure by design” Risque principal
Durée de vie runner VM/pod persistant avec cache Runner éphémère (VM/pod par job) persistance, infection, exfiltration récurrente
Confiance du code mêmes runners pour tout runners séparés trusted / untrusted secrets exposés à des PR/forks

Côté isolation, évitez le grand classique : monter le socket Docker (/var/run/docker.sock) dans un job “pour builder vite”. C’est pratique, oui : ça donne aussi un accès root au host dans beaucoup de scénarios. Préférez des builds rootless (BuildKit rootless), Kaniko, ou des runners VM avec privilèges stricts.

Ajoutez aussi une couche que beaucoup négligent : le contrôle réseau des runners.

  • En sortie (egress), une CI n’a généralement pas besoin d’Internet “ouvert” : elle a besoin d’accéder à votre SCM, votre registry, quelques endpoints (ex : dépôts de dépendances), et vos API cloud. Une allowlist réduit drastiquement l’exfiltration.
  • En interne, segmentez : un runner de build ne devrait pas avoir un chemin direct vers des réseaux de prod.

Si vos runners sont dans Kubernetes (GitHub Actions Runner Controller, GitLab Runner, Argo workflows), vos contrôles K8s deviennent critiques : PSP n’existe plus, donc vous jouez avec Pod Security Admission, seccomp, AppArmor, NetworkPolicies, et quotas. Dans beaucoup d’organisations, le cluster CI finit par devenir une “zone grise” entre dev et prod : c’est précisément là qu’un plan de durcissement est utile, comme Sécurisation Kubernetes : plan d’action 30 jours pour PME.

Checklist runner (pratique, sans dogme) :

  • runner éphémère ou, a minima, nettoyage strict (workspace, caches, credentials) + reconstruction régulière.
  • jobs “non fiables” (forks/PR externes) sur runners dédiés sans secrets.
  • pas de --privileged, pas de montage du docker.sock, pas de hostPath non contrôlé en K8s.
  • egress contrôlé (DNS/HTTP) et logs réseau exploitables.
  • inventaire des runners (qui a le droit d’en enregistrer ? où ?), avec alerte sur tout nouvel enregistrement.

Secrets : arrêter de les coller dans des variables d’environnement (et d’espérer)

Dans un pipeline CI/CD, un secret c’est tout ce qui permet d’agir au nom du système : token Git, clé cloud, identifiants registry, certificat de signature, webhook secret, mot de passe de DB de staging (oui, c’est aussi un secret). La plupart des fuites ne viennent pas d’un APT mystique, mais d’un mix toxique : logs trop verbeux, permissions trop larges, PR non fiables, et secrets longue durée.

Le pattern moderne, c’est moins de secrets statiques et plus d’identités éphémères. Exemple : sur GitHub Actions, utilisez OIDC (id-token: write) pour obtenir des credentials temporaires (AWS/GCP/Azure) au lieu de stocker des clés IAM. Avec Vault, privilégiez des tokens short-lived + policies minimales, et des secrets dynamiques (DB creds générés). Objectif mesurable : une clé exposée doit être révoquée en moins de 1h, et la rotation automatisée doit être testée (sinon ce n’est pas un processus, c’est un espoir).

Une façon simple de raisonner (et de convaincre) est de classer les secrets par portée et durée de vie :

Secret Où il doit vivre TTL cible Contrôle clé
Accès cloud (deploy) identité éphémère via OIDC minutes policies minimales + audit
Token registry (push) identité technique à scope réduit heures/jours rotation + accès par environnement
Certificat/clé de signature HSM ou service dédié long (mais protégé) accès break-glass + traçabilité
DB staging/dev secrets dynamiques si possible minutes/heures révocation automatisée

Le point qui fait perdre du temps aux équipes après incident : le secret n’est pas juste “au repos”, il est surtout “en transit” et “en usage”. Masquage de variables ne protège pas contre l’exfiltration par requête sortante. Donc : egress control (au moins DNS/HTTP), allowlist vers vos registries et API cloud, et interdiction de jobs secrets sur des événements non fiables (forks, PR externes).

Quelques mauvaises surprises fréquentes (à corriger avant l’incident) :

  • un secret injecté en variable d’environnement est récupérable par n’importe quel binaire exécuté dans le job (y compris une dépendance compromise) ;
  • un job “debug” ou un script trop bavard peut logguer des éléments sensibles ;
  • des environnements “staging” trop permissifs donnent un chemin vers la prod (mêmes identifiants, mêmes VPC, mêmes permissions).

Et si vous avez besoin d’industrialiser la gestion et le partage (humain) des secrets, la page Gestion & transmission de mots de passe par Vikings Technologies rappelle que “envoyer un mot de passe sur Slack” n’est pas un mécanisme de sécurité, juste une tradition.

Runbook “secret leak” (résumé opérationnel) :

  1. Révoquer immédiatement (pas “après l’analyse”).
  2. Tourner les secrets dépendants (ex. si un token permet de générer d’autres tokens).
  3. Chercher l’empreinte : logs CI, artefacts de job, historiques de commandes, accès registry.
  4. Bloquer la récidive : règle repo + pipeline (jobs non fiables, permissions, logs, egress).
  5. Rétablir proprement via des identités temporaires et des scopes minimaux.

Artefacts et dépendances : signer, attester, vérifier — sinon vous déployez l’inconnu

Un artefact CI/CD, c’est ce que vous livrez : binaire, package npm, wheel Python, image Docker/OCI, chart Helm, bundle Terraform, archive front, etc. Et comme c’est précisément ce qui part en prod, c’est aussi la cible la plus rentable pour un attaquant. L’attaque supply chain typique : on ne touche pas la prod, on touche l’artefact en amont, et la prod se contamine “légalement”. Pratique, non.

La défense moderne repose sur trois piliers : immutabilité, signature, provenance.

  • Immutabilité : registry/artefact repository configuré en write-once (ou versionné), rétention, audit logs, et promotion d’environnements (dev → staging → prod) par références immuables (digest, checksum), pas par tags flottants.
  • Signature : Sigstore (Sigstore) / cosign signe vos images et peut attester des bundles. L’idée : “ce qui n’est pas signé comme attendu n’a pas le droit d’exister en prod”.
  • Provenance : in-toto / SLSA provenance pour décrire “qui a buildé quoi, quand, avec quels inputs” (in-toto, SLSA). Oui, ça fait des métadonnées. Non, ce n’est pas du luxe.

Ajoutez un SBOM (Software Bill of Materials) systématique, au format SPDX ou CycloneDX, stocké avec l’artefact et vérifié en gate de déploiement (SPDX, CycloneDX). C’est ce qui transforme une CVE critique en tâche priorisée au lieu d’un jeu de piste.

Mini-scenario : vous déployez myapp:1.8.2. Un attaquant a réussi à injecter un binaire malveillant pendant le build (runner compromis ou dépendance piégée). Sans signature/provenance, vous n’avez qu’une “étiquette”. Avec une politique “déployer uniquement des images signées par la CI et avec provenance attendue”, vous avez un contrôle objectif : l’image est vérifiée ou elle ne passe pas.

Concrètement, une chaîne “propre” ressemble souvent à :

  • build dans un environnement éphémère,
  • génération SBOM + attestation de provenance,
  • signature de l’image/artefact,
  • push vers une registry avec rétention et audit,
  • promotion par digest (pas par tag),
  • vérification côté déploiement (admission control / policy).

Et si votre pipeline cible Kubernetes, vous pouvez chaîner ça avec des contrôles admission (Gatekeeper/Kyverno) : “pas de signature cosign valide → pas de déploiement”. Pour la mécanique CI/CD côté K8s (GitHub Actions + GitOps), l’article interne Pipeline CI/CD Kubernetes : GitHub Actions et Argo CD étape par étape aide à positionner proprement build, registry et déploiement — reste à y ajouter l’armure.

Opérer la sécurité CI/CD : logs, métriques, alertes… et le jour où ça brûle

La sécurité CI/CD “papier” est rassurante jusqu’au premier token exfiltré. Il faut donc observer votre chaîne : événements SCM (push, création de PAT, changements de règles), exécution de workflows, enregistrements de runners, accès aux artefacts, et déploiements. Centralisez ces logs dans votre SIEM, et exposez des métriques actionnables : taux de builds signés, % d’artefacts avec SBOM, temps médian de rotation de secrets après alerte, nombre de runners non conformes, etc. Sans métriques, vous “pensez” être secure — ce qui est une posture philosophique, pas une posture sécurité.

Exemples de signaux “haut rendement” (peu de bruit, beaucoup de valeur) :

  • modification d’un fichier de pipeline (workflows CI, scripts de release) en dehors d’une PR review attendue ;
  • changement de permissions: dans les workflows (surtout si ça ajoute des droits d’écriture) ;
  • nouveau runner enregistré, ou runner qui change de configuration/labels ;
  • hausse soudaine des échecs de vérification (signature, provenance, SBOM manquant) ;
  • trafic sortant anormal depuis les runners (nouveaux domaines, volume inhabituel).

Pour la partie observabilité, vous avez déjà les briques : métriques (Prometheus), logs/traces (OpenTelemetry), alerting (Alertmanager). Ce n’est pas “que pour la perf”. Instrumentez vos runners et services CI (CPU, réseau sortant, anomalies DNS), et alertez sur les patterns suspects : exécution de jobs hors heures, augmentation brutale des téléchargements d’artefacts, modifications de permissions: dans les workflows, ou nouveaux endpoints sortants. Les bases sont bien détaillées dans Prometheus : architecture monitoring, PromQL, Alertmanager et Grafana et OpenTelemetry : unifier métriques, traces et logs pour l’observabilité.

Et puis il y a le moment où, malgré tout, quelqu’un a pushé un secret dans un repo public, ou un runner a été compromis. Là, la différence se fait sur l’exécution : runbooks, break-glass accounts, révocation immédiate, rotation orchestrée, et audit post-mortem. Le NIST formalise ce socle de pratiques dans le Secure Software Development Framework (SSDF) (NIST SP 800-218) : c’est utile pour structurer “qui fait quoi” (dev, ops, sécu) et éviter le pilotage à l’instinct.

Un minimum “jour où ça brûle” à préparer à froid :

  • une procédure de gel des déploiements (sans bloquer la prod indéfiniment) ;
  • un moyen rapide de révoquer en masse (tokens CI, clés cloud, accès registry) ;
  • une capacité à reconstruire des runners et des environnements de build propres ;
  • une stratégie de rebuild des artefacts “depuis la source” (et pas “on redéploie le dernier tag”).

Si vous voulez tester votre exposition réelle (dépôts, runners, secrets, artefacts) avec une approche orientée risques, vous avez les points d’entrée opérationnels : Audit Cybersécurité pour structurer la roadmap, et Urgence cybersécurité si votre pipeline vient de transformer “CI” en “Crimeware Injection”.

Kévin DECQ-CAILLET, Directeur associé

Co-fondateur du studio de développement Les Vikings, mon cœur est voué aux paradoxes. Amour de la technologie et de l'Histoire, passion pour la gestion, le potager et le béhourd - si vous ne connaissez pas, ça vaut le détour. Accessoirement, une expérience de plus de 15 ans dans le domaine du numérique. Ce qui implique que j'en sais assez pour constater que j'ai encore beaucoup à apprendre.

Résumé de la politique de confidentialité

Ce site utilise des cookies afin que nous puissions vous fournir la meilleure expérience utilisateur possible. Les informations sur les cookies sont stockées dans votre navigateur et remplissent des fonctions telles que vous reconnaître lorsque vous revenez sur notre site Web et aider notre équipe à comprendre les sections du site que vous trouvez les plus intéressantes et utiles.