La pull request ressemblait à une banale montée de version de dépendance. Enfoui dans le diff, un petit changement dans la configuration CI aurait ajouté une étape supplémentaire à chaque build. Le mainteneur qui a raconté cette histoire sur Medium faisait tourner ses jobs sur un runner auto-hébergé, un VPS pas cher qu'il gardait parce que les minutes hébergées finissent par coûter, et cette machine acceptait des jobs venant de forks tout en stockant des identifiants cloud dans un fichier de config. Il a lu le diff correctement, fermé la PR, et rien ne s'est passé. Mon argument aujourd'hui, c'est que nous devrions tous faire comme si cette même PR attendait déjà dans notre file de review.
Si tu maintiens un paquet PHP public, réfléchis à ce que fait ton pipeline quand un inconnu ouvre une PR. composer install peut exécuter des plugins et des scripts avant même le premier test. Ta matrice couvre sans doute plusieurs versions de PHP sur un runner qui met les dépendances en cache entre les jobs. Déclencher la CI depuis un fork, c'est exécuter du code que tu n'as jamais lu sur du matériel dont tu es responsable. C'est un beau service à offrir aux contributeurs. Ça devient dangereux dès que la machine peut atteindre quelque chose qui compte pour toi, et un fichier d'identifiants laissé là par ton toi du passé compte clairement.
Le réflexe, c'est d'envelopper le runner dans un conteneur, et je ne vais pas prétendre que ça ne sert à rien. Mais un conteneur emprunte le noyau de l'hôte, la plupart des setups CI y montent la socket Docker pour que les builds puissent construire des images, et le répertoire de cache qui rend ton pipeline rapide est accessible en écriture à quiconque a exécuté le job précédent. Un build malveillant peut laisser un cadeau au suivant, et redémarrer le conteneur entre les deux ne le fera pas disparaître. Une isolation qui partage de l'état entre les jobs, c'est une isolation avec un astérisque.
La réponse de l'auteur, c'est une microVM par job : chaque build reçoit une VM Firecracker toute neuve, la même couche d'isolation sur laquelle AWS a construit Lambda, qui démarre en moins d'une seconde, détruite dès la fin du build, vivant sur un réseau privé sans aucun port exposé. Il fait tourner tout ça via un service nommé Krova Cloud, et oui, le billet se lit en partie comme une pub pour lui. Enlève le branding et la propriété tient toujours : quand la durée de vie entière d'une machine se résume à un seul job, la persistance cesse d'être quelque chose qu'un attaquant peut obtenir. Un cache trafiqué meurt avec la machine, tout comme ce qu'un build a écrit sur le disque en espérant qu'un job futur trébuche dessus.
Avant de partir faire tes courses en hébergement de microVM, cela dit, le contre-argument ennuyeux mérite sa place. Les runners hébergés de GitHub sont déjà éphémères, et pour la librairie PHP médiane, les minutes gratuites couvrent toute la matrice de tests. Tu peux aussi exiger une approbation manuelle avant que les workflows des contributeurs externes ne s'exécutent. Si tu auto-héberges parce que tes tests d'intégration réclament une grosse machine, très bien. Si tu auto-héberges uniquement pour économiser des minutes sur un dépôt public, le changement le plus sûr se trouve dans une page de réglages et prend cinq minutes. Une éphémérité que tu n'as pas à construire toi-même vaut très cher.
Quoi que tu utilises pour exécuter tes jobs, la partie que j'épinglerais au wiki de l'équipe concerne les identifiants, et là l'auteur est d'une honnêteté rafraîchissante sur les limites de son propre setup : une VM immaculée ne protège en rien une clé cloud longue durée que tu injectes dans chaque build. L'exfiltration se fait juste depuis du matériel plus élégant. Génère des tokens par job via la fédération OIDC, restreins-les au seul bucket ou registre que le job touche vraiment, et laisse-les expirer en quelques minutes. Si ton job de test peut encore lister toutes les ressources du compte, ton architecture de runner sophistiquée n'est que de la décoration.
Ce qui me reste, c'est à quel point l'attaque était ordinaire. Pas de zero-day, pas d'évasion du noyau. Un bout de diff dans un fichier de workflow et un mainteneur en pleine review un mardi soir, c'était tout le plan, et il a failli marcher. La contre-mesure est tout aussi ordinaire : passe en revue tout changement sous .github/workflows avec la même méfiance que tu réserverais à une modification de ton middleware d'authentification, et considère par défaut la machine qui exécute les PR de forks comme un terrain hostile. Alors dis-moi comment tu gères ça en pratique. Est-ce que tu auto-héberges des runners pour un dépôt public, et si oui, qu'est-ce qui sépare concrètement un job de fork de ton environnement ? J'adorerais entendre parler des setups qui ont survécu au contact d'une PR d'inconnu, et tout autant de ceux qui n'ont pas tenu.




Commentaires
Pas encore de commentaire — écris le premier.
Lance la discussion
Pas de compte ni de mot de passe — saisis simplement ton adresse e-mail et nous t’envoyons un lien de connexion à usage unique. Première visite ? Tout se met en place automatiquement.
Ton évaluation sera appliquée automatiquement après ta connexion.
Vérifie ta boîte mail
Nous avons envoyé un lien de connexion à …. Ouvre-le sur cet appareil — cet onglet te connectera automatiquement.
Rien reçu ? Vérifiez le dossier spam — et marquez le message « Non spam » pour qu'il arrive directement la prochaine fois.