NIS2 50+ salariés ou €10M+ CA : reporting d’incident en 72h obligatoire. Reporting d’incident en 72h obligatoire. Êtes-vous prêt ? →
Skip to main contentSkip to footer

Sécuriser les runners CI/CD contre les supply chain attacks

Le message à retenir en une phrase : un self-hosted runner CI/CD équipé d’un EDR commercial (CrowdStrike Falcon ou équivalent) et corrélé par un Agentic SOC est strictement plus sûr qu’un runner hosted GitHub ou GitLab — précisément parce que les runners managés n’embarquent aucun EDR et restent par construction des boîtes noires pour le client. Cet article documente pourquoi cette inversion va à contre-courant de l’intuition habituelle, et comment la mettre en œuvre en quelques jours dans une PME-ETI.

Quand un dirigeant PME-ETI fait l’inventaire des serveurs « les plus critiques » de son SI, il pense d’abord à l’Active Directory, aux serveurs de bases de données, parfois à l’ESXi qui porte la prod. Le self-hosted runner qui exécute ses workflows GitHub Actions ou GitLab CI ne lui vient quasiment jamais à l’esprit — et c’est précisément l’angle mort que les attaquants exploitent. Ce serveur est en pratique le plus privilégié de l’organisation : il détient des tokens de publication npm, des PAT GitHub avec accès au monorepo, des clés AWS de déploiement, le kubeconfig de production, et il exécute du code venant de pull requests externes plusieurs fois par heure. Pourtant, dans 9 PME-ETI sur 10 que nous auditons, ce serveur n’a aucun EDR, aucun logging avancé, et tourne sur une VM partagée avec d’autres outils. Beaucoup d’organisations en concluent à tort que la sortie de secours est de revenir aux runners managés GitHub-hosted ou GitLab-hosted — c’est une fausse bonne idée, parce que ces runners n’ont pas d’EDR non plus et restent opaques en runtime. La vraie réponse est l’architecture documentée ici : runner self-hosted éphémère + sonde EDR CrowdStrike Falcon + corrélation Agentic SOC.

Pourquoi le self-hosted runner est l’endpoint le plus critique de votre SI

Un self-hosted runner n’est pas un agent CI « comme un autre ». Trois propriétés en font une cible privilégiée pour tout attaquant supply chain, et l’année 2026 l’a démontré à six reprises (TanStack, OpenSearch, Grafana, Mistral AI, @antv, GitHub interne) :

  • Concentration des secrets de niveau prod. Sur un runner GitHub Actions self-hosté qui pousse en production, on trouve typiquement : un token OIDC fédéré pour npm/PyPI/Docker Hub, des clés AWS / GCP / Azure d’un service-principal avec droits de déploiement, le kubeconfig vers le cluster de prod, des secrets Vault montés en mémoire, et plusieurs deploy keys SSH. Compromettre le runner = compromettre la chaîne de livraison complète.
  • Exécution de code non vérifié. Tout workflow déclenché par un événement pull_request ou pull_request_target exécute, par construction, du code venant d’une branche externe. C’est le point d’entrée du fameux pattern Pwn Request exploité par TeamPCP lors de la compromission TanStack Mini Shai-Hulud.
  • Persistance triviale entre les jobs. Un runner non éphémère réutilise le même filesystem entre les jobs. Un attaquant qui a obtenu une exécution arbitraire pendant un job dépose un binaire dans /tmp ou un cron dans /etc/cron.d/ qui s’activera sur le job suivant — y compris celui qui pousse en prod sur la branche main.

