Volver a la wiki

Fuga cross-tenant en el publicador anónimo de signage (playlist_id sin validar)

Cuándo

Hallazgo señalado por una auditoría interna de aislamiento multi-tenant y listado como pendiente de verificación independiente en AUDIT.md (sección “Hallazgos multi-tenant SIN VERIFICAR”), apuntando a signage/api/schedules.py:53,102. Verificado y cerrado el 23-08-2026 en la ronda de mantenimiento de signage — commit 49904a3d (PR #420), versión v1.82.2, task #248.

Síntomas visibles

Ninguno reportado por clientes. El hallazgo nació de una auditoría de código, no de un incidente en producción; no consta evidencia de explotación real.

Causa raíz

El publicador de signage (signage/views_publish.py, función _get_playlist_items) es una ruta anónima: el middleware de Row Level Security exime explícitamente a las peticiones sin autenticar (bypass app.current_org_id = '0', ver [[decision—20260403—multi-tenancy-rls]]). Esa ruta resolvía el horario activo del reproductor (evaluate_schedule) y usaba el playlist_id devuelto tal cual para buscar la Playlist, sin filtrar por organización:

playlist = Playlist.objects.filter(id=result["playlist_id"]).first()

El playlist_id vive dentro del JSON libre del campo rules de Schedule. Nada impedía que ese número apuntara a una playlist de otra organización — ni por error al guardar, ni de forma deliberada si alguien conocía o adivinaba un ID ajeno. Sin el filtro, el reproductor podía acabar emitiendo los textos, rótulos y colores de la playlist de otro cliente.

El borde autenticado tampoco cerraba la puerta: al crear o editar un Schedule (signage/api/schedules.py), nada validaba que los playlist_id citados en las reglas pertenecieran a la organización del usuario — un horario podía guardarse apuntando a una playlist ajena sin ningún aviso, y el problema solo se manifestaba más tarde, en el publicador.

Fix aplicado

Commit 49904a3d (PR #420, v1.82.2), doble barrera:

Se cierra tanto en el borde de guardado (autenticado) como en el borde de lectura (anónimo, sin red de seguridad de RLS por debajo).

Lecciones

Preventivos futuros

Véase también

Subir