
Un partage public semblait propre dans l'arborescence de pages, mais le point de terminaison de recherche racontait une histoire différente. Dans Docmost, les pages enfants restreintes, cachées des spectateurs du partage public, pouvaient encore fuir à travers les résultats de recherche du partage public.
Un partage public semblait propre dans l'arborescence des pages, mais le point de terminaison de recherche racontait une histoire différente. Dans Docmost, les pages enfants restreintes, masquées aux visiteurs du partage public, pouvaient tout de même fuiter via les résultats de recherche du partage public.
J'ai découvert ce problème en examinant Docmost, une plateforme wiki et de documentation collaborative open-source, avec une question très simple en tête :
Si une page est intentionnellement masquée à un visiteur de partage public, est-ce que toutes les fonctionnalités publiques respectent cette même restriction ?
Dans ce cas, la réponse était non.
Une page enfant restreinte pouvait rester cachée dans l'arborescence du partage public tout en fuitant via le point de terminaison de recherche du partage public.
Le problème a été accepté et a reçu l'identifiant CVE-2026-33146.
Docmost : Docmost sur GitHub
CVE : CVE-2026-33146
Le site officiel de Docmost le présente comme un wiki sur site prêt pour l'entreprise avec plus de 3 millions de téléchargements, et indique qu'il est utilisé par des équipes d'organisations comme Vilnius City, Bechtle, le gouvernement australien, la Croix-Rouge et ETS Québec.
partage parent public avec sous-pages activées → descendant restreint omis de l'arborescence publique → attaquant interroge la recherche du partage public → titre et extrait de l'enfant restreint fuient
Docmost est une plateforme wiki et de documentation collaborative.
Elle propose :
Cela signifie que son modèle de partage public constitue une véritable frontière de sécurité.
La question importante ici n'était pas de savoir si Docmost peut partager des pages publiquement.
La vraie question était :
Lorsque Docmost décide qu'une page descendante est restreinte et ne doit pas apparaître à un visiteur du partage public, cette restriction est-elle appliquée partout dans le flux de partage public ?
Dans ce cas, elle ne l'était pas.
De nombreuses analyses de sécurité s'arrêtent trop tôt dès qu'elles constatent qu'une page est masquée dans l'interface utilisateur.
Ce n'est pas suffisant.
La question plus forte est celle-ci :
Est-ce que chaque chemin côté serveur applique la même décision de visibilité ?
C'est important car les frontières de sécurité ne sont pas définies par l'apparence de l'interface.
Elles sont définies par ce que le serveur renvoie réellement.
Ici, le point de terminaison de l'arborescence publique se comportait correctement :
Mais le chemin de recherche du partage public se comportait différemment :
Cela en faisait un véritable problème d'autorisation et de divulgation d'informations, et pas seulement une incohérence d'affichage.
Je n'ai pas abordé Docmost en effectuant des tests aléatoires sur les routes en espérant que quelque chose d'intéressant apparaisse.
La meilleure approche était de choisir d'abord une frontière de confiance.
Pour les applications qui prennent en charge :
l'une des meilleures questions à se poser est :
La couche de recherche applique-t-elle exactement la même frontière d'autorisation que la couche de navigation ?
Cette question devient particulièrement pertinente lorsque :
C'est exactement là où ce problème est apparu.
Le bug n'était pas que Docmost n'ait pas masqué la page restreinte dans l'arborescence publique normale.
Le bug était que la recherche publique n'honorait pas cette même logique de restriction.
D'après la revue du code source, le flux de l'arborescence publique utilisait une traversée des descendants tenant compte des restrictions.
Zone concernée :
apps/server/src/core/share/share.service.tsCe chemin excluait intentionnellement les descendants restreints en utilisant :
getPageAndDescendantsExcludingRestricted(...)Mais le flux de recherche du partage public suivait un chemin différent.
Zones concernées :
apps/server/src/core/search/search.controller.tsapps/server/src/core/search/search.service.tsLà, le code collectait les descendants en utilisant :
getPageAndDescendants(...)Cela signifiait que les descendants restreints restaient dans le périmètre de la recherche.
Dans le contexte du partage public, cela a une grande importance car la branche de recherche s'exécute sans le contexte de permissions d'un utilisateur authentifié normal. Ainsi, une fois que les descendants restreints étaient inclus dans l'ensemble des pages consultables, leurs métadonnées pouvaient fuiter via la réponse.
Parce que l'attaquant n'a pas besoin d'un compte authentifié.
Il a seulement besoin :
Une fois cette condition réunie, un visiteur public peut interroger le point de terminaison de recherche du partage et récupérer :
C'est suffisant pour créer une fuite de confidentialité, même si le corps complet de la page n'est pas renvoyé.
La distinction importante est que l'application signale déjà clairement le modèle de sécurité prévu.
Le point de terminaison de l'arborescence publique masque les descendants restreints.
Donc la vraie question n'est pas :
« Est-ce que la recherche renvoie par hasard un ensemble de résultats plus large ? »
La vraie question est :
« Est-ce que la recherche viole une décision d'autorisation déjà appliquée ailleurs pour la même frontière de partage public ? »
Dans Docmost, c'était le cas.
Cela transforme ce problème :
en :
C'est pourquoi il s'agit d'une véritable vulnérabilité.
J'ai validé le problème en comparant les deux points de terminaison publics concernés côte à côte.
D'abord, j'ai testé le point de terminaison normal de l'arborescence publique en utilisant la clé de partage public.
Exemple de requête :
POST /api/shares/tree HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key"
}
La réponse renvoyait uniquement la page enfant publique dans l'arborescence.
Résultat représentatif :
{
"pageTree": [
{
"id": "public-child",
"title": "Public roadmap"
}
]
}
Cela établissait le comportement attendu du produit :
J'ai ensuite interrogé le point de terminaison de recherche du partage public avec un terme apparaissant dans le descendant restreint.
Exemple de requête :
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 réponse incluait tout de même l'enfant restreint :
{
"items": [
{
"id": "public-child",
"title": "Public roadmap",
"highlight": "release plan and milestones"
},
{
"id": "restricted-child",
"title": "Payroll Q4",
"highlight": "salary bands and bonus targets"
}
]
}
Cela prouvait l'affirmation centrale :
La partie la plus forte de ce problème n'est pas la deuxième requête en elle-même.
C'est le contraste entre les deux points de terminaison.
Cela montre que le produit a déjà un modèle de restriction intentionnel pour les partages publics.
Le descendant restreint n'est pas censé être visible pour le visiteur public.
Cela prouve que le chemin de recherche brise exactement cette même frontière.
Cela rend le problème plus difficile à écarter comme un comportement de recherche attendu ou une lacune documentaire.
L'application elle-même établit la règle via la réponse de l'arborescence, puis la viole via la réponse de la recherche.
C'est une preuve solide.
Ce problème n'expose pas un contenu arbitraire dans tout l'espace de travail.
Son périmètre est plus restreint.
Mais dans la sous-arborescence du partage public concernée, il donne quand même à un attaquant des connaissances non autorisées utiles :
Même de courts extraits peuvent être importants.
Un titre comme :
crée déjà une valeur de sécurité pour un attaquant.
Donc, bien que cela ait finalement été classé comme Modérée, il s'agit toujours d'un problème de confidentialité valide avec une rupture de frontière claire et défendable.
Ce problème a reçu :
La sévérité de l'avis était :
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:NCe score reflète une fuite de confidentialité plus restreinte plutôt qu'un accès complet non autorisé aux documents.
Le point important est que le problème reste valide.
L'affirmation ici n'est pas :
L'affirmation est :
C'est une véritable divulgation d'informations liée à l'autorisation.
Certaines personnes rejettent trop rapidement les fuites de métadonnées.
C'est une erreur.
La vraie question est de savoir si les données divulguées franchissent une frontière prévue.
Ici, c'était le cas.
Si l'application dit :
mais qu'un point de terminaison public révèle quand même :
alors le modèle de confidentialité a échoué, même si l'impact est limité.
Cela mérite donc d'être signalé.
Des bugs propres, précis et reproductibles comme celui-ci sont exactement le genre de problèmes qui aident à démontrer un bon jugement en matière de revue de sécurité.
La direction de correction la plus sûre est de faire en sorte que la recherche publique utilise la même logique de descendants tenant compte des restrictions que le flux de l'arborescence publique.
En pratique, cela signifie que la branche de recherche du partage ne devrait pas énumérer les descendants avec :
getPageAndDescendants(...)
Elle devrait plutôt s'aligner sur la traversée plus sûre du partage public et utiliser :
getPageAndDescendantsExcludingRestricted(...)
Une correction alternative consisterait à conserver l'énumération plus large, puis à filtrer explicitement les descendants restreints avant que la requête de recherche ne renvoie les résultats.
Mais la conception la plus propre est simple :
la frontière de recherche doit correspondre à la frontière de navigation
C'est la propriété de sécurité qui a échoué.
Ce problème a été signalé de manière privée via le flux de signalement de sécurité de GitHub.
Le rapport montrait :
/api/shares/tree/api/search/share-searchLe problème a été accepté et a reçu :
CVE-2026-33146
La sévérité finale de l'avis était Modérée, ce qui correspond mieux au périmètre de fuite plus restreint qu'une revendication de criticité plus large ne l'aurait fait.
Cela n'affaiblit pas la validité de la découverte.
Cela définit simplement son impact de manière plus précise.
La leçon clé ici est simple :
masquer quelque chose dans un point de terminaison public ne suffit pas si un autre point de terminaison public le révèle encore.
Beaucoup de développeurs ne pensent à l'autorisation que dans le chemin de rendu évident :
Mais la véritable frontière est plus large.
Vous devez aussi vous demander :
Dans Docmost, la réponse était non.
C'est le véritable enseignement.
Cette vulnérabilité ne concernait pas des payloads flashy ou des chaînes d'exploitation complexes.
Il s'agissait de poser une question très pratique sur les frontières de confiance.
Docmost masquait la page restreinte à un endroit.
Puis la fuyait à un autre.
C'est pourquoi cela est devenu CVE-2026-33146.