Share
## https://sploitus.com/exploit?id=PACKETSTORM:224390
# CVE-2026-33146
    A public share looked clean in the page tree, but the search endpoint told a different story. In Docmost, restricted child pages hidden from public share viewers could still leak through public share search results.
    
    ## Intro
    
    I found this issue while reviewing **Docmost**, an open-source collaborative wiki and documentation platform, with a very simple question in mind:
    
    **If a page is intentionally hidden from a public share viewer, does every public feature respect that same restriction boundary?**
    
    In this case, the answer was no.
    
    A restricted child page could stay hidden in the public share tree while still leaking through the public share search endpoint.
    
    The issue was accepted and assigned **CVE-2026-33146**.
    
    **Docmost:** [Docmost on GitHub](https://github.com/docmost/docmost)  
    **CVE:** CVE-2026-33146
    
    Docmostโ€™s official site presents it as an enterprise-ready on-premises wiki with **3M+ downloads**, and says it is trusted by teams at organizations including **Vilnius City**, **Bechtle**, the **Australian Government**, the **Red Cross**, and **ETS Quebec**.
    
    <img width="840" height="560" alt="photo0" src="https://github.com/user-attachments/assets/0f153c00-d467-40bd-8bd1-5560e568f1fa" />
    
    ---
    
    ## Attack Chain
    
    `public parent share with subpages enabled โ†’ restricted descendant omitted from public tree โ†’ attacker queries public share search โ†’ restricted child title and snippet leak`
    
    ---
    
    ## What Docmost Does
    
    **Docmost** is a collaborative wiki and documentation platform.
    
    It provides:
    - shared pages
    - public share links
    - nested page trees
    - workspace and space-level content organization
    - search across shared content
    
    That means its public-sharing model is a real security boundary.
    
    The important question here was not whether Docmost can share pages publicly.
    
    The real question was:
    
    **When Docmost decides a descendant page is restricted and should not appear to a public share visitor, does that restriction hold everywhere in the public share flow?**
    
    In this case, it did not.
    
    ---
    
    ## Why This Bug Was Worth Looking At
    
    A lot of security reviews stop too early once they see a page hidden in the UI.
    
    That is not enough.
    
    The stronger question is this:
    
    **Does every backend path enforce the same visibility decision?**
    
    That matters because security boundaries are not defined by what the interface looks like.  
    They are defined by what the server actually returns.
    
    Here, the public tree endpoint behaved safely:
    - restricted descendants were hidden
    
    But the public share search path behaved differently:
    - restricted descendants still influenced results
    - their titles leaked
    - their highlighted content snippets leaked
    
    That made this a real authorization and information disclosure issue, not just a presentation inconsistency.
    
    ---
    
    ## The Boundary I Focused On
    
    I did not approach Docmost by randomly fuzzing routes and hoping something interesting appeared.
    
    The stronger path was to choose a trust boundary first.
    
    For applications that support:
    - public sharing
    - nested objects
    - per-page restrictions
    - content search
    
    one of the best questions to ask is:
    
    > **Does the search layer enforce the exact same authorization boundary as the browse layer?**
    
    That question becomes especially valuable when:
    - a parent object is public
    - descendants have different visibility rules
    - search is implemented through a separate service path
    
    That is exactly where this issue showed up.
    
    ---
    
    ## Root Cause
    
    The bug was not that Docmost failed to hide the restricted page in the normal public tree.
    
    The bug was that **public search did not honor that same restriction logic**.
    
    From source review, the public tree flow used restricted-aware descendant traversal.
    
    Relevant area:
    - `apps/server/src/core/share/share.service.ts`
    
    That path intentionally excluded restricted descendants by using:
    
    - `getPageAndDescendantsExcludingRestricted(...)`
    
    But the public share search flow followed a different path.
    
    Relevant areas:
    - `apps/server/src/core/search/search.controller.ts`
    - `apps/server/src/core/search/search.service.ts`
    
    There, the code collected descendants using:
    
    - `getPageAndDescendants(...)`
    
    That meant restricted descendants remained in scope for search.
    
    In the public-share context, that matters a lot because the search branch runs without a normal authenticated user permission context. So once restricted descendants were included in the searchable page set, their metadata could leak through the response.
    
    ### Why this is exploitable
    
    Because the attacker does not need an authenticated account.
    
    They only need:
    - a valid public share key
    - subpages included in the share
    - knowledge of, or guesses about, search terms likely to appear in hidden descendants
    
    Once that condition exists, a public visitor can query the share-search endpoint and recover:
    - hidden page titles
    - highlighted body snippets
    - proof that a restricted child page exists under the shared parent
    
    That is enough to create a confidentiality leak, even if the full page body is not returned.
    
    ---
    
    ## What Makes This a Security Issue, Not Just Different Endpoint Behavior
    
    The important distinction is that the application already signals the intended security model clearly.
    
    The public tree endpoint hides restricted descendants.
    
    So the real question is not:
    
    > โ€œDoes search happen to return a broader result set?โ€
    
    The real question is:
    
    > โ€œIs search violating an authorization decision already enforced elsewhere for the same public share boundary?โ€
    
    In Docmost, it was.
    
    That turns this from:
    - inconsistent functionality
    
    into:
    - inconsistent access control enforcement
    
    That is why this is a real vulnerability.
    
    ---
    
    ## PoC
    
    I validated the issue by comparing the two relevant public endpoints side by side.
    
    ### Case 1: Public tree correctly hides the restricted child
    
    First, I tested the normal public tree endpoint using the public share key.
    
    Example request:
    
    ```http
    POST /api/shares/tree HTTP/1.1
    Host: 127.0.0.1:6752
    Content-Type: application/json
    
    {
      "shareId": "public-share-key"
    }
    ```
    
    The response returned only the public child page in the page tree.
    
    Representative result:
    
    ```json
    {
      "pageTree": [
        {
          "id": "public-child",
          "title": "Public roadmap"
        }
      ]
    }
    ```
    
    That established the expected product behavior:
    - the restricted child was intentionally hidden from the public visitor
    
    ---
    
    ### Case 2: Public share search still leaks the restricted child
    
    I then queried the public share search endpoint using a term that appeared inside the restricted descendant.
    
    Example request:
    
    ```http
    POST /api/search/share-search HTTP/1.1
    Host: 127.0.0.1:6752
    Content-Type: application/json
    
    {
      "shareId": "public-share-key",
      "query": "salary"
    }
    ```
    
    The response included the restricted child anyway:
    
    ```json
    {
      "items": [
        {
          "id": "public-child",
          "title": "Public roadmap",
          "highlight": "release plan and milestones"
        },
        {
          "id": "restricted-child",
          "title": "Payroll Q4",
          "highlight": "salary bands and bonus targets"
        }
      ]
    }
    ```
    
    That proved the core claim:
    - the restricted child was hidden in the public tree
    - but still leaked through public share search
    
    ---
    
    ## Why the Two Reproductions Matter
    
    The strongest part of this issue is not the second request by itself.
    
    It is the contrast between the two endpoints.
    
    ### First
    
    It shows the product already has an intended restriction model for public shares.
    
    The restricted descendant is not supposed to be visible to the public visitor.
    
    ### Second
    
    It proves the search path breaks that exact same boundary.
    
    That makes the issue harder to dismiss as expected search behavior or a documentation gap.
    
    The application itself establishes the rule through the tree response, then violates it through the search response.
    
    That is powerful evidence.
    
    ---
    
    ## What the Leak Actually Gives an Attacker
    
    This issue does not expose arbitrary content across the whole workspace.
    
    Its scope is narrower than that.
    
    But within the affected public share subtree, it still gives an attacker useful unauthorized knowledge:
    - hidden document titles
    - highlighted snippets from hidden content
    - confirmation that restricted descendants exist
    - clues about payroll, legal, planning, credentials, or internal operations depending on document content
    
    Even short snippets can matter.
    
    A title like:
    - payroll
    - hiring plan
    - legal draft
    - customer incident
    - credential rotation
    
    already creates security value for an attacker.
    
    So while this was ultimately classified as **Moderate**, it is still a valid confidentiality issue with a clean and defensible boundary break.
    
    ---
    
    ## Severity and Classification
    
    This issue was assigned:
    
    - **CVE-2026-33146**
    
    The advisory severity was:
    
    - **Moderate**
    - **CVSS:** `CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:N/A:N`
    
    That scoring reflects a narrower confidentiality leak rather than full unauthorized document access.
    
    The important point is that the issue is still valid.
    
    The claim here is not:
    - full page read
    - arbitrary workspace disclosure
    - integrity impact
    - availability impact
    
    The claim is:
    
    - a public share visitor can retrieve metadata from a restricted child page that the product intentionally hides elsewhere in the same public-share flow
    
    That is a real authorization-related information disclosure.
    
    ---
    
    ## Why This Was Still Worth Reporting
    
    Some people dismiss metadata leaks too quickly.
    
    That is a mistake.
    
    The real question is whether the leaked data crosses an intended boundary.
    
    Here, it did.
    
    If the application says:
    - this restricted child should not be visible to public share viewers
    
    but a public endpoint still reveals:
    - its title
    - part of its content
    
    then the confidentiality model has failed, even if the impact is limited.
    
    That makes it worth reporting.
    
    Clean, scoped, and reproducible bugs like this are exactly the kind of issues that help demonstrate strong security review judgment.
    
    ---
    
    ## Fix Analysis
    
    The safest fix direction is to make public search use the same restriction-aware descendant logic as the public tree flow.
    
    In practice, that means the share-search branch should not enumerate descendants with:
    
    ```text
    getPageAndDescendants(...)
    ```
    
    It should instead align with the safer public-share traversal and use:
    
    ```text
    getPageAndDescendantsExcludingRestricted(...)
    ```
    
    An alternative fix would be to keep the broader enumeration and then explicitly filter restricted descendants out before the search query returns results.
    
    But the cleaner design is simple:
    
    **the search boundary should match the browse boundary**
    
    That is the security property that failed.
    
    ---
    
    ## Disclosure
    
    This issue was reported privately through GitHubโ€™s security reporting flow.
    
    The report showed:
    - the intended safe behavior through `/api/shares/tree`
    - the inconsistent vulnerable behavior through `/api/search/share-search`
    - the underlying source-level cause
    - a concrete reproduction path
    
    The issue was accepted and assigned:
    
    **CVE-2026-33146**
    
    The final advisory severity was **Moderate**, which fits the narrower leak scope better than a broader criticality claim would have.
    
    That does not weaken the validity of the finding.
    
    It just defines its impact more precisely.
    
    ---
    
    ## What This Bug Actually Teaches
    
    The key lesson here is simple:
    
    > hiding something in one public endpoint is not enough if another public endpoint still reveals it.
    
    A lot of developers think about authorization only in the obvious rendering path:
    - page tree
    - page view
    - main UI
    
    But the real boundary is broader.
    
    You also have to ask:
    
    - does search honor the same rules?
    - do side channels honor the same rules?
    - do metadata responses honor the same rules?
    
    In Docmost, the answer was no.
    
    That is the real takeaway.
    
    ---
    
    ## Key Points
    
    - public-share features must enforce the same visibility model across browse and search paths
    - metadata leaks still matter when they cross an intended authorization boundary
    - side-by-side endpoint comparison makes this class of issue much stronger
    - restricted descendants should never remain searchable if they are hidden from the same public viewer
    - tight scope does not make a valid bug unworthy of reporting
    - good security review is often about testing consistency, not just finding crashes or full bypasses
    
    ---
    
    ## Final Words
    
    This vulnerability was not about flashy payloads or complex exploit chains.
    
    It was about asking a very practical trust-boundary question.
    
    Docmost hid the restricted page in one place.  
    Then leaked it in another.
    
    That is why this became **CVE-2026-33146**.