
El subsistema de activos V2 de Plane confiaba en slugs de espacio de trabajo y UUIDs de activos sin aplicar las verificaciones de membresía correctas, lo que permitía a un usuario autenticado leer, copiar, eliminar y sobrescribir activos en otros espacios de trabajo.
El subsistema de activos V2 de Plane confiaba en slugs de espacios de trabajo y UUIDs de activos sin aplicar las comprobaciones de membresía correctas, lo que permitía a un usuario autenticado leer, copiar, eliminar y sobrescribir activos en otros espacios de trabajo.
Encontré este problema mientras revisaba Plane, la plataforma de gestión de proyectos de código abierto, con una pregunta muy específica en mente:
¿Los endpoints de activos V2 realmente imponen los límites del espacio de trabajo, o confían demasiado en los slugs de espacios de trabajo y los IDs de activos proporcionados por el atacante?
En este caso, la respuesta fue no.
El subsistema de activos V2 de Plane expuso dos fallos de autorización relacionados que rompían el aislamiento del espacio de trabajo para cualquier usuario autenticado:
Eso hizo posible el abuso de activos entre espacios de trabajo.
En mi PoC validado, un usuario normal en el espacio de trabajo Bravo pudo:
Ese problema luego fue asignado como CVE-2026-46558.
Plane: Plane en GitHub
CVE: CVE-2026-46558
Esto afectó a Plane, que en su sitio oficial se presenta como utilizado por . Plane también destaca una fuerte adopción de código abierto, incluyendo y , y muestra organizaciones como , , y .

