Si tu utilises Composer en CI, ton pipeline exécute déjà des commandes. Pas « peut-être ». Déjà. Entre les drivers VCS (Git, SVN… et Perforce), les plugins et les scripts déclarés dans composer.json, ton job d’install est une surface d’exécution à part entière.
La release Composer 2.9.6 corrige justement des vulnérabilités de command injection liées au driver Perforce (deux CVE). Même si tu n’as jamais touché à Perforce, retiens surtout la leçon : « faire un composer install » dans un runner qui build du code venant d’ailleurs, c’est un vrai sujet supply chain. Et c’est souvent là que les équipes se racontent des histoires.
Composer + VCS en CI : pourquoi une injection est plausible
Composer ne se contente pas de télécharger des archives depuis Packagist. Il sait aussi récupérer des dépendances directement depuis des dépôts VCS. Ça arrive quand tu déclares un dépôt dans la section repositories (repo privé, fork, package interne), ou quand tu tires une dépendance qui n’est pas distribuée « en dist » et doit être clonée.
Dans ce mode-là, Composer devient un orchestrateur. Il appelle des binaires externes (git, svn, hg… et p4 pour Perforce), passe des arguments, prépare des répertoires temporaires, et manipule des URLs, des branches, des tags, parfois des identifiants. Si une partie de ces données est malléable (ou juste mal échappée), tu n’es plus dans « je télécharge des zip ». Tu es dans « j’exécute des commandes ».
Et le CI rend le truc plus sensible : tu as souvent un runner avec des secrets (tokens, clés SSH, accès réseau), des caches (vendor, cache Composer), et une confiance implicite dans ce qui déclenche le pipeline (PR externes, branches venant de forks, jobs déclenchés par commentaires, etc.). Autrement dit, un endroit parfait pour transformer une petite faille en gros incident.
Sponsorisé par Le Scribouillard
Besoin de contenu optimisé SEO ?
Utilisez la meilleure plateforme française de création de contenu assistée par IA ! Et générez des articles pour moins de 1€ !
La CVE « Perforce » : qui est vraiment concerné
Concrètement, cette correction vise le driver Perforce dans Composer. Donc le premier cas évident, c’est : tu as des dépendances récupérées via Perforce, ou tu as configuré des repos Perforce dans composer.json (ou dans un composer.json d’un composant interne).
Le deuxième cas est plus sournois, et c’est celui que je vois en audit : des runners « généralistes » avec plein d’outils installés « au cas où » (dont p4), et une CI qui build des branches pas totalement maîtrisées. Si un vecteur permet de forcer Composer à emprunter un driver VCS, tu veux que ce driver soit sain, même si tu ne l’utilises pas « normalement ».
Et enfin, il y a le cas des environnements où Composer peut être influencé via la configuration globale (config home du runner, variables d’env, caches partagés). Quand tu mutualises trop, tu crées des chemins bizarres. Sur le papier « c’est la même image pour tout le monde », en vrai c’est souvent un mélange de couches et d’héritages.
Audit express : savoir si ton repo ou ta CI touche Perforce (ou un VCS non maîtrisé)
Je te conseille de faire un audit bête et méchant, en partant du principe que tu as oublié des trucs. Cherche d’abord Perforce dans tes composer.json (racine et packages internes), puis cherche les dépôts VCS déclarés à la main. Le but n’est pas de produire un rapport, juste de répondre vite à « est-ce que Composer peut appeler autre chose que git sur ce projet ? ».
# Dans le repo
rg -n "perforce|\"type\"\s*:\s*\"perforce\"|\"repositories\"|p4" composer.json **/composer.json
# Dans tes workflows / scripts CI
rg -n "composer (install|update)|COMPOSER_|--no-scripts|--no-plugins|prefer-dist|--no-dev" .github/ .gitlab-ci.yml .circleci/ JenkinsfileEnsuite, regarde comment ton CI est déclenché. Le point clé n’est pas « on fait un install ». Le point clé c’est « on fait un install sur du code qui peut contenir un composer.json modifié par quelqu’un que je ne contrôle pas totalement ». Typiquement, les PR venant de forks, ou des jobs qui tournent avant review. Si ça te concerne, tu dois considérer le composer.json comme une entrée potentiellement hostile, au même titre qu’un fichier de config exécuté.
Dernier check simple : est-ce que ton pipeline fait des composer update en CI ? Si oui, c’est rarement une bonne idée en dehors de jobs dédiés, parce que tu agrandis la surface (résolution dynamique + accès réseau + exécution de scripts). Un install sur un lockfile audité, c’est déjà assez sport.
Correction immédiate : passer en Composer 2.9.6 (et arrêter les upgrades invisibles)
La première action, c’est juste de mettre Composer à jour en 2.9.6. Pas parce que « il faut toujours update », mais parce que ce patch ferme un vrai bug de sécurité dans un composant qui exécute des commandes. Ça fait partie des rares upgrades où je ne débat pas longtemps.
La deuxième action est plus importante que beaucoup de gens ne le pensent : arrêter les upgrades invisibles. Si ton CI installe « le dernier Composer » à chaque run, tu n’as pas de reproductibilité, et tu n’as aucun contrôle sur quand un changement de comportement débarque. La bonne hygiène, c’est de pinner la version de Composer dans l’image Docker du job, ou dans l’action/setup utilisé par le runner.
Et oui, ça veut dire qu’il faut assumer un petit rituel de maintenance. Mais tu préfères quoi : faire un PR trimestriel « bump Composer », ou découvrir un matin que tes builds ont changé de comportement parce que la version a bougé dans ton outil d’installation ?
Durcir ton usage de Composer en CI : ce qui paye vite
Si ton job CI ne fait que builder et tester, tu peux souvent réduire l’exécution de choses « actives ». Le duo le plus simple, c’est limiter les scripts et limiter les plugins. Les scripts, parce qu’ils sont littéralement faits pour exécuter des commandes. Les plugins, parce qu’ils peuvent modifier le comportement de Composer. C’est normal en local, c’est plus discutable sur des runners qui construisent du code non fiable.
Dans la vraie vie, tu ne pourras pas toujours tout couper. Certaines stacks ont besoin d’un script post-install (framework, assets, discovery…). L’approche pragmatique, c’est d’être explicite. Tu autorises ce qui est nécessaire, tu bloques le reste, et tu sais pourquoi c’est là. Composer te donne de quoi contrôler l’autorisation des plugins et l’exécution des scripts. Utilise-le au lieu de prier.
Autre durcissement souvent oublié : préférer les distributions « dist » quand c’est possible, plutôt que des checkouts VCS, et éviter de multiplier des repos VCS custom. Plus tu tires du code via VCS, plus tu dépends d’outils externes et de logique de transport. Moins c’est varié, plus c’est stable.
# Exemple GitHub Actions : pin Composer et réduire les surprises côté install
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:2.9.6
- name: Install dependencies
run: |
composer --version
composer install --no-interaction --no-progress --prefer-distLe code ci-dessus ne « sécurise pas tout », mais il règle deux problèmes fréquents : tu sais quelle version tourne, et tu privilégies un mode d’installation qui évite de déclencher des récupérations VCS inutiles. Si ton cas te permet d’aller plus loin avec --no-scripts ou des restrictions de plugins, c’est encore mieux, mais je préfère éviter les conseils qui cassent la moitié des builds sans contexte.
Le cas Perforce en particulier : attention aux runners « qui ont p4 »
Perforce est un outil courant dans certains environnements (gaming, legacy, grosses boîtes). Le piège, c’est le runner « image universelle » qui a été gonflée au fil des années, et qui embarque des clients VCS que personne n’utilise sur ce projet… mais qui sont là. Quand tu as des binaires disponibles, tu augmentes mécaniquement la surface d’attaque d’outils qui orchestrent des commandes.
Le move simple : si tu n’as aucune raison d’avoir p4 dans l’image, enlève-le. Ton but, ce n’est pas d’être « capable de tout ». Ton but, c’est d’être prévisible. Et si ton organisation a besoin de Perforce pour certains repos, alors isole ces builds dans des runners dédiés, avec des droits réseau et des secrets adaptés, pas dans le même pool que tes jobs web standard.
Si tu suspectes un incident : un runbook réaliste (pas un roman)
Si tu penses qu’un pipeline a pu exécuter quelque chose de sale via Composer, le bon réflexe est de raisonner « comme en prod ». D’abord, tu assumes que des secrets ont pu fuiter. Tu rotates ce qui était exposé au job (tokens Git, credentials de registry, clés SSH, accès à des API internes). Sans ça, tu peux passer deux jours à analyser… et te faire re-pwner par le même secret encore valide.
Ensuite, tu traites le runner et ses caches comme potentiellement contaminés. Les caches partagés (Composer cache, vendor/, caches de build) sont pratiques, mais ils peuvent aussi devenir un vecteur de persistance ou de confusion. Sur un runner auto-hébergé, je préfère largement un rebuild propre de l’image et un nettoyage des volumes que « on va juste relancer le job ». Sur un runner managé, tu vérifies les mécanismes de cache, les scopes, et tu purges ce qui doit l’être.
Enfin, tu vas aux logs utiles. Pas seulement le log « build ». Tu regardes les traces d’accès réseau pendant le job, les commandes exécutées, les téléchargements, et les modifications de fichiers dans l’espace de travail. Et tu prends le temps de vérifier le scénario d’entrée : qui a déclenché le pipeline, sur quel commit, avec quel composer.json et quel composer.lock. Très souvent, l’histoire est là.
Mon avis : la vraie leçon, ce n’est pas « update Composer », c’est « traite ton CI comme de la prod »
Oui, il faut passer en 2.9.6. Mais si tu t’arrêtes à ça, tu n’as rien appris. Le vrai sujet, c’est que beaucoup de CI tournent avec une confiance implicite énorme, des secrets trop larges, des caches trop partagés, et des builds qui exécutent des trucs « parce que ça a toujours été comme ça ».
Composer est juste un bon rappel, parce qu’il est partout en PHP. Ton composer install n’est pas un download, c’est un mini orchestrateur qui vit au cœur de ta supply chain. Si tu veux dormir tranquille, rends-le prévisible, pin tes outils, limite ce qui s’exécute, et isole ce qui doit l’être. C’est moins sexy qu’un nouveau framework, mais c’est là que tu gagnes des mois de tranquillité.