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.