TECHNOLOGIES / Psalm
Psalm : analyse de teinte du code PHP
Psalm suit une entrée utilisateur jusqu'à la requête SQL, la sortie HTML ou la commande système qu'elle atteint, et ne signale que les chemins prouvés. C'est le contrôle qu'un type-checker ne fait pas et nous l'utilisons en complément de PHPStan.
Cette fiche documente ce que Psalm vérifie sur notre stack Symfony et comment nous le configurons pour qu'il ne rentre pas en doublon avec PHPStan.
PRÉSENTATION
Qu'est-ce que Psalm ?
Psalm est un analyseur statique PHP open source, créé chez Vimeo. Comme PHPStan, il lit le code sans l'exécuter et vérifie les types. Il porte en plus un mode que PHPStan n'a pas : l'analyse de teinte, ou taint analysis.
Dans ce mode, Psalm ne s'intéresse plus à ce qu'une valeur est, mais à ce qu'elle a traversé. Une donnée qui vient de l'utilisateur est teintée ; elle le reste tant qu'elle n'a pas été échappée, validée ou liée en paramètre. Si elle atteint un point d'exécution, Psalm affiche le chemin complet, du contrôleur à la ligne fautive.
Chez SmartBooster, nous utilisons Psalm pour cette analyse de sécurité seulement.
POURQUOI PSALM
Les avantages de l'analyse de sécurité avec Psalm
Psalm est activé sur tous nos projets Symfony grâce à de notre standard-bundle, en complément de PHPStan. Il est l'une des briques de notre démarche de qualité logicielle.
Un flux prouvé, pas un motif
Psalm suit la donnée depuis sa source (une requête HTTP) jusqu'au sink (une requête SQL, un echo, un exec), à travers les appels de méthodes et les fichiers. Il ne signale que lorsqu'un chemin complet existe.
Ce qu'un type-checker ne voit pas
PHPStan au niveau 10 est vert sur une requête DQL concaténée avec un paramètre de requête : le code est parfaitement typé. Il est simplement injectable. La vérification manquante porte sur un chemin, pas sur un type.
Des familles entières de vulnérabilités
Les types de teinte natifs couvrent l'injection SQL, le XSS, l'exécution de commande, l'inclusion de fichier, la SSRF, l'injection LDAP, l'injection d'en-tête, unserialize et le cookie. Chacun a son sink et son annotation d'échappement.
Le plugin Symfony déclare les sources
psalm/plugin-symfony marque Request, InputBag, ParameterBag et HeaderBag comme entrées non fiables, et le contenu d'une Response comme sink HTML. Sans lui, Psalm ne connaît que $_GET et $_POST.
Une baseline dédiée
L'analyse de teinte tourne à part du reste et a sa propre baseline, psalm-taint-baseline.xml. Sur un projet repris, on fige les remontées historiques et la CI ne bloque que sur les nouvelles.
Un remplaçant maintenu
Psalm a pris la place de pheromone/phpcs-security-audit, dont la dernière version date d'août 2019. Ses 32 règles à motifs ont été reprises une à une, par Psalm, par PHPStan ou par composer audit, et rien n'est tombé dans un trou.
NOTRE USAGE
Comment nous utilisons Psalm chez SmartBooster
Chaque outil de notre stack de validation de code à un objectif clair et nous optimisons sa configurationpour réduire les résultats inutiles (doublons ou faux positifs)
Mode teinte uniquement
Psalm est aussi un type-checker, concurrent direct de PHPStan. Notre psalm.xml le bride : runTaintAnalysis="true", errorLevel="8" (le niveau le plus permissif, l'échelle est inversée par rapport à PHPStan) et toutes les détections de code inutilisé désactivées. Il ne parle que de flux.
Un stub maison pour Doctrine
Aucun plugin ne marque le chemin ORM de Doctrine comme sink SQL. Notre fichier psalm-taint-stubs.php annote createQuery(), les méthodes where, andWhere, having, groupBy du QueryBuilder et executeQuery() côté DBAL. Sans lui, l'analyse est verte et ne détecte rien.
Trois commandes, livrées par le standard-bundle
make psalm pour toutes les remontées, make psgb pour générer la baseline, make psalm-ci pour n'échouer que sur les nouvelles. La dernière fait partie de make qa. Configuration, stub et baseline vide arrivent par la recette du standard-bundle.
Un faux positif se justifie, il ne se tait pas
Sur une remontée de teinte, le bon outil est @psalm-taint-escape sql posé sur la valeur que l'on considère comme assainie. L'annotation oblige à dire pourquoi la donnée n'est plus dangereuse, au lieu de masquer l'alerte.
SOURCES ET SINKS
Rendre Psalm pertinent pour Symfony & Doctrine
Psalm ne connaît ni Symfony ni Doctrine. Pour qu'un flux soit détecté, ses deux extrémités doivent être déclarées : la source d'un côté, le sink de l'autre.
vimeo/psalm
Le moteur. Il porte les types de teinte natifs et les sinks des fonctions PHP : mysqli, PDO, echo, exec, include, header, curl_*, unserialize.
psalm/plugin-symfony
Les sources : tout ce qui sort de Request, InputBag, ParameterBag et HeaderBag. Et un sink HTML sur le contenu d'une Response. Les stubs suivent la version de Symfony du projet.
psalm-taint-stubs.php
Les sinks Doctrine ORM et DBAL, écrits par nous. Le fichier reproduit les signatures du vendor en y ajoutant @psalm-taint-sink sql : si Doctrine change une signature, le stub suit.
FAQ
Les réponses à vos questions
Et si vous ne trouvez pas ce que vous cherchez, nous serons ravis de vous répondre en direct lors d'un rendez-vous entre humains !
Parce qu'ils ne répondent pas à la même question. PHPStan vérifie ce qu'une valeur est, Psalm en mode teinte vérifie ce qu'une valeur a traversé. Le mainteneur de PHPStan a décliné en 2022 d'implémenter l'analyse de teinte (issue #8038) et aucune extension largement adoptée ne le fait. Psalm sait aussi vérifier les types, mais nous le bridons pour qu'il ne concurrence pas PHPStan : un seul type-checker, un seul analyseur de flux.
On déclare des sources, tout ce qui vient de l'utilisateur, et des sinks, tout ce qui exécute ou affiche. L'outil suit la donnée de l'une à l'autre. Si un paramètre de requête HTTP arrive dans une requête SQL sans passer par un échappement ou un paramètre lié, c'est une injection SQL prouvée, et Psalm affiche le chemin complet : le contrôleur qui lit la valeur, le service qui la transmet, la ligne du repository qui l'exécute.
Psalm connaît nativement les sinks SQL de mysqli et de PDO, mais notre code passe par Doctrine : createQuery() et le QueryBuilder. Aucun plugin ne marque ce chemin ORM comme sink, le plugin communautaire existant ne couvre que le DBAL bas niveau et ses stubs entrent en collision avec ceux du plugin Symfony. Sans notre fichier, l'analyse tourne, termine en vert et ne détecte aucune injection passant par Doctrine. C'est le pire résultat possible : un contrôle qui rassure sans contrôler.
Oui. Le projet a connu un passage à vide autour de 2023, ce qui justifie la question. La version 6.17.0 est sortie le 10 septembre 2026, la branche 7.0 est en bêta depuis fin 2025 et psalm/plugin-symfony a publié sa 5.3.0 en février 2026. Nous surveillons cette cadence comme celle de toutes nos dépendances.
Pas avec @psalm-suppress, qui n'atteint pas les remontées de teinte. On pose @psalm-taint-escape suivi du type de teinte sur la valeur que l'on considère comme assainie, par exemple après une validation stricte ou une conversion en entier. L'annotation documente pourquoi la donnée n'est plus dangereuse. Pour les remontées historiques d'un projet repris, la baseline dédiée les fige et la CI ne bloque que sur les nouvelles.
Tout ce dont les deux extrémités ne sont pas déclarées. Un sink absent des stubs n'existe pas pour lui, c'est la raison du fichier Doctrine. La présence d'une fonction dangereuse sans flux (eval, phpinfo, md5) n'est pas non plus son sujet : PHPStan la bannit via phpstan-disallowed-calls. Et les CVE des dépendances relèvent de composer audit. Trois familles, trois outils.
Oui, c'est le rôle de la baseline. On la génère une fois avec make psgb pour figer les remontées existantes, puis make psalm-ci en CI ne signale que les nouveaux flux. Il faut la générer avant d'activer le job, sinon la première exécution échoue sur l'ensemble du code. Les remontées historiques se traitent ensuite par lots, dans le cadre de la maintenance.
Pour aller plus loin
Approfondir votre réflexion
L'arbitrage complet : la reprise sniff par sniff de l'ancien outil, les choix de configuration et ce que ça coûte à faire vivre.
La démonstration pas à pas d'une injection SQL détectée par Psalm, et sa traduction dans les référentiels de sécurité.
L'analyse de teinte fait partie de ce que nous passons sur une application existante avant de nous engager sur sa reprise.
Vous avez un projet ?
Contactez-nous pour savoir comment nous pouvons vous aider.