
Un recurso compartido público parecía limpio en el árbol de páginas, pero el endpoint de búsqueda contó una historia diferente. En Docmost, las páginas secundarias restringidas ocultas a los espectadores del recurso compartido público aún podían filtrarse a través de los resultados de búsqueda del recurso compartido público.
Un recurso compartido público se veía limpio en el árbol de páginas, pero el endpoint de búsqueda contaba una historia diferente. En Docmost, las páginas hijas restringidas ocultas a los visores de recursos compartidos públicos aún podían filtrarse a través de los resultados de búsqueda del recurso compartido público.
Encontré este problema mientras revisaba Docmost, una plataforma colaborativa de wiki y documentación de código abierto, con una pregunta muy simple en mente:
Si una página se oculta intencionalmente a un visor de un recurso compartido público, ¿cada funcionalidad pública respeta ese mismo límite de restricción?
En este caso, la respuesta fue no.
Una página hija restringida podía permanecer oculta en el árbol del recurso compartido público mientras se filtraba a través del endpoint de búsqueda del recurso compartido público.
El problema fue aceptado y se le asignó CVE-2026-33146.
Docmost: Docmost en GitHub
CVE: CVE-2026-33146
El sitio oficial de Docmost lo presenta como una wiki local preparada para empresas con más de 3 millones de descargas, y afirma que es utilizado por equipos de organizaciones como Vilnius City, Bechtle, el Gobierno Australiano, la Cruz Roja y ETS Quebec.
recurso compartido público principal con subpáginas habilitadas → descendiente restringido omitido del árbol público → atacante consulta la búsqueda del recurso compartido público → se filtran el título y el fragmento de la página hija restringida
Docmost es una plataforma colaborativa de wiki y documentación.
Proporciona:
Eso significa que su modelo de recurso compartido público es un límite de seguridad real.
La pregunta importante aquí no era si Docmost puede compartir páginas públicamente.
La pregunta real era:
Cuando Docmost decide que una página descendiente está restringida y no debería aparecer a un visitante del recurso compartido público, ¿esa restricción se mantiene en todas partes del flujo del recurso compartido público?
En este caso, no fue así.
Muchas revisiones de seguridad se detienen demasiado pronto cuando ven una página oculta en la interfaz de usuario.
Eso no es suficiente.
La pregunta más sólida es esta:
¿Cada ruta del backend aplica la misma decisión de visibilidad?
Esto importa porque los límites de seguridad no se definen por cómo se ve la interfaz.
Se definen por lo que el servidor realmente devuelve.
Aquí, el endpoint del árbol público se comportó de manera segura:
Pero la ruta de búsqueda del recurso compartido público se comportó de manera diferente:
Eso convirtió esto en un problema real de autorización y divulgación de información, no solo en una inconsistencia de presentación.
No abordé Docmost fuzzeando rutas al azar y esperando que apareciera algo interesante.
El camino más sólido fue elegir primero un límite de confianza.
Para aplicaciones que admiten:
una de las mejores preguntas que hacer es:
¿La capa de búsqueda aplica exactamente el mismo límite de autorización que la capa de navegación?
Esa pregunta se vuelve especialmente valiosa cuando:
Aquí es exactamente donde apareció este problema.
El error no fue que Docmost no ocultara la página restringida en el árbol público normal.
El error fue que la búsqueda pública no respetaba esa misma lógica de restricción.
Según la revisión del código fuente, el flujo del árbol público utilizaba un recorrido de descendientes consciente de restricciones.
Área relevante:
apps/server/src/core/share/share.service.tsEsa ruta excluía intencionalmente a los descendientes restringidos usando:
getPageAndDescendantsExcludingRestricted(...)Pero el flujo de búsqueda del recurso compartido público seguía una ruta diferente.
Áreas relevantes:
apps/server/src/core/search/search.controller.tsapps/server/src/core/search/search.service.tsAllí, el código recopilaba descendientes usando:
getPageAndDescendants(...)Eso significaba que los descendientes restringidos permanecían en el ámbito de la búsqueda.
En el contexto del recurso compartido público, eso importa mucho porque la rama de búsqueda se ejecuta sin un contexto de permisos de usuario autenticado normal. Por lo tanto, una vez que los descendientes restringidos se incluían en el conjunto de páginas buscables, sus metadatos podían filtrarse a través de la respuesta.
Porque el atacante no necesita una cuenta autenticada.
Solo necesita:
Una vez que existe esa condición, un visitante público puede consultar el endpoint de búsqueda del recurso compartido y recuperar:
Eso es suficiente para crear una fuga de confidencialidad, incluso si no se devuelve el cuerpo completo de la página.
La distinción importante es que la aplicación ya señala claramente el modelo de seguridad previsto.
El endpoint del árbol público oculta a los descendientes restringidos.
Por lo tanto, la pregunta real no es:
"¿La búsqueda devuelve un conjunto de resultados más amplio?"
La pregunta real es:
"¿La búsqueda está violando una decisión de autorización ya aplicada en otro lugar para el mismo límite de recurso compartido público?"
En Docmost, lo estaba.
Eso convierte esto de:
en:
Por eso es una vulnerabilidad real.
Validé el problema comparando los dos endpoints públicos relevantes uno al lado del otro.
Primero, probé el endpoint normal del árbol público usando la clave del recurso compartido público.
Ejemplo de solicitud:
POST /api/shares/tree HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key"
}
La respuesta devolvió solo la página hija pública en el árbol de páginas.
Resultado representativo:
{
"pageTree": [
{
"id": "public-child",
"title": "Public roadmap"
}
]
}
Eso estableció el comportamiento esperado del producto:
Luego consulté el endpoint de búsqueda del recurso compartido público usando un término que aparecía dentro del descendiente restringido.
Ejemplo de solicitud:
POST /api/search/share-search HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key",
"query": "salary"
}
La respuesta incluía igualmente la página hija restringida:
{
"items": [
{
"id": "public-child",
"title": "Public roadmap",
"highlight": "release plan and milestones"
},
{
"id": "restricted-child",
"title": "Payroll Q4",
"highlight": "salary bands and bonus targets"
}
]
}
Eso demostró la afirmación central:
La parte más sólida de este problema no es la segunda solicitud por sí sola.
Es el contraste entre los dos endpoints.
Muestra que el producto ya tiene un modelo de restricción previsto para los recursos compartidos públicos.
El descendiente restringido no debería ser visible para el visitante público.
Demuestra que la ruta de búsqueda rompe ese mismo límite.
Eso hace que sea más difícil descartar el problema como un comportamiento de búsqueda esperado o una brecha de documentación.
La aplicación misma establece la regla a través de la respuesta del árbol, y luego la viola a través de la respuesta de búsqueda.
Eso es evidencia contundente.
Este problema no expone contenido arbitrario en todo el espacio de trabajo.
Su alcance es más limitado que eso.
Pero dentro del subárbol del recurso compartido público afectado, aún proporciona a un atacante conocimiento no autorizado útil:
Incluso fragmentos cortos pueden importar.
Un título como:
ya crea valor de seguridad para un atacante.
Por lo tanto, aunque esto finalmente se clasificó como Moderado, sigue siendo un problema de confidencialidad válido con una ruptura de límites limpia y defendible.
A este problema se le asignó:
La gravedad del aviso fue:
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:NEsa puntuación refleja una fuga de confidencialidad más limitada, no un acceso completo no autorizado a documentos.
El punto importante es que el problema sigue siendo válido.
La afirmación aquí no es:
La afirmación es:
Eso es una divulgación de información relacionada con la autorización real.
Algunas personas descartan las fugas de metadatos demasiado rápido.
Eso es un error.
La verdadera pregunta es si los datos filtrados cruzan un límite previsto.
Aquí, así fue.
Si la aplicación dice:
pero un endpoint público aún revela:
entonces el modelo de confidencialidad ha fallado, incluso si el impacto es limitado.
Eso hace que valga la pena reportarlo.
Errores limpios, acotados y reproducibles como este son exactamente el tipo de problemas que ayudan a demostrar un buen criterio en las revisiones de seguridad.
La dirección de corrección más segura es hacer que la búsqueda pública use la misma lógica de descendientes consciente de restricciones que el flujo del árbol público.
En la práctica, eso significa que la rama de búsqueda del recurso compartido no debería enumerar descendientes con:
getPageAndDescendants(...)
Debería alinearse con el recorrido más seguro del recurso compartido público y usar:
getPageAndDescendantsExcludingRestricted(...)
Una corrección alternativa sería mantener la enumeración más amplia y luego filtrar explícitamente los descendientes restringidos antes de que la consulta de búsqueda devuelva resultados.
Pero el diseño más limpio es simple:
el límite de búsqueda debería coincidir con el límite de navegación
Esa es la propiedad de seguridad que falló.
Este problema se reportó de forma privada a través del flujo de reportes de seguridad de GitHub.
El informe mostraba:
/api/shares/tree/api/search/share-searchEl problema fue aceptado y se le asignó:
CVE-2026-33146
La gravedad final del aviso fue Moderada, lo que se ajusta mejor al alcance de fuga más limitado de lo que habría sido una afirmación de criticidad más amplia.
Eso no debilita la validez del hallazgo.
Simplemente define su impacto con mayor precisión.
La lección clave aquí es simple:
ocultar algo en un endpoint público no es suficiente si otro endpoint público aún lo revela.
Muchos desarrolladores piensan en la autorización solo en la ruta de representación obvia:
Pero el límite real es más amplio.
También tienes que preguntar:
En Docmost, la respuesta fue no.
Esa es la verdadera conclusión.
Esta vulnerabilidad no trataba de payloads llamativos ni cadenas de exploit complejas.
Se trataba de hacer una pregunta práctica sobre el límite de confianza.
Docmost ocultó la página restringida en un lugar.
Luego la filtró en otro.
Por eso se convirtió en CVE-2026-33146.