Skip to content

WP-Pulse: corrección selectiva de Pillow — 20 octubre 2026

Estado actual, 21oct09:54CEST: Pillow13.0.0 instalado selectivamente en CT281, API renovada una sola vez y comprobación independiente posterior correcta. La fuente está integrada en main por PR94. Las secciones de preparación y sus resultados inciertos se conservan como historia; la instalación se cierra abajo.

Evidencia y cambio de fuente

La lectura de GitHub API posterior al push 146462f devuelve diez alertas abiertas. Nueve se crearon el 20 de octubre, entre 21:15:37 y 21:15:39 UTC: Pillow directo de ejecución en services/wp-pulse-server/uv.lock, versión 12.3.0. No son nueve paquetes distintos ni evidencia de explotación. La alerta 146 de braces es anterior, transitiva de desarrollo y sigue sin versión corregida publicada; este cambio no la silencia ni modifica el frontend.

Alertas Severidad GitHub GHSA
148 Crítica 8g5g-8vmr-9782
149, 151, 152, 153, 155, 156 Alta c6hw-5grf-ff96; p4wg-p73j-c528; pchr-8j38-3x4p; q45j-58j2-45rg; 65qm-jvmh-6pqg; 58r4-6rv3-gfh8
150, 154 Moderada 462x-pgg6-55fp; vx6w-vv9f-6gvg

Las nueve identifican 13.0.0 como primera versión corregida. Véanse el advisory crítico del mantenedor y las notas oficiales de Pillow 13.0.0, publicado el 16 de octubre. Las severidades de la tabla proceden de GitHub; no se equiparan a la clasificación propia de las notas de Pillow.

El requisito pasa de pillow>=12.3.0 a pillow>=13.0.0; se regeneró el lock con uv --no-config lock --upgrade-package pillow. Comparación TOML: mismos 62 paquetes, únicamente cambia el bloque Pillow y su requisito en los metadatos del proyecto. Los otros paquetes y los demás campos del lock quedan idénticos. No se cambian certifi, Python ni dependencias de desarrollo.

Compatibilidad y pruebas locales

El proyecto ya requiere Python >=3.12. Pillow 13 requiere >=3.11, por lo que no se eleva el mínimo. El uso propio revisado está en api/utils/visual_diff.py: abrir imágenes, convertir a RGB, redimensionar con LANCZOS, comparar píxeles y guardar una composición PNG. No usa las APIs retiradas documentadas en 13.0.0; la conversión NumPy conserva el tipo uint8/RGB admitido. Esta revisión no afirma que todas las rutas de los nueve avisos sean alcanzables en el producto.

Instalaciones aisladas congeladas en Python 3.12.13 y 3.14.7: 17 pruebas correctas en cada intérprete, catorce existentes de visual_diff y tres nuevas con Pillow real. Sólo imágenes PNG/JPEG sintéticas y temporales: diferencias con resize/alturas distintas, imagen idéntica entre formatos, ratio y píxeles de la composición PNG. Los tests existentes usan SQLite en memoria y ASGI sin lifespan; Playwright está simulado. Sin navegadores, HTTP externo, productos, credenciales, NAS ni datos personales. Dos avisos FastAPI sobre regex son ajenos a este cambio. No es una prueba de exploits ni una garantía de runtime.

Logs privados: ~/.local/state/wp-pillow-fixtures-cahju3j2/pytest.log y pytest314.log. Se añade CI focal WP visual diff tests en Python 3.12 y 3.14 con uv0.11.17, instalación congelada y el mismo comando/entorno sintético. Antes no existía un workflow que ejecutase estas pruebas de aplicación. Los dos checks Linux del run 39501686521 completaron correctamente para el head <REDACTED>: 17 pruebas por intérprete. Las 17 pruebas no cubren todo WP.

La metadata oficial PyPI ofrece wheel pillow-13.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl, 7142574 bytes, SHA256 <REDACTED>. El wheel se ha verificado y preparado en un directorio privado del servidor; la preparación todavía no lo instala ni importa Pillow.

Runtime y recuperación

