Laravel 13.30 refuerza los workers y el acceso a Storage
Laravel 13.30 añade señales útiles para operar workers y corrige una ruta de escape en Storage::path().
Cuando un worker se detiene y nadie sabe por qué
Un worker de Laravel que desaparece sin contexto complica más la operación que el fallo en sí. En Docker, un homelab o cualquier servidor sin una consola abierta, lo normal es acabar mirando logs a posteriori e intentando reconstruir qué ocurrió. Laravel 13.30 incorpora el motivo de parada en la salida de queue:work, algo pequeño pero útil para dejar de adivinar. (github.com)
La mejora cobra sentido cuando los logs del contenedor salen hacia Loki, Elasticsearch, Grafana o simplemente se consultan con docker logs. Hasta ahora podía verse que el proceso había terminado, pero faltaba una señal directa sobre la causa. Ahora el worker emite un registro final cuando dispone de un motivo de parada. (raw.githubusercontent.com)
Logs JSON que sirven para alertar
La opción --json de queue:work ya existe para emitir información del worker en formato JSON. En Laravel 13.30, el evento de parada también usa ese formato cuando el comando se ejecuta con esa opción. El registro incluye, entre otros datos, el estado stopped, el motivo, el código de salida, los trabajos procesados y el uso de memoria cuando está disponible. (raw.githubusercontent.com)
Yo lo usaría en workers que viven dentro de contenedores y cuyos logs se recogen fuera del proceso. No hace falta montar un listener propio solo para saber si un worker salió por memoria, por límite de trabajos, por tiempo máximo o por una señal de reinicio. Esa información queda en la salida estándar, que es donde debería estar para integrarla con el resto de la observabilidad. (github.com)
services:
worker:
image: mi-aplicacion:latest
command: php artisan queue:work redis --json --sleep=3 --tries=3
restart: unless-stoppedEsta configuración no sustituye una estrategia de supervisión, pero deja una pista estructurada antes de que Docker decida reiniciar el contenedor. También permite crear alertas más concretas que un simple “el contenedor se ha reiniciado”. Por ejemplo, una parada por límite de memoria no se trata igual que una salida intencionada después de procesar un número máximo de trabajos.
No confundamos salida del worker con error de un job
El nuevo registro describe por qué se detuvo el worker, no convierte automáticamente cada fallo de un job en un diagnóstico completo. Los jobs fallidos siguen teniendo su propio ciclo de reintentos, excepciones y almacenamiento en la tabla o backend de trabajos fallidos. Conviene correlacionar ambos tipos de eventos, pero no mezclar sus responsabilidades. (raw.githubusercontent.com)
También hay un detalle fácil de pasar por alto: Laravel no escribe esta salida si el comando se ejecuta con --quiet o --silent. Si la idea es recoger esos registros desde Docker o un agregador de logs, esas opciones anulan precisamente la señal que interesa conservar. Es una decisión razonable para comandos silenciosos, pero merece una revisión en los manifiestos y scripts de despliegue. (raw.githubusercontent.com)
Storage path deja de aceptar atajos peligrosos
La otra parte importante de Laravel 13.30 afecta a Storage::path(). Ese método devuelve una ruta para un archivo dentro del disco configurado y, en discos locales, suele ser una ruta absoluta. A partir de esta versión, Laravel normaliza el valor antes de construir la ruta y rechaza intentos de salir de la raíz del disco con segmentos como ... (raw.githubusercontent.com)
El cambio corrige una inconsistencia seria: otras operaciones de almacenamiento ya normalizaban la ruta, mientras que Storage::path() podía construir una ruta fuera del directorio raíz configurado. Si una aplicación componía esa ruta con una entrada controlada por el usuario, el resultado podía apuntar a un archivo ajeno al disco. Ahora ese patrón provoca una excepción PathTraversalDetected en lugar de devolver una ruta válida. (github.com)
Revisar las descargas construidas desde la petición
Buscaría primero controladores que reciban un nombre o una ruta por query string, parámetro de URL o formulario. El patrón problemático no siempre será evidente, porque a veces la entrada se concatena con un prefijo aparentemente seguro. Si el valor llega desde fuera de la aplicación, no debería ser la referencia directa al archivo que se descarga.
Prefiero guardar en base de datos la ruta relativa generada por la aplicación y recuperar el documento mediante un identificador autorizado. Después, Laravel puede servir el archivo desde el disco sin pasar por Storage::path() ni construir rutas nativas a mano. Esta aproximación separa la autorización del usuario, la identidad del documento y la ubicación física del archivo.
use Illuminate\Support\Facades\Storage;
Route::get('/documentos/{documento}', function (Document $document) {
abort_unless($documento->user_id === auth()->id(), 403);
return Storage::disk('private')->download(
$documento->storage_path,
$documento->original_name,
);
});El método download() trabaja con una ruta relativa al disco, igual que el resto de la API de almacenamiento. Eso no elimina la necesidad de comprobar permisos sobre el documento, pero evita convertir una ruta aportada por el cliente en una ruta absoluta del sistema. La documentación oficial también deja claro que las rutas de almacenamiento deben especificarse en relación con la raíz del disco. (laravel.com)
La letra pequeña de este endurecimiento
Este cambio no arregla una autorización mal planteada. Que una ruta no pueda escapar del disco no impide que un usuario descargue el archivo privado de otro usuario si la aplicación acepta identificadores predecibles y no verifica la propiedad. La normalización de rutas es una barrera técnica, no un modelo de permisos.
Tampoco conviene capturar PathTraversalDetected y continuar con una alternativa improvisada basada en realpath(), concatenación de cadenas o rutas absolutas. Si aparece esa excepción tras actualizar, normalmente hay dos posibilidades: existe una entrada malformada o el código dependía de un comportamiento que no debía usar. En ambos casos, lo útil es corregir el flujo y añadir una prueba de regresión.
Hay casos legítimos donde una aplicación necesita manipular archivos fuera de un disco de Laravel, por ejemplo al invocar una herramienta del sistema sobre un directorio controlado por la infraestructura. Ese caso debe usar una ruta definida por la aplicación o por configuración de confianza, no un valor que viaje desde la petición HTTP. El cambio en Storage::path() no pretende cubrir todas las APIs de archivos de PHP.
chunkBy para grupos consecutivos
Laravel 13.30 también añade chunkBy() a las colecciones. El método agrupa elementos adyacentes comparando una clave o el resultado de un callback, por lo que resulta práctico cuando la colección ya viene ordenada por el criterio que interesa. No es lo mismo que agrupar todos los elementos iguales repartidos por toda la colección. (github.com)
$bloques = collect([
['estado' => 'nuevo', 'id' => 1],
['estado' => 'nuevo', 'id' => 2],
['estado' => 'pagado', 'id' => 3],
])->chunkBy('estado');En ese ejemplo se generan bloques por cambios de estado consecutivos. Si el mismo estado reaparece más adelante, se crea otro bloque distinto. Para agrupaciones globales sigue siendo más apropiado plantear groupBy() o resolver el orden de los datos antes de usar chunkBy(). (raw.githubusercontent.com)
Una actualización para revisar, no solo instalar
No veo aquí una lista de cambios para correr a actualizar sin mirar el código. El registro de parada de workers puede mejorar bastante la observabilidad de una instalación pequeña o de un despliegue con varios contenedores. El endurecimiento de Storage::path() merece una búsqueda específica en el proyecto antes de llegar a producción.
Mi lista de comprobación sería sencilla: activar --json donde los logs se recojan de forma centralizada, verificar que no se use --quiet por inercia y localizar cada llamada a Storage::path(). Si alguna recibe datos de una petición, la solución no debería consistir solo en validar que no lleve ... Lo correcto es que el cliente pida un recurso autorizado y que la aplicación resuelva internamente la ruta relativa guardada para ese recurso.
Fuente: Laravel News Documentación oficial: Laravel Framework CHANGELOG