A AegiFlow
MEDIUMCVSS 5.8

GHSA-xp3q-r38w-vgqm

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

Published
2026-10-23
Modified
2026-10-23
Sources
github-advisory

Summary

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

Affected packages

EcosystemPackageAffected versionsFixed versions
Gogithub.com/siyuan-note/siyuan/kernel—0.0.0-20260729005258-4daee87d4152

Remediation: Upgrade to 0.0.0-20260729005258-4daee87d4152 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.