La PR94 está integrada en main <REDACTED>. Los checks posteriores a main 39502162247 y la publicación de documentación 39502162064 terminaron correctamente; el publicador identifica esa revisión como construida. Las nueve alertas de Pillow aparecen corregidas en fuente en GitHub. Esto no acredita por sí solo la versión instalada en el servidor.

La lectura inicial identifica Pillow12.3.0 en CT281, propietario proxmox, Python3.14.5/glibc2.36 y venv /opt/wp-pulse-server/.venv. Proyección privada ~/.local/state/wp-pillow-read-mhsmwe5l/projection.json, SHA256 <REDACTED>.

La preparación posterior verificó propietario/boots antes y después, servicio activo con PID856, NRestarts0, KillMode control-group y definición efectiva estable. Se preparó el wheel oficial sin importarlo ni modificar la venv y se creó una copia independiente de los142 archivos de Pillow12.3.0, incluidos PIL, pillow.libs y dist-info. Doble lectura, hashes/identidades completas, propietario/permisos/mtime y fsync de archivos/directorios; manifiesto <REDACTED>. La copia está en /root/wp-pillow-rollback-xusk_w6w dentro de CT281; no es una copia general del producto ni una restauración E2E. Recibo local ~/.local/state/wp-pillow-selective-plan-iy3th4es/prepare-result.json.

El preflight puro sobre los archivos vivos acredita exactamente dos cambios en uv.lock (bloque Pillow y su requisito en los metadatos del proyecto) y uno en pyproject.toml; preserva los demás bytes y contenido TOML. No se copia el lock completo, cuyas diferencias de runtime ya eran conocidas. En esta fase la API sigue activa y Pillow instalado sigue en12.3.0: faltan parada breve, instalación offline selectiva, CAS, comprobación nativa sintética y arranque verificado. La comprobación de trabajos activos se repetirá inmediatamente antes de parar; una muestra previa no reserva el servicio.

La recuperación preparada conserva copias independientes y exige comprobar el estado actual antes de restaurar sólo Pillow y las dos fuentes emparejadas. No hay reintento ni rollback automático. Revertir Git no revierte una instalación; volver a12.3.0 sería recuperación excepcional a una versión vulnerable.

Nueva observación — 21 octubre, 09:41 CEST

El intento de preparar los helpers ya ligados a las rutas reales terminó en unknown / parent_before: la guarda detectó un boot diferente antes de entrar en CT281. No se reintentó ni se relajaron las guardas de mutación. No hubo parada de API, instalación ni CAS. Se conserva bound-stage-result.json con ese resultado; las copias ligadas al boot anterior quedan sin ejecutar.

Una lectura nueva, exclusivamente de metadatos, acredita host proxmox boot <REDACTED_UUID>, btime21oct06:25:34CEST, y CT281 boot <REDACTED_UUID>. WP está activo/running, PID834 desde 06:26:11CEST, NRestarts0. Pillow sigue en12.3.0; uv.lock6097d722 y pyproject6c387cce conservan sus hashes originales completos indicados en los recibos. El manifiesto de recuperación mantiene el SHA eb403c98 registrado arriba; esto no sustituye revalidar todas sus copias antes de una mutación. El control ligado al boot anterior no llegó a copiarse al servidor.

Evidencia local: parent-change-readback.json y postboot-metadata.json, en el mismo directorio privado de preparación. Hay que volver a acreditar inventario, identidades, definición de unidad, fuentes y quiescencia para este nuevo boot; no basta sustituir los UUID antiguos en los scripts. Los subagentes alcanzaron el límite de uso antes de entregar las copias finales ligadas; root conserva los templates y las revisiones previas, sin afirmar una revisión de pares de bindings que no llegaron a entregarse. La corrección de fuente sigue integrada; la instalación real continúa pendiente.

A las09:42 una nueva lectura verificó los142 archivos completos y sus141 hashes RECORD, sin extras ni pyc. Inventario e identidades completas, directorios, metadatos Pillow, uv, lock, Python y plataforma coinciden exactamente con la lectura anterior; sí cambian los boots y la identidad del proceso. Recibo ~/.local/state/wp-pillow-postboot-read-7hu32rik/projection.json, SHA256 <REDACTED>; comparación privada postboot-baseline-comparison.json. No importa PIL ni la aplicación, no hace HTTP y no modifica el runtime. Esta comparación reduce las comprobaciones pendientes, pero no religa las guardas de mutación por sí sola.

