Aller au contenu principal
Retour au blog

Composer 2.10 : config.policy, malware bloqué et audit en CI

Composer 2.10 bloque les malwares par défaut et fait échouer composer audit en code 1. Migrez config.audit vers config.policy avant que la CI ne casse.

Flavien Métivier · 9 min de lecture
Sommaire · 7 parties

Votre pipeline est vert depuis deux ans, et une simple mise à jour de l’image Composer peut le faire passer au rouge sans qu’une ligne de votre code ait changé. Depuis Composer 2.10 (28 mai 2026), les versions malveillantes sont bloquées par défaut, composer audit sort en code 1 dès qu’il échoue, et config.audit cède la place à config.policy. Voici ce qui bouge dans vos composer.json et vos jobs CI, et comment migrer sans surprise.

Tout ce qui suit a été vérifié à l’exécution sur l’image Docker composer:2.10, soit Composer 2.10.3 (27 août 2026) avec PHP 8.5.11. Quand la documentation, le billet de lancement et le comportement réel divergent, nous le signalons.

Ce que Composer 2.9 puis 2.10 changent vraiment

Pendant dix ans, la sécurité des dépendances PHP a reposé sur deux béquilles : le méta-paquet roave/security-advisories, qui déclare des conflict avec les versions vulnérables, et un composer audit lancé « quand on y pense ». Composer 2.9 (14 novembre 2025) a renversé la logique : le blocage des mises à jour vers une version qui porte un advisory — une alerte de sécurité publiée sur un paquet — est activé par défaut. Le billet officiel de Packagist le présente comme un remplacement complet de roave/security-advisories.

Composer 2.10.0 (28 mai 2026) généralise le principe : un nouveau bloc config.policy pilote advisories, malwares et paquets abandonnés, et les versions signalées comme malveillantes sont bloquées sans aucune configuration. Le contexte tient en une phrase : en 2026, plusieurs attaques sur Packagist sont passées par des tokens GitHub volés et des tags réécrits, avec du code exécuté à l’installation. Packagist importe depuis mars 2026 un flux de détection fourni par Aikido, que Composer 2.10 exploite par défaut. Le détail des incidents fera l’objet d’un article dédié.

La bonne cible n’est pas 2.10.0 mais 2.10.3. Les patchs suivants ont corrigé le blocage des advisories quand Xdebug est actif, l’écriture du résultat d’audit sur stdout, et plusieurs failles de Composer lui-même : traversée de chemin sur les binaires en 2.10.2, symlink sur bin en 2.10.3.

config.policy : trois politiques, trois comportements

config.policy est le bloc de composer.json, introduit en Composer 2.10, qui règle pour les malwares, les advisories et les paquets abandonnés ce qui est bloqué à la résolution, ce qui fait échouer l’audit et ce qui est ignoré. Chaque politique accepte block, audit (ignore, report ou fail) et des exceptions. Les défauts, d’après la documentation de configuration :

  • malware : bloqué, avec block-scope: all par défaut — donc aussi à l’install. C’est la seule politique qui bloque même depuis un composer.lock existant : un lock commité ne vous protège pas d’une version malveillante, il vous empêche de l’installer.
  • advisories : bloqué à l’update, au require et au remove, et l’audit échoue. Une version affectée reste installable depuis un lock existant : vos déploiements en cours passent, vos prochaines mises à jour non.
  • abandoned : jamais bloqué, mais l’audit échoue par défaut. Le billet de lancement parle de paquets « seulement signalés » ; la documentation et nos tests disent l’inverse (voir plus bas).

Voici une configuration explicite, validée avec composer validate sur Composer 2.10.3. Rendre la politique explicite plutôt que dépendre des défauts a deux avantages : elle se relit en revue de code, et elle ne bouge pas quand Composer change d’avis.

json
{
    "config": {
        "policy": {
            "advisories": {
                "block": true,
                "audit": "fail",
                "ignore-id": {
                    "PKSA-xxxx-xxxx-xxxx": "Faux positif : fonction non utilisée, revue le 2026-10-15"
                }
            },
            "malware": {
                "block-scope": "all"
            },
            "abandoned": {
                "block": false,
                "audit": "report",
                "ignore": ["acme/legacy-lib"]
            }
        }
    }
}

