GHSA-xp3q-r38w-vgqm: SiYuan: publish storage endpoint leaks admin context

GHSA-xp3q-r38w-vgqmMEDIUM
Published October 23, 2026
CISO Take

SiYuan's publish mode exposes the administrator's local storage (recently closed tabs, search keywords, expanded private folder paths, and document identifiers) to anonymous or reader-role users because the filter only strips three fields from the whole map. This is a confidentiality-only issue (CVSS 5.8, medium) with no document bodies returned, no write access, no known exploit, no scanner template, and no KEV listing, and EPSS data is not available. The risk is mainly for organizations that run SiYuan in publish mode with anonymous access or shared reader tokens, since a single empty POST request is enough to retrieve the data. The leaked titles, search terms and IDs can reveal sensitive project subjects and feed follow-on targeting, and the package has 3,043 dependents and 75 prior CVEs. Upgrade the kernel to the patched build (v3.7.4-alpha.3 or later, commit 4daee87), and until then disable publish mode or require authentication for it.

Sources:GitHub AdvisoryNVDOpenSSFATLAS

What is the risk?

Medium. Exploitation is trivial (a single unauthenticated POST with an empty body) and needs no user interaction, but impact is limited to low-level confidentiality loss with a changed scope. There is no integrity or availability impact, no public exploit, no Nuclei template, and no KEV or SSVC signal. Exposure depends on whether publish mode is enabled and reachable, and on whether Publish.Auth is disabled. Package hygiene is mediocre (OpenSSF 5.8/10, 75 other CVEs, package risk 32/100).

How does the attack unfold?

Discovery
The attacker finds a SiYuan publish instance reachable on the network, typically on port 6808, with anonymous access or a reader token.
AML.T0006
Exploitation
The attacker sends an empty POST to /api/storage/getLocalStorage, where the blocklist-style filter returns almost the whole administrator storage map.
AML.T0049
Collection
The attacker extracts closed tab titles, search keywords, private folder paths and document IDs from the response.
AML.T0036
Impact
The leaked workspace context exposes sensitive project subjects and supports follow-on targeting or social engineering.
AML.T0048.001

What systems are affected?

PackageEcosystemVulnerable RangePatched
Jupyter Notebookgo< 0.0.0-20260729005258-4daee87d41520.0.0-20260729005258-4daee87d4152
13.5KOpenSSF 5.83.0K dependentsPushed 11d ago79% patched~75d to patchFull package profile →

Do you use Jupyter Notebook? You're affected.

How severe is it?

CVSS 3.1
5.8 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AVACPRUISCIA
AVNetwork
ACLow
PRNone
UINone
SChanged
CLow
INone
ANone

What should I do?

1 step
  1. 1) Upgrade github.com/siyuan-note/siyuan/kernel to the patched version (0.0.0-20260729005258-4daee87d4152, release v3.7.4-alpha.3 or later). 2) If you cannot patch, disable publish mode or restrict the publish port to trusted networks or a VPN, and enable Publish.Auth with strong credentials. 3) Block or require authentication at the reverse proxy for POST /api/storage/getLocalStorage, getLocalStorageVal and getLocalStorageVals. 4) Review web and proxy logs for anonymous requests to these endpoints. 5) Treat titles, search terms and paths in the administrator's local storage as exposed and rotate or reclassify any sensitive items. 6) Follow-up fix upstream: the filter should become an allowlist.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

ISO 42001
A.7.4-Quality of data for AI systems
NIST AI RMF
MANAGE 2.3-Procedures to respond to and recover from previously unknown risks
OWASP LLM Top 10
LLM02-Sensitive Information Disclosure

Frequently Asked Questions

What is GHSA-xp3q-r38w-vgqm?

SiYuan's publish mode exposes the administrator's local storage (recently closed tabs, search keywords, expanded private folder paths, and document identifiers) to anonymous or reader-role users because the filter only strips three fields from the whole map. This is a confidentiality-only issue (CVSS 5.8, medium) with no document bodies returned, no write access, no known exploit, no scanner template, and no KEV listing, and EPSS data is not available. The risk is mainly for organizations that run SiYuan in publish mode with anonymous access or shared reader tokens, since a single empty POST request is enough to retrieve the data. The leaked titles, search terms and IDs can reveal sensitive project subjects and feed follow-on targeting, and the package has 3,043 dependents and 75 prior CVEs. Upgrade the kernel to the patched build (v3.7.4-alpha.3 or later, commit 4daee87), and until then disable publish mode or require authentication for it.

Is GHSA-xp3q-r38w-vgqm actively exploited?

No confirmed active exploitation of GHSA-xp3q-r38w-vgqm has been reported, but organizations should still patch proactively.

How to fix GHSA-xp3q-r38w-vgqm?