A las09:44 se verificaron además las142 copias independientes del respaldo, la definición efectiva de systemd y las cuatro fuentes de API: todos conservan los hashes e identidades previstos. La búsqueda acotada de formas conocidas de escritores de paquetes/deploy revisó111 procesos sin coincidencias; no es un bloqueo de escritores futuros. La credencial se inspeccionó sólo por metadatos: cambia dev56→49 tras el boot y mantiene inode/uid/gid/permisos, longitud/mtime/ctime. Su contenido no se leyó en esta comprobación. Recibo privado postboot-controls-result.json; quiescencia SQL y bindings nuevos siguen pendientes, sin reutilizar la acreditación anterior de la credencial.

Instalación y cierre — 21 octubre, 09:49–09:54 CEST

Tras las lecturas del boot actual se prepararon copias nuevas de los templates, con un registro explícito de los cambios de constantes: boots, PID834, inicio, SHA del nuevo inventario y del helper que lo admite. Inventario/identidades, fuentes, definición efectiva y respaldo fueron acreditados antes de ligarlas; no se ignoró la guarda que rechazó el intento anterior. Los bindings son sustituciones de constantes verificadas/compiladas por root; no se atribuye a los subagentes una revisión final posterior a su límite de uso.

El directorio temporal de wheels anterior ya no existía tras el boot. Un primer intento nuevo falló antes de crear su staging al comprobarlo; se conserva el resultado incierto y su diagnóstico, sin reintentar instalación. Después se prepararon directorios privados nuevos con el mismo wheel oficial local, rehash/owner/permisos y fsync. Directorio de evidencia: ~/.local/state/wp-pillow-current-plan-677xvd_y/.

La única lectura SQL de09:49:19CEST fue de sólo lectura/repeatable-read, con statement_timeout5s/lock_timeout1s: cuatro sitios y cuatro horarios habilitados, ninguno debido/próximo en cinco minutos; cinco comandos pendientes/reclamados son contexto, los otros conteos de trabajo activo revisados están en cero. La credencial acreditada para este boot conserva sus demás metadatos, dev49; DSN consumido sólo en RAM del guest. No se publicaron contenidos ni credenciales.

Se efectuó una parada y una instalación: uv0.11.17 como wp, wheel local pinned, offline/no-index/no-build/no-deps/require-hashes/link-mode-copy/no-cache; sin sync, deploy integral, migraciones ni actualización de otros paquetes. La parada quedó verificada inactive/dead/PID0 antes de sustituir código nativo.

uv devolvió0; el primer verificador rechazó un único permiso: el SBOM pillow-13.0.0.dist-info/sboms/pillow-13.0.0.cdx.json estaba0644 y el wheel lo declara0600. Un diagnóstico independiente verificó142 archivos y detectó sólo esa diferencia, pasando los demás controles. Root aplicó fchmod0600 sobre ese archivo bajo CAS de identidad/hash/owner/nlink y servicio parado, sin tocar sus bytes. No se repitió uv ni se relajó el verificador. La comprobación estricta original pasó entonces completa y generó un recibo independiente.

El recibo incierto original se conserva sin cambiar sus bytes, SHA256 <REDACTED>, en install-disk-receipt.original-uncertain.json dentro del staging. Tras preservarlo/fsync y verificarlo se reconcilió el recibo principal con el resultado independiente SHA <REDACTED>. El resultado local fallido de la instalación también permanece intacto; los pasos siguientes exigieron explícitamente el recibo nuevo. No se atribuye al readback la prueba de descriptores ELF que no llegó a cerrarse en el primer intento. El respaldo independiente completo sí se volvió a verificar.

El CAS de las tres partes autorizadas pasó y conservó los demás bytes/TOML, propietario y permisos, con copias independientes/fsync de ambas fuentes:

Fuente viva SHA256 después
uv.lock <REDACTED>
pyproject.toml <REDACTED>

