Mil dólares. Esa es la recompensa que Google pagó a Salvatore Gulizia, el investigador detrás del alias Serotav, por reportar lo que acabó siendo CVE-2026-85046, un bug de confusión de tipos en V8 que los criminales ya estaban explotando antes de que el parche llegara a la mayoría de las máquinas. La build corregida, Chrome 152.0.7977.82, salió el 3 de septiembre de 2026. CISA añadió el fallo a su catálogo de Known Exploited Vulnerabilities al día siguiente y dio a las agencias federales de EE. UU. hasta el 18 de septiembre. CVSS 8.8, el sexto zero-day de Chrome del año confirmado como explotado activamente. Podrías preguntarte, con razón, por qué una publicación de PHP dedica su columna a un bug de navegador. Porque hay muchas probabilidades de que tu aplicación PHP despliegue un navegador, y de que nadie en tu equipo se encargue de parchearlo.

Haz un grep mental rápido sobre tu propio stack. spatie/browsershot renderizando facturas y etiquetas de envío a través de Puppeteer. Laravel Dusk o Symfony Panther manejando un Chromium real en tu suite de tests. Un contenedor de Playwright en el pipeline de CI. Quizá un scraper que descarga páginas de proveedores y las convierte en datos estructurados para el backend de la tienda. Cada uno de ellos arrastra una build completa de Chromium, y ese binario empequeñece todo lo demás en tu directorio vendor junto. Y sin embargo composer audit reporta verde, Dependabot guarda silencio, y la imagen que construiste en marzo sigue ejecutando el Chromium con el que nació. Hemos pasado una década profesionalizando la higiene de dependencias en PHP, y el ejecutable más grande del contenedor se la salta entera sin despeinarse.

El aviso de seguridad ofrece un consuelo: este bug por sí solo produce ejecución de código dentro del proceso de renderizado, que está en sandbox, y convertir eso en un compromiso total del host requiere encadenar un escape aparte. Vale para un Chrome de escritorio. Ahora abre tu Dockerfile. ¿Ves ese flag --no-sandbox que pasaste porque Chromium se niega a arrancar su sandbox dentro de un contenedor sin privilegios extra, y que todos los tutoriales de Browsershot de internet te dijeron que añadieras? Enhorabuena, el consuelo ya no aplica en tu caso. Con esa configuración, ejecución de código en el renderer es ejecución de código en tu worker, con el entorno de tu worker: el DSN de la base de datos, las claves de S3, los tokens de las APIs internas descansando en $_ENV como fruta en un frutero.

La objeción más fuerte merece una respuesta directa: mi worker de renderizado no navega por webs de atacantes, yo controlo el HTML que ve. ¿Seguro? El disparador de CWE-843 es JavaScript en una página manipulada, que explota una confusión del compilador donde un array de objetos normales queda etiquetado con un map pensado para enteros pequeños, lo que desemboca en lecturas y escrituras arbitrarias en el heap de JS. Tu plantilla de facturas interpola nombres de clientes, direcciones, notas de pedido en texto libre. Tu scraper descarga páginas de terceros cuyo trabajo es, precisamente, estar fuera de tu control. Salvo que desactives JavaScript explícitamente durante el renderizado, y casi nadie lo hace porque las hojas de estilo de impresión y las librerías de gráficas lo necesitan, el input no confiable está a un render de plantilla del JIT de V8.

Y esta no será la última vez. Dos de los cinco zero-days explotados de Chrome anteriores en 2026 también vivían en V8. Ese motor especula agresivamente sobre las formas de los objetos para hacer JavaScript rápido, y cada especulación es una suposición que se puede romper. Google lo fuzzea, lo endurece, paga recompensas por él, y los bugs siguen llegando de todas formas, porque el diseño sacrifica márgenes de seguridad a cambio de velocidad en los caminos calientes. El Chrome de escritorio absorbe esto con la actualización automática, normalmente antes de que los usuarios se den cuenta. Tu versión de Puppeteer fijada en el package.json que convive con composer no absorbe nada. Espera, pacientemente vulnerable, hasta que un humano la sube, reconstruye la imagen y redespliega los workers de la cola.

Así que esto es lo que yo cambiaría de verdad. Primero, asciende el navegador a dependencia de producción con nombre, versión y responsable, igual que tratas al propio runtime de PHP: cuando Chrome estable se mueva por una razón de seguridad, un job de CI debería fallar a gritos hasta que tu Chromium empaquetado se mueva también. Segundo, ponlo en cuarentena. Un paso de renderizado debería ser un servicio pequeño y aburrido que acepta HTML por un lado y emite bytes de PDF por el otro, sin credenciales que merezca la pena robar y sin ruta a internet que merezca la pena abusar. Tercero, y este es el incómodo, pregúntate si el navegador pinta algo en el pipeline. Una factura a dos columnas con un logo no necesita un compilador JIT. dompdf o mpdf la dibujarán sin ejecutar jamás una línea de JavaScript, y sí, maldecirás su soporte de CSS, y sí, ese trueque puede seguir siendo el correcto.

Ese último trueque es donde de verdad quiero que me lleves la contraria. He entregado ambas cosas: documentos renderizados con Chromium, perfectos al píxel, que hicieron feliz al departamento de contabilidad, y PDFs en PHP nativo que hicieron corta la revisión de seguridad. Después de ver a V8 producir su sexto zero-day explotado en nueve meses, me inclino por reservar el navegador para los pocos documentos que realmente se lo ganan. ¿Tú dónde te posicionas? ¿Merece la pena un CSS de impresión fiel a cambio de hacer de niñera de un motor de navegador con esta cadencia de parches, o has vuelto a una librería de PDF nativa en PHP y has hecho las paces con sus rarezas? Cuéntame sobre qué corre tu pipeline de documentos, y por qué.