Le runner managé par GitHub ou GitLab (les hosted runners) règlent deux des trois problèmes structurels par construction : VM éphémère détruite après chaque job et secrets retirés à la fin du run. C’est pour cela qu’ils restent l’option par défaut. Mais attention au sens du mot « isolation » : il s’agit d’isolation multi-tenants (aucun autre client GitHub ne peut atteindre votre VM) — pas d’isolation réseau sortante. Par défaut, un runner hosted a un accès Internet sortant complètement ouvert : il peut joindre n’importe quelle IP publique, n’importe quel domaine, y compris filev2.getsession.org ou n’importe quel C2 jetable sur GitHub. Si un payload type TeamPCP s’exécute dans un de vos jobs hosted, il exfiltrera vos secrets vers l’extérieur exactement comme il le ferait depuis n’importe quel autre serveur — et vous ne le verrez tout simplement pas, parce que les runners hosted n’embarquent aucun EDR commercial (ni CrowdStrike, ni SentinelOne, ni Microsoft Defender for Endpoint). La télémétrie disponible côté client se résume aux logs applicatifs du job que vous exécutez vous-même. C’est un compromis assumé par GitHub : maximiser la séparation entre tenants au prix d’une opacité totale sur le runtime côté client. Les options de filtrage egress existent (« larger runners with network filtering » sur GitHub Enterprise, allow-list IP côté GitLab) mais restent réservées aux plans Enterprise et ne sont pas activées par défaut, donc la quasi-totalité des PME-ETI sur des plans Team / Premium ne les ont pas.

Dès qu’une PME-ETI a besoin d’accès à un réseau interne, à un cluster Kubernetes on-premise, à un registry privé ou simplement à des performances supérieures (builds Rust, conteneurs lourds), elle bascule sur du self-hosted. Dans 9 cas sur 10, ce passage se fait sans réflexion sécurité : VM permanente, pas d’EDR, pas de politique réseau spécifique. L’organisation perd alors les deux propriétés que les runners managés offraient (éphémérité et retrait des secrets) sans pour autant gagner d’EDR ni de politique de prévention. C’est le pire des deux mondes — et c’est la posture par défaut dans la quasi-totalité des PME-ETI que nous auditons.

L’architecture cible : runner éphémère + Falcon + Agentic SOC

L’idée directrice est double : récupérer chez soi les propriétés que les runners managés offrent par défaut (éphémérité, isolation, retrait des secrets) et ajouter la couche EDR + corrélation comportementale que GitHub et GitLab ne fournissent pas dans leurs runners hosted. C’est cette combinaison qui rend le self-hosted strictement supérieur aux options managées, pas un simple rattrapage. Trois briques :

  • Brique 1 — runner éphémère et isolé. Chaque job tourne dans une VM (ou conteneur) créée à la volée, détruite à la fin, sans état partagé. Cela neutralise la persistance entre jobs et limite le rayon de compromission à un seul build.
  • Brique 2 — sonde EDR CrowdStrike Falcon installée dans l’image de base du runner. Le sensor Linux observe tous les processus, les accès fichiers (secrets, kubeconfig, AWS credentials), les flux réseau sortants, les écritures de binaires. Avec une politique de prévention adaptée CI/CD, le sensor bloque l’exfiltration vers les domaines connus de TeamPCP (Session P2P, raw paste sites) avant qu’elle ne se produise.
  • Brique 3 — ingestion des audit logs GitHub/GitLab dans le SIEM, et corrélation par l’Agentic SOC ucyber.ai. La télémétrie EDR par elle-même ne dit pas « ce job correspondait à la PR #4271 sur le repo ucyber-prod ouverte par un compte créé hier ». Cette mise en contexte est ce qui transforme cinq cents évènements bruts en un verdict actionnable : « job 8821 sur runner build-03 — accès anormal à ~/.aws/credentials à 14h33, exfiltration HTTPS bloquée vers raw.githubusercontent.com, PR auteur compte créé il y a 47 minutes — compromission confirmée, secrets en rotation ».

Procédure pas-à-pas : du provisioning à la corrélation

La séquence ci-dessous décrit une mise en œuvre minimale sur une stack Linux (Ubuntu 24.04 LTS), avec GitHub Actions comme exemple. Le même schéma s’applique à GitLab Runner (s/actions-runner/gitlab-runner/) et à Azure DevOps avec quelques ajustements de naming.