Con la API aún parada pasó la prueba nativa de Pillow13: codificar/decodificar PNG y JPEG diminutos en memoria, dimensiones/RGB/píxeles/versiones; sin importar la app, usar imágenes personales, HTTP ni trabajos reales. Un único start dio PID227007 a09:54:15CEST, observado estable5s, NRestarts0, sin cambios de fuentes, definición, ejecutable, usuario ni directorio de trabajo.

La comprobación independiente posterior acredita propietario/boots antes y después, proceso activo/running, los142 archivos instalados con hash/identidad, Pillow13.0.0, ambas fuentes y metadatos de todas las otras distribuciones intactos, así como el respaldo original. Recibos start-result.json y postcheck-result.json en el directorio privado indicado. No se hizo ninguna petición al producto, trabajo sintético de la aplicación ni prueba E2E; la prueba nativa no demuestra ausencia de todas las rutas de explotación. El watchdog del host sigue siendo una incidencia distinta y no queda resuelto por esta actualización.

Braces: cadena revisada el 21 de octubre, sin corrección compatible acreditada

Observación 11:06:33 CEST / 09:06:33 UTC, fuente base 31cd59f. La alerta 146 sigue abierta, high y de ámbito development en el lock del web: GHSA-vfj7-8cjw-p6xm / CVE-2026-93687, afecta braces <=3.0.3, sin primera versión corregida. El aviso original al mantenedor explica el agotamiento de pila de los walkers recursivos con patrones anidados que todavía caben en el límite de caracteres; reducir el problema a tamaño de entrada o al ámbito development no demuestra que esté mitigado.

El lock local introduce una única cadena hacia ese paquete:

eslint-config-next 15.5.27 (devDependency)
└─ @next/eslint-plugin-next 15.5.27
   └─ fast-glob 3.3.1
      └─ micromatch 4.0.8
         └─ braces 3.0.3

Metadata del registro oficial consultada en esa misma revisión: latest de braces sigue siendo 3.0.3 y micromatch 4.0.8 mantiene la referencia a braces. Fast-glob 3.3.3 conserva micromatch; la fuente oficial del plugin Next 16.4.2 sigue declarando fast-glob 3.3.1. Por tanto esas actualizaciones, incluida una major de Next, no acreditan retirar la cadena ni corregir esta alerta.

Comandos y límite de exposición observado

package.json conserva dev: next dev --turbopack, build: next build, start: next start, lint: next lint y typecheck: tsc --noEmit. .eslintrc.json habilita next/core-web-vitals y next/typescript; quitar eslint-config-next retiraría comprobaciones existentes. next.config.ts ya contiene eslint.ignoreDuringBuilds=true: un build correcto no acreditaría que haya pasado lint. Esta revisión no cambia ninguna de esas opciones.

En la función oficial getRootDirs de la versión usada, el glob se procesa cuando settings.next.rootDir especifica paths/patrones; el caso predeterminado usa el directorio de ESLint. La configuración local no establece ese rootDir. No se encontró entrada HTTP de la aplicación hacia esa ruta concreta del linter; no es prueba de explotabilidad nula ni una auditoría de otros consumidores. No se inspeccionaron node_modules, builds ni procesos del frontend desplegado, y el ámbito development no describe por sí solo cómo se instaló producción.

No se eliminó lint, no se sustituyó una API de glob por otra mediante override, no se mantuvo un fork de walkers y no se silenció la alerta. No se encontró una remediación compatible acreditada con las versiones oficiales consultadas. La condición para cerrarla sigue siendo retirada compatible de la cadena o versión corregida validada con lint, typecheck y build del frontend. La contención actual exige mantener patrones/configuración de herramientas bajo control de fuente; no presentar eso como parche del paquete.

Recibo privado 0600 bajo directorio 0700: ~/.local/state/wp-braces-review-20261021-nwr0mauf/receipt.json, SHA256 <REDACTED>. Conserva campos seleccionados de la alerta, versiones/dependencias de registro, URL y hash de fuentes primarias y hashes de los cinco archivos locales revisados. No hubo instalación, cambio de manifiesto/lock, pruebas/build, consulta o despliegue de producción. Al ser un cambio documental no se declara validación funcional nueva.

PR73: actualización útil distinta de braces, pendiente de validación

