
Uma partilha pública parecia limpa na árvore de páginas, mas o endpoint de busca contou uma história diferente. No Docmost, páginas filhas restringidas, ocultas dos visualizadores de partilha pública, ainda podiam ser expostas através dos resultados de busca da partilha pública.
Um compartilhamento público parecia limpo na árvore de páginas, mas o endpoint de busca contava uma história diferente. No Docmost, páginas filhas restritas, ocultas dos visualizadores de compartilhamento públicos, ainda podiam vazar por meio dos resultados de busca do compartilhamento público.
Encontrei esse problema ao revisar o Docmost, uma plataforma colaborativa de wiki e documentação de código aberto, com uma pergunta muito simples em mente:
Se uma página é intencionalmente ocultada de um visualizador de compartilhamento público, todos os recursos públicos respeitam essa mesma restrição de limite?
Neste caso, a resposta foi não.
Uma página filha restrita podia permanecer oculta na árvore de compartilhamento público enquanto ainda vazava pelo endpoint de busca do compartilhamento público.
O problema foi aceito e recebeu CVE-2026-33146.
Docmost: Docmost no GitHub
CVE: CVE-2026-33146
O site oficial do Docmost o apresenta como uma wiki local pronta para empresas, com mais de 3 milhões de downloads, e afirma que é confiável por equipes em organizações como Vilnius City, Bechtle, o Governo Australiano, a Cruz Vermelha e ETS Quebec.
compartilhamento público pai com subpáginas ativadas → descendente restrito omitido da árvore pública → atacante consulta busca do compartilhamento público → título e trecho da filha restrita vazam
Docmost é uma plataforma colaborativa de wiki e documentação.
Ela oferece:
Isso significa que seu modelo de compartilhamento público é uma fronteira de segurança real.
A questão importante aqui não era se o Docmost pode compartilhar páginas publicamente.
A verdadeira questão era:
Quando o Docmost decide que uma página descendente é restrita e não deve aparecer para um visitante de compartilhamento público, essa restrição é respeitada em todos os lugares do fluxo de compartilhamento público?
Neste caso, não foi.
Muitas revisões de segurança param cedo demais ao ver uma página oculta na interface.
Isso não é suficiente.
A questão mais forte é esta:
Todo caminho de backend impõe a mesma decisão de visibilidade?
Isso importa porque os limites de segurança não são definidos pela aparência da interface.
Eles são definidos pelo que o servidor realmente retorna.
Aqui, o endpoint da árvore pública se comportou de forma segura:
Mas o caminho de busca do compartilhamento público se comportou de forma diferente:
Isso tornou essa uma questão real de autorização e divulgação de informações, não apenas uma inconsistência de apresentação.
Não abordei o Docmost testando rotas aleatoriamente na esperança de algo interessante aparecer.
O caminho mais forte foi escolher um limite de confiança primeiro.
Para aplicações que suportam:
uma das melhores perguntas a fazer é:
A camada de busca aplica exatamente o mesmo limite de autorização que a camada de navegação?
Essa pergunta se torna especialmente valiosa quando:
Foi exatamente aí que esse problema apareceu.
O bug não foi que o Docmost falhou em ocultar a página restrita na árvore pública normal.
O bug foi que a busca pública não honrava essa mesma lógica de restrição.
Da revisão do código-fonte, o fluxo da árvore pública usava travessia de descendentes com consciência de restrição.
Área relevante:
apps/server/src/core/share/share.service.tsEsse caminho intencionalmente excluía descendentes restritos usando:
getPageAndDescendantsExcludingRestricted(...)Mas o fluxo de busca do compartilhamento público seguia um caminho diferente.
Áreas relevantes:
apps/server/src/core/search/search.controller.tsapps/server/src/core/search/search.service.tsLá, o código coletava descendentes usando:
getPageAndDescendants(...)Isso significava que descendentes restritos permaneciam no escopo para a busca.
No contexto de compartilhamento público, isso importa muito porque o ramo de busca é executado sem um contexto de permissão de usuário autenticado normal. Então, uma vez que descendentes restritos eram incluídos no conjunto de páginas buscáveis, seus metadados podiam vazar através da resposta.
Porque o atacante não precisa de uma conta autenticada.
Ele precisa apenas:
Uma vez que essa condição existe, um visitante público pode consultar o endpoint de busca do compartilhamento e recuperar:
Isso é suficiente para criar um vazamento de confidencialidade, mesmo que o corpo completo da página não seja retornado.
A distinção importante é que a aplicação já sinaliza o modelo de segurança pretendido claramente.
O endpoint da árvore pública oculta descendentes restritos.
Portanto, a verdadeira pergunta não é:
“A busca por acaso retorna um conjunto de resultados mais amplo?”
A verdadeira pergunta é:
“A busca está violando uma decisão de autorização já aplicada em outro lugar para o mesmo limite de compartilhamento público?”
No Docmost, estava.
Isso transforma isso de:
em:
Por isso isso é uma vulnerabilidade real.
Validei o problema comparando os dois endpoints públicos relevantes lado a lado.
Primeiro, testei o endpoint normal da árvore pública usando a chave de compartilhamento público.
Exemplo de requisição:
POST /api/shares/tree HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key"
}
A resposta retornou apenas a página filha pública na árvore de páginas.
Resultado representativo:
{
"pageTree": [
{
"id": "public-child",
"title": "Public roadmap"
}
]
}
Isso estabeleceu o comportamento esperado do produto:
Então consultei o endpoint de busca do compartilhamento público usando um termo que aparecia dentro do descendente restrito.
Exemplo de requisição:
POST /api/search/share-search HTTP/1.1
Host: 127.0.0.1:6752
Content-Type: application/json
{
"shareId": "public-share-key",
"query": "salary"
}
A resposta incluiu a filha restrita mesmo assim:
{
"items": [
{
"id": "public-child",
"title": "Public roadmap",
"highlight": "release plan and milestones"
},
{
"id": "restricted-child",
"title": "Payroll Q4",
"highlight": "salary bands and bonus targets"
}
]
}
Isso provou a afirmação central:
A parte mais forte desse problema não é a segunda requisição por si só.
É o contraste entre os dois endpoints.
Mostra que o produto já tem um modelo de restrição pretendido para compartilhamentos públicos.
O descendente restrito não deveria ser visível para o visitante público.
Prova que o caminho de busca quebra exatamente esse mesmo limite.
Isso torna o problema mais difícil de ser descartado como comportamento de busca esperado ou lacuna de documentação.
A própria aplicação estabelece a regra através da resposta da árvore, depois a viola através da resposta da busca.
Isso é uma evidência poderosa.
Este problema não expõe conteúdo arbitrário em todo o workspace.
Seu escopo é mais restrito do que isso.
Mas dentro da subárvore de compartilhamento público afetada, ainda dá a um atacante conhecimento não autorizado útil:
Mesmo trechos curtos podem importar.
Um título como:
já cria valor de segurança para um atacante.
Portanto, embora tenha sido classificado como Moderado, ainda é um problema válido de confidencialidade com uma quebra de limite limpa e defensável.
Este problema recebeu:
A gravidade do aviso foi:
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:NEssa pontuação reflete um vazamento de confidencialidade mais restrito, em vez de acesso total não autorizado a documentos.
O ponto importante é que o problema ainda é válido.
A alegação aqui não é:
A alegação é:
Isso é uma divulgação real de informações relacionadas à autorização.
Algumas pessoas descartam vazamentos de metadados rápido demais.
Isso é um erro.
A verdadeira questão é se os dados vazados cruzam um limite pretendido.
Aqui, cruzaram.
Se a aplicação diz:
mas um endpoint público ainda revela:
então o modelo de confidencialidade falhou, mesmo que o impacto seja limitado.
Isso torna o relato válido.
Bugs limpos, escopados e reproduzíveis como este são exatamente o tipo de problema que ajuda a demonstrar um bom julgamento em revisão de segurança.
A direção de correção mais segura é fazer a busca pública usar a mesma lógica de descendentes com consciência de restrição que o fluxo da árvore pública.
Na prática, isso significa que o ramo de busca do compartilhamento não deve enumerar descendentes com:
getPageAndDescendants(...)
Ele deve se alinhar com a travessia mais segura de compartilhamento público e usar:
getPageAndDescendantsExcludingRestricted(...)
Uma correção alternativa seria manter a enumeração mais ampla e então filtrar explicitamente os descendentes restritos antes que a consulta de busca retorne resultados.
Mas o design mais limpo é simples:
o limite da busca deve corresponder ao limite da navegação
Essa foi a propriedade de segurança que falhou.
Este problema foi reportado privadamente através do fluxo de relatórios de segurança do GitHub.
O relatório mostrou:
/api/shares/tree/api/search/share-searchO problema foi aceito e recebeu:
CVE-2026-33146
A gravidade final do aviso foi Moderada, o que se encaixa melhor no escopo mais restrito de vazamento do que uma alegação de criticidade mais ampla teria.
Isso não enfraquece a validade da descoberta.
Apenas define seu impacto de forma mais precisa.
A lição principal aqui é simples:
esconder algo em um endpoint público não é suficiente se outro endpoint público ainda o revela.
Muitos desenvolvedores pensam em autorização apenas no caminho de renderização óbvio:
Mas o verdadeiro limite é mais amplo.
Você também precisa perguntar:
No Docmost, a resposta foi não.
Essa é a verdadeira conclusão.
Esta vulnerabilidade não foi sobre payloads chamativos ou cadeias de exploração complexas.
Foi sobre fazer uma pergunta prática de limite de confiança.
O Docmost escondeu a página restrita em um lugar.
Depois a vazou em outro.
É por isso que isso se tornou CVE-2026-33146.