Étape 1 — Provisioning du runner éphémère

L’objectif est qu’aucun job ne réutilise le filesystem d’un job précédent. Deux approches praticables pour une PME-ETI : actions-runner-controller (ARC) sur un petit cluster Kubernetes interne, ou un autoscaler de VM avec Packer + Terraform. Le mode --ephemeral du binaire GitHub Actions Runner garantit qu’après l’exécution d’un seul job, l’agent se désinscrit et le superviseur (systemd, Kubernetes Job, ou cloud-init) doit recréer la VM ou le pod from scratch :

cd /home/runner/actions-runner
./config.sh --url https://github.com/<org> \
            --token <short-lived-registration-token> \
            --name "build-$(hostname)-$$" \
            --labels "ephemeral,linux,x64,prod" \
            --ephemeral \
            --unattended
./run.sh
# À la fin du job, le binaire exit ; le superviseur recrée la VM.

Bonnes pratiques côté image : utilisateur dédié non-root, montage noexec,nosuid,nodev sur /tmp et /var/tmp, kernel hardening (sysctl kernel.kptr_restrict=2, kernel.yama.ptrace_scope=2), AppArmor en mode enforce pour les processus de build, désactivation de l’historique bash pour le compte runner.

Étape 2 — Bootstrap CrowdStrike Falcon dans l’image de base

Le déploiement le plus reproductible utilise le dépôt falcon-scripts (officiel CrowdStrike) qui fournit un wrapper d’installation paramétrable. Le bootstrap doit se faire dans la construction de l’image (Packer) et non au démarrage du runner — sinon la fenêtre entre le boot et l’installation du sensor est exploitable :

# Dans le template Packer ou Dockerfile de base
curl -sSL https://raw.githubusercontent.com/CrowdStrike/falcon-scripts/main/bash/install/falcon-linux-install.sh \
  | sudo FALCON_CLIENT_ID="$FALCON_CID" \
         FALCON_CLIENT_SECRET="$FALCON_SECRET" \
         FALCON_CLOUD="eu-1" \
         bash

# Affecter une politique CI/CD spécifique via la console Falcon
sudo /opt/CrowdStrike/falconctl -g --aid
# → injecter cet Agent ID dans le bon Host Group dans la console

Côté politique de prévention, créez un groupe d’hôtes « CI Runners » distinct des serveurs métier et activez les protections suivantes en blocage (pas seulement détection) : Suspicious Process Blocking, Script-Based Execution Monitoring, Cloud Anti-Malware niveau agressif, Credential Dumping Prevention, et Sensor Anti-Tampering. Activez aussi Custom IOA avec une règle qui détecte la lecture séquentielle de plusieurs fichiers de secrets (~/.aws/credentials, ~/.config/gh/hosts.yml, ~/.kube/config, ~/.npmrc) par un même processus enfant d’un binaire de build — c’est la signature comportementale d’un payload TeamPCP.

Étape 3 — Ingestion des audit logs GitHub/GitLab dans le SIEM

