Daniil Gentili ha anunciado Psalm 7, la nueva versión del analizador estático de PHP. La lista de novedades es larguísima, pero hay un cambio que destaca por encima del resto: el análisis de seguridad viene activado por defecto y se ejecuta en la misma pasada que la comprobación de tipos y la detección de código muerto.
Hasta ahora, el análisis de taint (seguir los datos que vienen de fuera hasta que llegan a un sitio peligroso) era algo que había que activar a propósito con --taint-analysis, y que en la práctica casi nadie metía en el CI porque era lento. Psalm 7 lo ha rehecho entero: guarda los taints como máscaras de bits y resuelve el grafo sobre resúmenes compactos de cada función. Según el anuncio, hasta 10 veces más rápido, y con resultados que ya no cambian entre ejecuciones ni según el número de hilos.
Más fuentes y más caminos
El análisis sigue ahora los datos por muchos más sitios: closures y parámetros callable, generadores, parámetros por referencia, traits, propiedades promocionadas del constructor, __toString(), excepciones…
<?php
$run = function (string $c): void {
shell_exec($c);
};
$run((string) $_GET['cmd']); // TaintedShell, a través del closure
function body() {
return json_decode((string) file_get_contents('php://input'), true);
}
eval((string) body()); // TaintedEval: php://input es una fuente
Hay fuentes nuevas, como php://input, STDIN, $argv, las respuestas DNS, los nombres de los ficheros subidos o las entradas de $_SERVER que manda el cliente. Y también tipos de taint nuevos:
| Tipo | Qué detecta |
|---|---|
TaintedNosql | Inyección en consultas a MongoDB |
TaintedLlmPrompt | Entrada del usuario que acaba en un prompt de un LLM |
TaintedUrlComponent / TaintedUrlPath | Inyección en la query o en la ruta de una URL |
El de los prompts me parece especialmente acertado. Cada vez hay más código PHP que monta un prompt con lo que escribe el usuario y se lo pasa a un modelo, y tratarlo igual que una consulta SQL es justo la forma correcta de verlo.
Para reducir falsos positivos, se descartan los taints que un tipo no puede llevar (un int no puede inyectar HTML), y trae stubs para Guzzle, PSR-7, Monolog, MongoDB, PhpSpreadsheet y unas cuantas librerías más, que sólo se cargan si tu código las usa.
Pureza con capacidades
La otra gran novedad es el modelo de pureza. @psalm-pure, @psalm-mutation-free y compañía se sustituyen por capacidades, copiadas de Hack: una función declara exactamente qué efectos secundarios puede tener (io, time, read-props, write-props, read-globals…).
<?php
/** @psalm-capabilities time */
function today(): string {
return date('Y-m-d'); // bien: declara que usa el tiempo
}
/** @psalm-pure */
function todayWrong(): string {
return date('Y-m-d'); // ImpureFunctionCall: requiere time
}
Tiene además plantillas de pureza: array_map() es pura si el callback que recibe lo es, y ahora Psalm lo entiende así. Y puede sugerir y añadir las anotaciones él solo con --alter. Esto tiene un efecto que no es evidente: si Psalm sabe que una función es pura, puede analizar sus taints en cada llamada por separado, así que anotar el código también quita falsos positivos del análisis de seguridad.
El resto
- Código muerto por grafo: sólo se considera usado lo que es alcanzable desde un punto de entrada, así que detecta clases que sólo se usan entre ellas (ciclos incluidos) y excepciones que se crean y nunca se lanzan.
- Plantillas con valor por defecto:
@template TError of Throwable = RuntimeException, así queResult<int>funciona y las librerías pueden añadir plantillas sin romper a nadie. - PHP 8.5 y 8.6: tipa cada paso del operador pipe y la aplicación parcial de funciones, así que si un paso no encaja con el anterior, lo dice.
- Plugins incluidos: los de PHPUnit, Symfony, Laravel y Mockery vienen de serie y se activan solos si tu
composer.jsonusa esas librerías. - Versiones diarias y
--init-cipara generar el workflow de GitHub Actions.
Actualizar
Funciona con PHP 8.2 a 8.6 de 64 bits, y para la mayoría de proyectos basta con:
composer require --dev vimeo/psalm:^7.0 -W
Lo que más vas a notar es que, de repente, aparecen avisos de seguridad que antes no salían. Se puede desactivar con runTaintAnalysis="false" en psalm.xml, pero yo no lo haría. Mejor dejarlo, pasarlo una vez, revisar lo que salga con calma y suprimir sólo lo que de verdad sea un falso positivo.
Lo que me llevo
Durante años, el análisis estático en PHP fue un extra que pocos proyectos usaban. Que una herramienta así pase a revisar la seguridad sin pedírselo es un cambio de enfoque importante: lo que antes era opcional (y por tanto casi nunca se hacía) ahora es lo normal, y desactivarlo es una decisión consciente.
Y con tanto código generado por IA, tener algo que sigue cada dato de $_GET hasta un shell_exec, un eval o un prompt, sin cansarse y sin fiarse de nadie, me parece todavía más necesario que antes.
Fuente: Announcing Psalm 7, Daniil Gentili.