1) Upgrade github.com/siyuan-note/siyuan/kernel to the patched version (0.0.0-20260729005258-4daee87d4152, release v3.7.4-alpha.3 or later). 2) If you cannot patch, disable publish mode or restrict the publish port to trusted networks or a VPN, and enable Publish.Auth with strong credentials. 3) Block or require authentication at the reverse proxy for POST /api/storage/getLocalStorage, getLocalStorageVal and getLocalStorageVals. 4) Review web and proxy logs for anonymous requests to these endpoints. 5) Treat titles, search terms and paths in the administrator's local storage as exposed and rotate or reclassify any sensitive items. 6) Follow-up fix upstream: the filter should become an allowlist.

What systems are affected by GHSA-xp3q-r38w-vgqm?

This vulnerability affects the following AI/ML architecture patterns: Self-hosted knowledge management and note-taking platforms, Publish or web-sharing services, Internal documentation systems with AI features.

What is the CVSS score for GHSA-xp3q-r38w-vgqm?

GHSA-xp3q-r38w-vgqm has a CVSS v3.1 base score of 5.8 (MEDIUM).

What is the AI security impact?

Affected AI Architectures

Self-hosted knowledge management and note-taking platformsPublish or web-sharing servicesInternal documentation systems with AI features

MITRE ATLAS Techniques

AML.T0036Data from Information Repositories
AML.T0049Exploit Public-Facing Application
AML.T0095Search Open Websites/Domains

Compliance Controls Affected

ISO 42001: A.7.4
NIST AI RMF: MANAGE 2.3
OWASP LLM Top 10: LLM02

What are the technical details?

Original Advisory