La sonde EDR ne voit que le runner. Pour corréler l’évènement avec sa source (qui a déclenché le job, sur quel repo, depuis quelle PR), il faut ingérer les audit logs et webhooks GitHub. Deux flux complémentaires :

  • Audit log organisation via l’API GET /orgs/{org}/audit-log, pollé toutes les 2 minutes par un script Python ou Logstash, et envoyé vers le NG-SIEM (CrowdStrike Falcon LogScale ou équivalent). Ce log contient les évènements workflows.*, repo.*, org.*, y compris les changements de secrets, ajouts de collaborateurs et exécutions de workflow.
  • Webhook workflow_run côté repo, qui pousse en temps réel chaque démarrage et fin de job, avec le nom du runner, l’acteur déclencheur, et le hash du commit exécuté. Cela permet de joindre un évènement EDR Falcon (par exemple « processus suspect sur build-03 à 14h33 ») à un job CI précis (« PR #4271 sur ucyber-prod par compte créé il y a 47 minutes »).

Côté Agentic SOC ucyber.ai, ces deux flux sont automatiquement joints sur la clé (runner_hostname, timestamp). Quand le sensor Falcon signale un comportement anormal sur build-03, l’incident contextualisé indique en clair : repo ciblé, PR source, auteur de la PR, secrets potentiellement exposés, statut d’exfiltration. Un dirigeant non technique reçoit le verdict en français et peut décider sans avoir à lire la console Falcon.

Étape 4 — Durcissement réseau et exfiltration outbound

Compléter les briques précédentes par une politique réseau stricte sur le réseau du self-hosted runner :

  • Sortie HTTPS limitée à une liste explicite : *.github.com, *.githubusercontent.com, *.ghcr.io, *.docker.io, le registry npm de l’organisation, les endpoints AWS/GCP/Azure de la région cible — et rien d’autre. Tout le reste passe en deny by default.
  • Blocage DNS de *.getsession.org, des paste sites publics, et de tout TLD inattendu (.ru, .cn, .io hors allow-list).
  • Pas d’accès au metadata service cloud (169.254.169.254) depuis l’intérieur du job : utiliser IMDSv2 obligatoire au niveau infra, et idéalement bloquer cet endpoint au pare-feu du container puisque le job ne devrait jamais avoir besoin d’identifier la VM hôte.

La seule architecture qui combine éphémérité ET visibilité EDR

La triple comparaison ci-dessous résume pourquoi cette approche est strictement supérieure aux deux options par défaut. Sur six critères de sécurité, seule l’architecture self-hosted + Falcon + Agentic SOC coche toutes les cases.

  • Runners GitHub / GitLab hosted : éphémérité ✅, isolation multi-tenants ✅, egress réseau filtré ❌ (sortie Internet ouverte par défaut), EDR commercial ❌, politique de prévention sur mesure ❌, investigation post-incident ❌. Vous ne verrez pas un payload TeamPCP s’exécuter pendant un de vos jobs, il exfiltrera sans frein vers son C2, et la VM sera détruite avant que vous puissiez investiguer.
  • Self-hosted runner par défaut (VM permanente, pas d’EDR) : éphémérité ❌, isolation multi-tenants ❌ (souvent VM partagée avec d’autres outils), egress réseau filtré ❌, EDR commercial ❌, politique de prévention sur mesure ❌, investigation post-incident ❌. Zéro case cochée — c’est la configuration la plus dangereuse, et la plus répandue dans les PME-ETI.
  • Self-hosted runner + Falcon + Agentic SOC (architecture décrite ici) : éphémérité ✅, isolation multi-tenants ✅, egress réseau filtré ✅, EDR commercial ✅, politique de prévention sur mesure ✅, investigation post-incident ✅. Les six cases cochées.

Les trois axes différentiants à mettre en avant auprès d’un dirigeant PME-ETI :

  • Visibilité runtime que GitHub/GitLab ne fournissent pas. Les runners hosted n’exposent aucune télémétrie EDR — l’utilisateur voit uniquement les logs applicatifs du job. Avec Falcon dans l’image self-hosted, on récupère la trace complète des processus, des accès fichiers et des connexions sortantes. C’est la différence entre « un build s’est mal passé » et « le job a tenté d’exfiltrer le kubeconfig à 14h33, blocage par Falcon ».
  • Politique de prévention sur mesure. Les hosted runners ne permettent aucune politique de sécurité personnalisée — c’est un environnement managé fermé. Sur du self-hosted avec Falcon, la PME-ETI peut bloquer en prévention (pas seulement détecter) l’exfiltration vers les IoC TeamPCP, le credential dumping, le script obfusqué Node de plus d’un mégaoctet, et tout autre signal métier-spécifique.
  • Investigation post-incident. Sur un runner hosted, après un job suspect, la VM a déjà été détruite et il n’y a plus rien à analyser — vous dépendez des logs applicatifs que vous avez bien voulu écrire. Sur self-hosted éphémère + Falcon, la télémétrie sensor est conservée 30 à 90 jours dans la console et reste exploitable pour reconstituer la chaîne d’évènements même si la VM elle-même n’existe plus.

Pourquoi cette architecture n’est documentée nulle part en l’état

Le sujet « self-hosted runner + EDR commercial » se trouve à l’intersection de plusieurs piliers qu’aucun fournisseur n’a intérêt à joindre publiquement. CrowdStrike documente le déploiement Falcon sur Linux comme un cas d’usage générique Linux server — pour eux, un runner CI/CD est juste un endpoint parmi d’autres. GitHub documente la sécurisation des runners (réseau, éphémérité, isolation) sans recommander d’EDR spécifique pour rester neutre vis-à-vis du marché. Les analyses post-incident sur les attaques supply chain prouvent que Falcon détecte les comportements TeamPCP en production, mais s’arrêtent au constat. Personne n’écrit le join entre ces piliers — et c’est pourtant ce dont a besoin un DSI de PME-ETI qui veut une réponse complète en deux pages, pas quatre articles à recouper.

C’est précisément l’angle qu’ucyber.ai porte. Notre triple position — partenaire CrowdStrike, opérateur d’un SOC européen souverain, et utilisateur quotidien de runners self-hostés sur notre propre pipeline ucyber-agentic-soc — nous permet d’écrire la procédure complète avec retours d’expérience concrets, hors marketing pur. La couverture défensive obtenue est exactement celle des grandes ETI, sans recrutement d’équipe sécurité interne et avec un délai de déploiement de quelques jours.

Conclusion : pourquoi self-hosted + EDR bat les runners managés

La conclusion est volontairement contre-intuitive : pour une PME-ETI qui prend la sécurité supply chain au sérieux, le self-hosted runner équipé d’un EDR commercial et corrélé par un Agentic SOC est aujourd’hui le choix le plus sûr du marché, devant les runners managés GitHub-hosted et GitLab-hosted. Ces derniers offrent isolation multi-tenants et éphémérité mais aucun EDR, aucune politique de prévention sur mesure et aucun filtrage egress par défaut — vous ne saurez jamais ce qu’un payload TeamPCP a fait dans un de vos jobs hosted, il exfiltrera vers son C2 sans rencontrer le moindre obstacle, et la VM aura disparu avant que vous puissiez investiguer. L’architecture décrite ici corrige les quatre manques (EDR, politique, egress, investigation) sans sacrifier l’éphémérité ni l’isolation.

La leçon des six vagues TeamPCP de mai 2026 est claire : le prochain incident supply chain ne ciblera pas un nouveau registry, il ciblera un nouveau runner mal protégé. Un runner self-hosted qui exécute des PR externes sans EDR, sans politique de prévention, sans corrélation avec les audit logs, est un cadeau pour TeamPCP et ses copies à venir — et un runner hosted GitHub ou GitLab n’apporte aucune solution à ce risque, juste une opacité de plus. À l’inverse, l’architecture self-hosted + Falcon + Agentic SOC + politique réseau stricte neutralise les six modes opératoires observés à ce jour et reste robuste aux variantes futures parce qu’elle s’appuie sur la corrélation comportementale, pas sur des signatures statiques. Le coût opérationnel est mesurable (quelques jours d’ingénierie pour le bootstrap, une dizaine d’euros par poste-mois pour le sensor) — sans commune mesure avec le coût d’un monorepo applicatif exfiltré ou d’un cluster prod compromis.

Sources

Renforcez dès maintenant la cybersécurité de votre PME ou ETI avec ucyber.ai.
Évaluez votre niveau de sécurité ou
contactez-nous pour en savoir plus.
Suivez-nous sur LinkedIn.

Réserver 15 min — diagnostic