ignore-id accepte une liste ou une map identifiant → raison. Prenez la map : la justification vit dans le dépôt, passe en revue, et une exception sans date de revue redevient visible. En revanche, la CLI ne sait pas l’écrire : composer config policy.abandoned.audit report fonctionne, mais composer config policy.advisories.ignore-id.PKSA-1 "raison" est refusé. Éditez le JSON à la main. Enfin, "policy": false coupe tout : c’est un interrupteur d’urgence, pas un réglage.

Pourquoi composer audit casse votre CI

Le changelog de la 2.10.0 l’annonce comme un « minor break » : le code de sortie d’audit vaut désormais toujours 0 ou 1. Autre correctif voisin : audit renvoyait un succès quand vendor/ était absent. Il échoue maintenant — testé : sans vendor, « No installed packages found » et code 1. Un job qui faisait composer audit || true, ou qui ne lisait que certains bits du code de sortie des anciennes versions, change de comportement.

Le piège le plus probable sur un projet Symfony ancien concerne les paquets abandonnés. Testé avec swiftmailer/swiftmailer et aucune configuration : composer update affiche un simple avertissement, mais composer audit --locked retourne 1. Avec --abandoned=report ou policy.abandoned.audit à report, il retourne 0. Le défaut fail existe d’ailleurs depuis Composer 2.7 : c’est souvent le premier audit sérieux qui le révèle.

La réponse n’est pas de masquer le rouge, mais de séparer deux natures de problème : une étape bloquante pour les advisories et les malwares, une étape « dette » pour les abandonnés, en report, avec un ticket de remplacement — swiftmailer vers symfony/mailer est exactement ce cas.

yaml
# .github/workflows/ci.yml : étape sécurité des dépendances
- name: Audit des dépendances (bloquant)
  run: |
    composer install --no-interaction --prefer-dist --no-progress
    composer audit --locked --abandoned=report --format=summary

- name: Paquets abandonnés (dette suivie)
  run: composer audit --locked --abandoned=fail --format=plain
  # job non requis par la protection de branche, jamais continue-on-error

Deux options de contournement existent, confirmées dans composer help update : --no-blocking et la variable COMPOSER_NO_BLOCKING=1 désactivent tout le blocage de politique pendant la commande ; l’ancien --no-security-blocking est déprécié. Réservez-les à un déblocage d’urgence tracé. Dans nos tests, COMPOSER_NO_BLOCKING n’a d’ailleurs pas changé le code de sortie d’audit : ce n’est pas un moyen de faire passer un audit.

La fin du repli silencieux vers les sources

Qui s’occupe de vos mises à jour ?

Maintenance des versions PHP, framework et dépendances, à partir de 500 €/mois HT.

Voir la maintenance →

Avant la 2.10, si le téléchargement de l’archive (dist) échouait, Composer retombait sans bruit sur un git clone du dépôt — y compris depuis un tag qui pouvait avoir été réécrit, alors que l’archive de confiance existait. Depuis la 2.10.0, un échec de dist est une erreur franche. Un miroir d’artefacts instable ou un quota GitHub qui masquait des échecs fera désormais tomber le job : c’est voulu.

bash
# Restaure l’ancien comportement : temporaire uniquement
composer config source-fallback true
# Composer 2.10.3 avertit :
# "The source-fallback option is deprecated and will be removed in Composer 2.11 ..."

Testé : l’option est acceptée et affiche cet avertissement. Corrigez plutôt l’accès aux archives. Les installations depuis les sources demandées explicitement (--prefer-source, preferred-install) restent possibles.

Migrer config.audit vers config.policy

Les clés config.audit.* fonctionnent encore en 2.10 et resteront supportées jusqu’à une future majeure ; l’avertissement de dépréciation est annoncé pour la 2.11 — nous n’en avons vu aucun en 2.10.3. Une règle de la documentation change tout : dès que policy.advisories est défini, les clés audit.block-insecure, audit.ignore et audit.ignore-severity sont ignorées, sans message. Même logique entre policy.abandoned et les clés audit.*abandoned. Migrez donc une politique entière à la fois, jamais clé par clé : vous risqueriez de perdre des exceptions sans le savoir.

  • audit.block-insecure → policy.advisories.block
  • audit.ignore → policy.advisories.ignore-id (identifiants) ou policy.advisories.ignore (noms de paquets, contraintes)
  • audit.ignore-severity → policy.advisories.ignore-severity
  • audit.abandoned → policy.abandoned.audit
  • audit.block-abandoned → policy.abandoned.block
  • audit.ignore-abandoned → policy.abandoned.ignore
  • audit.ignore-unreachable → policy.ignore-unreachable