### Relationship to GHSA-hgfg-j9pg-43xw That advisory concerned the administrator's live UI layout, and commit `63c7d5474` resolved it by removing `FilterUILayoutByPublishIgnore` and nulling `Conf.UILayout` for readers. That fix is correct and complete for the live layout. This report concerns a different endpoint, a different filter function and a different storage backing. `/api/storage/getLocalStorage` reads `data/storage/local.json` rather than `Conf.UILayout`, and its filter `FilterLocalStorageByPublishAccess` was not touched by that commit. The consequence is that the same serialized tab objects the earlier fix withheld continue to be served, from the closed-tab history rather than the live layout. ### Summary Three routes, `/api/storage/getLocalStorage`, `getLocalStorageVal` and `getLocalStorageVals`, are registered with `CheckAuth` only and are reachable by the publish `RoleReader` token and anonymously when `Publish.Auth.Enable` is `false`. All three call `model.GetLocalStorage()` and then, for read-only roles, `FilterLocalStorageByPublishAccess`. That filter begins by assigning the administrator's entire storage map to its return value and then removes three specific fields. Everything else in the map is returned unmodified. A single unauthenticated POST with an empty body returns the administrator's recently closed tabs with their document titles and identifiers, their current search keywords and the paths those searches cover, the expanded private paths in their file tree, and per-document identifiers from several other keys. ### Details **Routes.** `kernel/api/router.go:87-89` on master, the same block on the development branch: ```go ginServer.Handle("POST", "/api/storage/getLocalStorage", model.CheckAuth, getLocalStorage) ginServer.Handle("POST", "/api/storage/getLocalStorageVal", model.CheckAuth, getLocalStorageVal) ginServer.Handle("POST", "/api/storage/getLocalStorageVals", model.CheckAuth, getLocalStorageVals) ``` No `CheckAdminRole`, no `CheckReadonly`. **The filter is an allowlist inverted into a blocklist.** `kernel/model/publish_access.go:911` on master and `:1481` on the development branch, byte-identical on both: ```go func FilterLocalStorageByPublishAccess(publishAccess PublishAccess, localStorage map[string]any) (ret map[string]any) { ret = localStorage // the entire administrator map publishIgnore := GetInvisiblePublishAccess(publishAccess) // clears local-searchkeys["keys"] // clears local-searchasset["k"] and ["keys"] // blanks local-docinfo["id"], only when bt != nil, visible tier only, no password check return } ``` `local.json` holds roughly thirty keys. Three are sanitized. The rest are returned as stored. **What survives.** Taking the client's own list of keys it round-trips through this endpoint (`app/src/protyle/util/compatibility.ts:563`), the notable survivors are: - `local-closed-tabs`. Up to `SIZE_UNDO` entries, each produced by `layoutToJSON(item, tabJSON)` at `app/src/layout/Wnd.ts:779`. This is the same serializer that produced the data in the earlier advisory: tab title, `docIcon`, `rootId`, `notebookId`, `blockId`, `mode`, `action`, and for a Search tab the entire config object. Unfiltered. - `local-searchdata`. The live document-search configuration, including `k` (the search text), `r` (replace text), `hPath`, `idPath` (the paths the search was scoped to) and `types`. Note the filter clears the search *history* array and the *asset*-search keyword, but leaves the document-search keyword itself. The project's own standard is applied to two of the three related fields. - `local-filespaths`. `IFilesPath[]` of `{notebookId, openPaths[]}`, the expanded private notebook and folder paths in the file-tree dock. - `local-fileposition`, `local-outline`, `local-zoom`. Keyed by document or block identifier, so each discloses private document identifiers. - `local-move-path`. `{keys[], k}`, the move-document dialog's path history and last keyword. - `local-recent-docs`, `local-history`, `local-flashcard`, `local-dailynoteid`, `local-layouts`. **The asymmetry is visible in the function signature.** In the same file, `kernel/api/storage.go`: | Handler | Filter | Receives `*gin.Context` | |---|---|---| | `getCriteria` | `FilterCriteriaByPublishAccess(c, ...)` | yes | | `getRecentDocs` | `FilterRecentDocsByPublishAccess(c, ...)` | yes | | `getLocalStorage` / `Val` / `Vals` | `FilterLocalStorageByPublishAccess(publishAccess, data)` | no | The two sibling filters take the request context and can therefore call `CheckPublishAuthCookie` to evaluate the password tier. `FilterLocalStorageByPublishAccess` does not receive the context, so it cannot perform that check at all. The visible-only limitation is fixed in the parameter list rather than in the body. Separately, `local-recent-docs` passes through this endpoint unfiltered, which bypasses the dedicated `FilterRecentDocsByPublishAccess` that guards the same data on `getRecentDocs`. **Note on a prior assessment.** `getLocalStorage` appears on lists of endpoints considered correctly filtered, on the basis that it calls a `Filter*ByPublishAccess` function. That observation is accurate and this report does not contradict it. The filter is called. What it does is assign the whole map and remove three fields. **No persistence side effect.** The in-place mutations the filter performs do not corrupt the administrator's stored state, because `GetLocalStorage` re-reads and unmarshals `storage/local.json` on each call (`kernel/model/storage.go:82`), so the map is per-request. This is noted to pre-empt the concern rather than as part of the finding. ### Proof of Concept Precondition: publish mode enabled (default port 6808), anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. An administrator who has used the client normally, closing some document tabs and running a search. ``` POST http://127.0.0.1:6808/api/storage/getLocalStorage {} → 200. The returned map includes, among others: local-closed-tabs entries carrying title, docIcon, rootId, notebookId, blockId for recently closed documents, including documents the reader has no publish access to local-searchdata k, r, hPath, idPath for the administrator's current document search local-filespaths notebookId and openPaths for expanded private folders local-fileposition keys are private document identifiers ``` `getLocalStorageVal` returns any single key by name, so a caller who only wants the closed-tab history can request it directly. For contrast, requesting the same underlying data through the sibling endpoint shows the filtering that is applied elsewhere: ``` POST http://127.0.0.1:6808/api/storage/getRecentDocs {} → 200, filtered by FilterRecentDocsByPublishAccess ``` while `local-recent-docs` inside the storage response is not. ### Impact An anonymous reader in publish mode, or any publish `RoleReader`, obtains the administrator's working context: the titles and identifiers of documents they recently had open, what they have been searching for and where, which private folders they have expanded, and per-document identifiers across several other keys. Titles and search keywords are author-written text and routinely describe the subject of the documents they refer to. The document and notebook identifiers disclosed here are also the input required by identifier-to-path and path-to-content resolution, so the value is not limited to the metadata itself. No document bodies are returned, and nothing is written. Confidentiality only. ### Suggested fix Invert the filter into an actual allowlist. Return only the keys a publish reader needs to render published content, which is a short list along the lines of `local-images`, `local-emojis`, `local-pdftheme`, `local-fontstyles` and the theme and zoom preferences, and drop everything else by default. New keys added in future then default to withheld rather than exposed. Additionally, pass `*gin.Context` into `FilterLocalStorageByPublishAccess` so that any retained per-document entry can be gated with the full `CheckBlockIdAccessableByPublishAccess` including the password tier, matching the sibling filters in the same file.

Exploitation Scenario

An adversary scans for internet-exposed SiYuan publish instances on the default port 6808 where publish authentication is disabled. They send a POST to /api/storage/getLocalStorage with an empty JSON body and receive the administrator's closed tabs, current search text and scoped paths, expanded private folders, and document IDs. They use the titles and search keywords to learn what the organization is researching (for example an AI red-team project or an incident), and use the notebook and document IDs for further targeted requests against other endpoints. The data supports reconnaissance and social engineering rather than direct system compromise.

Weaknesses (CWE)

CWE-863 — Incorrect Authorization: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.

  • [Architecture and Design] Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
  • [Architecture and Design] Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N

Timeline

Published
October 23, 2026
Last Modified
October 23, 2026
First Seen
October 23, 2026

Related Vulnerabilities