atacante autenticado en espacio de trabajo B → ruta de activos V2 a nivel de espacio de trabajo confía en el slug del espacio de trabajo destino y el UUID del activo sin comprobaciones de membresía adecuadas → lectura presignada / parche / eliminación contra activos del espacio de trabajo A + búsqueda de fuente de duplicación de activos confía en el UUID de fuente subido → divulgación, copia, eliminación y sobrescritura de marca entre espacios de trabajo
Plane es una plataforma de gestión de proyectos de código abierto utilizada para gestionar:
Eso significa que su subsistema de activos se encuentra en un límite de confianza real.
La pregunta importante aquí no era si Plane admite subidas.
La verdadera pregunta era:
¿Plane impone el aislamiento del espacio de trabajo cuando un usuario autenticado hace referencia a activos propiedad de otro espacio de trabajo?
En este caso, no lo hizo.
Muchas revisiones de aplicaciones multiinquilino se centran primero en endpoints de administrador obvios o actualizaciones directas de configuración.
Eso pasa por alto una clase de errores muy común y muy real:
acceso a objetos secundarios a través de subsistemas de archivos o activos compartidos
Los sistemas de activos son fáciles de equivocar porque a menudo combinan:
Eso es exactamente el tipo de lugar donde los límites del inquilino se debilitan silenciosamente.
Este problema no era sobre corrupción de almacenamiento. No era sobre S3 en sí mismo. No era sobre el manejo de MIME en las subidas.
Era una falla en el límite de autorización:
Eso es suficiente para crear una vulnerabilidad real.
No abordé Plane probando ciegamente endpoints aleatorios o adivinando UUIDs sin un modelo.
El enfoque más sólido fue identificar primero el límite de aislamiento más prometedor.
Para Plane, ese fue el subsistema de activos V2.
¿Por qué?
Porque un sistema de activos compartido se vuelve peligroso cuando:
Ese era el límite correcto para inspeccionar.
Y fue exactamente donde vivía el error.
En realidad, fueron dos fallos de autorización relacionados en el mismo subsistema.
Las rutas de activos a nivel de espacio de trabajo estaban expuestas a través de:
apps/api/plane/app/urls/asset.py:50-56Los controladores vulnerables estaban en:
apps/api/plane/app/views/asset/v2.py:314apps/api/plane/app/views/asset/v2.py:379apps/api/plane/app/views/asset/v2.py:400apps/api/plane/app/views/asset/v2.py:409El problema era simple.
WorkspaceFileAssetEndpoint aceptaba un slug de espacio de trabajo y un UUID de activo, luego resolvía objetos directamente como:
workspace = Workspace.objects.get(slug=slug)
y:
asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug)
sin antes verificar que quien llamaba era realmente un miembro autorizado de ese espacio de trabajo destino.
Eso significaba que el endpoint aún podía:
para objetos de otro espacio de trabajo.
La ruta de duplicación de activos estaba mapeada a través de:
apps/api/plane/app/urls/asset.py:100-101La lógica vulnerable estaba en:
apps/api/plane/app/views/asset/v2.py:736-780El espacio de trabajo destino tenía un decorador de autorización. Pero la búsqueda del activo fuente no.
El objeto fuente se cargaba con:
original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first()
Eso significaba que quien llamaba solo necesitaba:
No había ninguna comprobación de que quien llamaba perteneciera al espacio de trabajo fuente que realmente poseía ese activo.
Ese es todo el segundo error.
La distinción importante es el impacto entre espacios de trabajo.
Muchos errores de autorización se minimizan como:
“aún requiere inicio de sesión”
Eso no capta el punto.
La verdadera pregunta no es:
“¿Quien llama está autenticado?”
La verdadera pregunta es:
“¿Quien llama está autorizado para el espacio de trabajo específico y el activo específico sobre el que se actúa?”
En Plane, la respuesta fue no.
Eso convierte lo que podría parecer un manejo ordinario de objetos en un problema real de seguridad multiinquilino.
Hay una clara diferencia entre:
Este problema era firmemente el segundo caso.
Validé el problema localmente contra Plane Community Edition 1.2.3 usando dos usuarios normales en dos espacios de trabajo no relacionados:
alpha-20260323072017bravo-20260323072017Usé Alpha para crear un activo privado legítimo subido en una incidencia de proyecto.
El ID del activo privado validado en mi ejecución fue:
6ed6ed62-d1b2-4399-8220-336c01b7d72c
Como Bravo, solicité:
GET /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
Plane respondió:
HTTP/1.1 302 Found
con una URL de descarga presignada para el activo de Alpha.
El hash del archivo descargado coincidió exactamente con el activo privado original de Alpha:
original: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
lectura no autorizada: 0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
Eso demostró que la ruta de lectura cruzó los límites del espacio de trabajo con éxito.
Como Bravo, luego solicité:
POST /api/assets/v2/workspaces/bravo-20260323072017/duplicate-assets/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
Plane respondió:
HTTP/1.1 200 OK
y creó un activo duplicado del lado del atacante:
72d51497-ccc1-4546-ba14-28fae5d37dbb
El SHA-256 del archivo duplicado coincidió exactamente con el activo original de Alpha:
0d4d070f550ba59ba6b30bee62343cf68ea221af7034f131f72f6409cf5a598e
Eso demostró que solo el UUID del activo fuente era suficiente para copiar contenido entre espacios de trabajo en un espacio de trabajo controlado por el atacante.
Como Bravo, luego envié:
DELETE /api/assets/v2/workspaces/alpha-20260323072017/6ed6ed62-d1b2-4399-8220-336c01b7d72c/
Plane respondió:
HTTP/1.1 204 No Content
Cuando Alpha recuperó ese activo más tarde, el servidor respondió:
HTTP/1.1 404 Not Found
Eso demostró un impacto en la integridad entre espacios de trabajo, no solo divulgación.
Como Bravo, creé un activo WORKSPACE_LOGO contra el espacio de trabajo de Alpha a través de la ruta vulnerable de activos a nivel de espacio de trabajo, subí contenido controlado por el atacante y lo finalicé.
Después de eso, los metadatos del espacio de trabajo de Alpha apuntaban al activo de logotipo controlado por el atacante:
c1032f06-3cf5-4f7e-b139-e6976d8c567d
El hash final del logotipo descargado coincidió exactamente con la carga útil del atacante:
esperado: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
observado: b9d1ef1de88d61bf55dd18055839bacacb832a752e17a7312547f641113d0e7b
Eso demostró una ruta visible de sobrescritura entre espacios de trabajo, no solo un problema de acceso oculto en el backend.
Cualquiera de los resultados anteriores ya habría sido suficiente para justificar un informe de error real.
Pero validar toda la cadena era importante por dos razones.
Mostró que el problema no se limitaba a exposición de solo lectura.
El mismo límite débil permitía:
Eso hace que el impacto sea mucho más fuerte que un IDOR estrecho de “puede obtener un archivo”.
Mostró que las dos rutas de código estaban relacionadas pero eran independientemente importantes.
Un fallo exponía directamente las operaciones de activos a nivel de espacio de trabajo. El segundo fallo convertía los UUIDs de activos subidos en una primitiva de exfiltración reutilizable a través de la duplicación.
Eso hizo que la historia de seguridad general fuera mucho más difícil de descartar.
El impacto de sobrescritura más visible que validé fue:
WORKSPACE_LOGOEso fue deliberado porque es fácil de verificar y demuestra una falla obvia de integridad entre inquilinos.
Pero el endpoint no se limitaba a logotipos de espacios de trabajo.
El flujo vulnerable de activos a nivel de espacio de trabajo también aceptaba múltiples contextos de entidad, incluyendo:
Eso importaba porque mostraba que el error era estructural, no vinculado a un solo campo de marca.
Validé la ruta del logotipo del espacio de trabajo directamente. La ruta de código más amplia sugería firmemente que contextos adicionales respaldados por activos estaban expuestos al mismo error de autorización.
Este problema fue clasificado razonablemente como Alto.
La clasificación del aviso fue:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:L
Esa clasificación tiene sentido.
La afirmación no es que un atacante no autenticado pueda comprometer Plane desde cero. La afirmación es que cualquier usuario autenticado normal puede cruzar los límites del inquilino en el subsistema de activos V2 y realizar operaciones de activos de alto impacto contra otros espacios de trabajo.
Esa es una vulnerabilidad de autorización multiinquilino real y defendible.
Algunas personas subestiman los errores autenticados entre inquilinos porque escuchan:
“el atacante ya necesitaba una cuenta”
Esa no es una defensa seria.
En software multi-espacio de trabajo, se supone que los usuarios autenticados normales están contenidos dentro de su propio ámbito de autorización.
Si un usuario con pocos privilegios en el espacio de trabajo Bravo puede leer, copiar, eliminar o sobrescribir objetos en el espacio de trabajo Alpha, entonces el aislamiento del espacio de trabajo está roto.
Esa es exactamente la propiedad de seguridad que se supone que la aplicación debe proteger.
Especialmente en una plataforma de gestión de proyectos que almacena contenido de trabajo interno y activos de marca, eso es un problema significativo con un impacto real en la confidencialidad e integridad.
El problema se corrigió en Plane v1.3.1.
Las notas de la versión v1.3.1 describían la corrección claramente:
@allow_permission a todos los métodos de WorkspaceFileAssetEndpointDuplicateAssetEndpoint a espacios de trabajo donde quien llama es un miembro activoEsa es la dirección de corrección correcta porque aborda ambas propiedades de seguridad fallidas:
Esto es exactamente lo que necesitaba este error.
Una buena corrección aquí no se trata de ocultar mejor los UUIDs. No se trata de cambiar la generación de URL presignadas.
Se trata de restaurar la regla correcta:
el slug del espacio de trabajo más el UUID del activo nunca deben ser suficientes sin una autorización limitada al usuario actual
Esa es la parte que el parche restauró.
Este problema se reportó de forma privada a través de GitHub Security Advisories.
El informe incluía:
El problema luego se publicó como:
El aviso se publicó el 15 de mayo de 2026. La corrección se envió en Plane v1.3.1.
La lección clave aquí es simple:
los subsistemas de activos compartidos son límites de autorización, no solo ayudas de almacenamiento
Muchos desarrolladores piensan en términos de:
Esas cosas son detalles de implementación.
La verdadera pregunta de seguridad es:
¿quién puede resolver, mutar, copiar o reenlazar ese activo a través de los límites del inquilino?
En Plane, ese límite no se aplicaba de manera consistente.
Esa es la verdadera conclusión.
Este error también refuerza algo importante sobre la revisión de aplicaciones multiinquilino:
Esta vulnerabilidad no trataba sobre un comportamiento de almacenamiento exótico.
Trataba de hacer la pregunta correcta sobre el límite de confianza.
En Plane, un usuario autenticado podía proporcionar el slug de otro espacio de trabajo y UUIDs de activos, y el subsistema de activos V2 confiaba en esos identificadores más de lo que debería.
Por eso se convirtió en CVE-2026-46558.
Corregido en Plane v1.3.1.