json
{
    "config": {
        "audit": {
            "block-insecure": true,
            "abandoned": "report",
            "ignore": ["PKSA-xxxx-xxxx-xxxx"],
            "ignore-severity": ["low"]
        }
    }
}

Et la même configuration, exprimée en config.policy pour Composer 2.10 :

json
{
    "config": {
        "policy": {
            "advisories": {
                "block": true,
                "ignore-id": ["PKSA-xxxx-xxxx-xxxx"],
                "ignore-severity": ["low"]
            },
            "abandoned": {
                "audit": "report"
            }
        }
    }
}

Dernier ménage : retirez roave/security-advisories de require-dev, mais seulement quand tous vos environnements — postes, CI, images de prod — tournent en Composer 2.9 ou plus. Un COPY --from=composer:2.10 /usr/bin/composer /usr/bin/composer dans le Dockerfile règle la question. Pour inspecter l’état de vos dépendances avant de toucher à la politique, ces 5 commandes Composer méconnues font gagner du temps.

Le cooldown : documenté, mais sans effet en 2.10.3

La page de configuration décrit un policy.cooldown : un délai minimal, par exemple "7 days", avant d’accepter une version fraîchement publiée — la meilleure parade contre un tag empoisonné détecté en quelques heures. Mais ni le changelog 2.10.x ni le billet de lancement n’en parlent, et Packagist le disait « planifié » en mai. Nous l’avons testé : une période de 3 650 jours passe composer validate, puis composer update installe quand même la dernière version de monolog. La clé est tolérée, mais inactive en 2.10.3. La documentation en ligne suit probablement la branche de développement. N’inscrivez pas le cooldown dans votre plan de sécurité tant qu’une release ne l’annonce pas.

En attendant, les protections qui fonctionnent sont connues : composer.lock commité et composer install en CI, config.allow-plugins strict (le vecteur commun des incidents de 2026 est le code exécuté à l’installation), revue des diffs de lock en PR, et --no-scripts dans les jobs qui n’en ont pas besoin. Les mêmes risques d’exécution à l’installation se retrouvent côté outillage IA, comme le détaille notre article sur la sécurité des agents de code et la supply chain.

Checklist de migration en six étapes

  • Épingler l’image CI et Docker sur composer:2.10 (2.10.3), plutôt que latest.
  • Auditer en local avec composer audit --locked --format=summary, noter le code de sortie, et décider pour chaque paquet abandonné : remplacer ou ignorer avec justification.
  • Convertir config.audit en config.policy, une politique entière à la fois.
  • Documenter les exceptions sous forme de map identifiant → raison, avec une date de revue.
  • Retirer les || true et séparer l’étape bloquante (advisories, malware) de l’étape dette (abandonnés).
  • Traquer le repli source : si le build casse, corriger l’accès aux archives ; source-fallback en dernier recours seulement. Puis supprimer roave/security-advisories.

Le fil conducteur de cette version est simple : les garde-fous passent de l’option au défaut. Le coût de migration ne vient pas des nouveautés, mais de la dette qu’elles rendent visible — un audit qui échoue sur un paquet abandonné depuis trois ans, une exception que personne ne sait plus justifier. Côté qualité de code, le même mouvement est à l’œuvre : voyez comment Mago complète PHPStan sur un projet Symfony.

Ce durcissement de Composer ne sera pas le dernier : chaque mise à jour d’outil, chaque advisory publié sur une dépendance ancienne finit par ressortir dans votre pipeline. L’enjeu est que quelqu’un tienne cette liste à jour en continu, plutôt que de la découvrir un vendredi soir avec une CI rouge. C’est le rôle de la maintenance Bear Upgrade : versions de PHP, du framework et des dépendances suivies pour vous, livrées en Merge Requests testées.

Cet article vous a été utile ? Partagez-le.

Portrait de Flavien Métivier, fondateur de The Bearded Bear : barbe longue, lunettes, sur fond sombre

Écrit par

Flavien Métivier

Expert PHP/Symfony indépendant depuis 2014, ancien CTO de Kartable et d'Extrabat. Plus de 80 projets menés, de l'audit à la migration.

Mon parcours

À lire ensuite

Qui s’occupe de vos mises à jour ?

Maintenance des versions PHP, framework et dépendances, à partir de 500 €/mois HT.

Newsletter Bear Bytes

Veille Claude Code, Symfony et PHP, retours d'expérience terrain et conseils techniques, directement dans votre boîte mail.