La PR73 de Dependabot seguía abierta en la consulta del 21 de octubre, head <REDACTED>, sin checks publicados en statusCheckRollup. Su manifiesto propone Next/eslint-config-next 15.5.27 → 15.5.28 y React/react-dom 19.3.0 → 19.3.1. Next 15.5.28 corrige GHSA-2464-fhr4-w9xw, critical y GHSA-p3m5-59mp-jv5r, high; ambos avisos oficiales incluyen 15.5.27 en su rango afectado. Es una mejora de seguridad concreta pendiente; no corrige la cadena braces descrita arriba.

La PR completa también reescribe overrides del lock, cambia Monaco 0.55.1 → 0.57.0 y baja, entre otros, DOMPurify 3.4.17 → 3.4.15, js-yaml 5.5.0 → 4.3.2 y nanoid 6.0.2 → 3.3.20. Esas diferencias requieren explicación propia; no se acredita compatibilidad de todo el grupo por el número de versión de Next ni se recomienda su merge directo. La búsqueda local en informes operativos fechados no encontró una revisión anterior de PR73 o de esas versiones; eso no demuestra que nunca se revisaran.

Propuesta acotada para la siguiente intervención: preparar sólo la pareja Next/eslint-config-next 15.5.28 y su cierre de dependencias, preservando los overrides y evitando las diferencias ajenas del lock. El registro oficial declara peers compatibles con React/react-dom 19.3.0 y ESLint 9.28.0, por lo que no obliga a cambiar React para esa pareja. La compatibilidad declarada no sustituye instalación reproducible desde lock, lint, typecheck y build. No se hizo ninguno de esos ensayos ni se modificaron paquetes en esta revisión.

Recibo de diff, estado de PR y metadata primaria: ~/.local/state/wp-braces-review-20261021-nwr0mauf/pr73-review.json, SHA256 <REDACTED>. La revisión es de fuente y no determina la versión ni exposición del frontend que está ejecutándose.

Candidato separado Next 15.5.28, validación local del 21 de octubre

Se preparó desde 31cd59f la actualización de next y eslint-config-next a 15.5.28 con pnpm 10.30.0. React/react-dom se mantienen en 19.3.0; los overrides de manifiesto, workspace y lock no cambian. La resolución modifica 25 versiones del cierre Next/ESLint, todas hacia delante, sin cambiar metadata de los restantes paquetes. Conserva Sharp 0.35.5, DOMPurify 3.4.17, Monaco 0.55.1 y braces 3.0.3; no importa el lock de PR73.

En macOS con Node 24.13.1: instalación --frozen-lockfile --ignore-scripts, typecheck y build pasan. Lint inicialmente falló con los mismos 16 errores react/no-unescaped-entities y 14 warnings que la base: comparación local acreditada con las dos versiones. Se escaparon sólo esos caracteres JSX en siete archivos, conservando texto visible y reglas, en un commit separado. Después pasan lint, typecheck y build; permanecen los 14 warnings. El build ya omitía lint por configuración, por eso se ejecutan por separado. La nueva CI acotada repite instalación frozen, lint, typecheck y build con Node 22 y pnpm 10.30.0, sin credenciales de producto.

La fuente no declara output: export; el comentario de next.config sobre FastAPI no demuestra que sólo se sirvan archivos estáticos. La lectura independiente de root encontró wp-pulse-web.service activo y Next 15.5.23 / React 19.1.0 en disco, distintos de Git; no acredita su versión cargada. Recibo privado ~/.local/state/wp-web-native-20261021-9sr0yjba/projection.json, SHA256 <REDACTED>. La actualización aquí es de fuente/build: no se declara parcheado el servicio vivo ni se atribuye explotabilidad concreta del producto. Antes de desplegar hay que reconciliar fuente y lock efectivos del web; esta PR no ejecuta ese paso ni instala sobre su árbol vivo.

Evidencia local del candidato en ~/.local/state/wp-next-security-20261021/: package-graph.json, lint-baseline-comparison.json, logs de las comprobaciones y checks-final.json. La metadata primaria de los avisos y el diff original de PR73 siguen en el recibo enlazado arriba.