KEVWire

Security intelligence, with context.

Response snapshot Oct 26, 2026, 22:12 UTC

Back to the wire
Permalink
VulnerabilityGHSA-xp3q-r38w-vgqm

SiYuan: The publish filter for local storage returns the administrator's entire storage map with only three keys sanitized

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.

At a glance

Exploited
Not known
Severity
Not scored yet
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: github.com/siyuan-note/siyuan/kernel: 0.0.0-20260729005258-4daee87d4152. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-xp3q-r38w-vgqm (opens a new tab)

What happened

### 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

Continue reading

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

Affected package ranges reported by GitHub: github.com/siyuan-note/siyuan/kernel: < 0.0.0-20260729005258-4daee87d4152

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-xp3q-r38w-vgqm (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  3. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  4. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  5. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  6. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  7. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  8. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  9. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  10. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

  11. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-xp3q-r38w-vgqm, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 23). SiYuan: The publish filter for local storage returns the administrator's entire storage map with only three keys sanitized. https://kevwire.com/record/GHSA-xp3q-r38w-vgqm

MLA

"SiYuan: The publish filter for local storage returns the administrator's entire storage map with only three keys sanitized." KEVWire, 23 Oct. 2026, kevwire.com/record/GHSA-xp3q-r38w-vgqm.

Chicago

KEVWire. "SiYuan: The publish filter for local storage returns the administrator's entire storage map with only three keys sanitized." Last modified October 23, 2026. https://kevwire.com/record/GHSA-xp3q-r38w-vgqm.

Evidence summary (plain text)

SiYuan: The publish filter for local storage returns the administrator's entire storage map with only three keys sanitized (Oct 23, 2026).
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.
Sources:
GitHub-reviewed advisory GHSA-xp3q-r38w-vgqm: https://github.com/advisories/GHSA-xp3q-r38w-vgqm
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo1MTozMFrOAAf0Mg%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0OToxMlrOAAf0LQ%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0NzoxN1rOAAf0Kg%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0NTozNlrOAAf0Jw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0NTowNVrOAAf0JQ%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0Mzo0NVrOAAf0Iw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QyMDo0MzozMVrOAAf0Ig%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QxODozNTowMVrOAAZRcw%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc&after=Y3Vyc29yOnYyOpK0MjAyNi0xMC0yM1QxODozMDoyN1rOAAY_5Q%3D%3D
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: GHSA-xp3q-r38w-vgqm.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 26, 2026, 20:55 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail