GHSA-gmmv-4cc5-wr9r SiYuan publish-mode Reader can mutate Conf and SQL index via 8 ungated APIs
SummarySiYuan publish-mode Reader can mutate Conf and SQL index via 8 ungated APIs`POST /api/graph/getGraph`, `POST /api/graph/getLocalGraph`, `POST /api/sync/setSyncInterval`, `POST /api/storage/updateRecentDocViewTime`, `POST /api/storage/updateRecentDocCloseTime`, `POST /api/storage/updateRecentDocOpenTime`, `POST /api/storage/batchUpdateRecentDocCloseTime`, and `POST /api/search/updateEmbedBlock` are registered with `model.CheckAuth` only, omitting both `model.CheckAdminRole` and `model.CheckReadonly`. Each of them writes server-side state, including atomic rewrites of `<workspace>/conf/conf.json` via `model.Conf.Save()`. Any caller whose JWT passes `CheckAuth`, including a publish-service `RoleReader` (the role assigned to anonymous publish visitors) and a `RoleEditor` against a workspace where `Editor.ReadOnly = true`, can hit them. This is the same root-cause class as the patched `GHSA-6r88-8v7q-q4p2` and `GHSA-4j3x-hhg2-fm2x`.DetailsAffected: github.com/siyuan-note/siyuan, all tags up to and including v3.6.5 (HEAD `96dfe0be`).The router in `kernel/api/router.go` registers each endpoint below with `model.CheckAuth` only. Sibling endpoints in the same group are correctly gated, which makes the omission unambiguous:Per-handler evidence:`kernel/api/graph.go:53` `getGraph`. Despite the verb "get", the body unconditionally overwrites `model.Conf.Graph.Global` from caller-supplied JSON and persists the entire workspace `conf.json`:`kernel/api/graph.go:106` `getLocalGraph`. Same pattern on `model.Conf.Graph.Local`. Note the legitimate writers `resetGraph` (`graph.go:29`) and `resetLocalGraph` (`graph.go:41`) only set the struct to its constructor default (`NewGlobalGraph()` / `NewLocalGraph()`), whereas `getGraph` / `getLocalGraph` accept the entire struct from the caller, so the unauthorized surface is strictly larger than the gated reset endpoints.`kernel/api/sync.go:597` `setSyncInterval`. Calls `model.SetSyncInterval(int(interval))` (`kernel/model/sync.go:394`) which writes `Conf.Sync.Interval`, persists `Conf.Save()`, and reschedules the sync goroutine via `planSyncAfter`. The model layer clamps the interval to `[30, 43200]`, but a Reader can still pin sync to either bound (30 s for battery and bandwidth pressure on every connected client, or 12 h to effectively suspend cloud sync without changing the UI toggle).`kernel/api/search.go:287` `updateEmbedBlock`. Calls `model.UpdateEmbedBlock(id, content)` (`kernel/model/search.go:198`), which validates only that the block type is `BlockQueryEmbed` and then forwards to `updateEmbedBlockContent` (`kernel/model/index.go:342`). That helper rewrites the SQL `blocks` row's `content` column for the given embed-block ID via `sql.UpdateBlockContentQueue`. There is no publish-access check, so any embed block ID anywhere in the workspace is writable. The SQL `content` column is what `fullTextSearchBlock` and `getEmbedBlock` read from, so a Reader can poison search results visible to other users.`kernel/api/storage.go:251,295,273,317` `updateRecentDocViewTime` / `updateRecentDocCloseTime` / `updateRecentDocOpenTime` / `batchUpdateRecentDocCloseTime`. Each rewrites the workspace recent-docs JSON file under `recentDocLock` (`kernel/model/storage.go:171,213` ...). A Reader can register any `rootID` (including IDs in publish-private notebooks) into the recent-docs list, manipulating the admin's recently-opened-documents UI and history.The bugs have all existed since v3.6.5 (the active release tag) and the live `master` branch. Two adjacent advisories already patched the exact same shape: `GHSA-6r88-8v7q-q4p2` (`getTag` writing `Conf.Tag.Sort`) and `GHSA-4j3x-hhg2-fm2x` (`renderSprig` missing `CheckAdminRole + CheckReadonly`). Both are listed by the maintainers as occurrences "the same root-cause class" that has to be patched per-occurrence, so this report enumerates the remaining occurrences in one pass.PoCSource-level reproduction. The same Docker compose lab the maintainers used for `GHSA-6r88` works here:Source-level proof, no privileged token involved:Standing up the publish-mode Reader path end-to-end was not done in this audit; the source-level diff against the gated siblings and the prior advisories' fix pattern are the same evidence the maintainers accepted for `GHSA-fmh9-gpqh-g53g` and `GHSA-6r88-8v7q-q4p2` published 2026-05-08.ImpactA publish-mode Reader (default for any anonymous publish visitor) and a publish-mode Editor against a `Editor.ReadOnly = true` workspace can:Atomically rewrite `<workspace>/conf/conf.json` via `Conf.Save()` from `setSyncInterval`, `getGraph`, `getLocalGraph`. `Conf.Save()` rewrites the entire file, so a Reader racing with a legitimate admin save can revert unrelated configuration changes the admin made in the same window.Set the cloud sync interval to either bound of the `[30, 43200]` clamp. 30 s pins clients to the worst-case sync hammer, draining battery and bandwidth on every connected device. 43200 s effectively pauses cloud sync for the workspace without flipping the visible "Sync enabled" toggle, increasing the chance of data divergence between devices and decreasing the likelihood that a Reader-induced state corruption is caught quickly.Overwrite `Conf.Graph.Global` and `Conf.Graph.Local` with a caller-controlled struct, breaking graph rendering for the admin (extreme `maxBlocks`, `minRefs`, `nodeSize`, etc.). The reset endpoints at the same path are gated behind admin role specifically because the maintainers considered graph configuration a privileged setting.Poison the SQL `blocks.content` column for any embed-block ID via `updateEmbedBlock`. Search functions that read the SQL index (`fullTextSearchBlock`, `getEmbedBlock`) return the poisoned content to other users, so a Reader can plant content other users will see.Manipulate the recent-documents list seen by the admin via the four `updateRecentDoc*` writers, including registering IDs from publish-private notebooks (information disclosure plus UI manipulation).The fix is a one-token edit per registration: add `model.CheckAdminRole` and `model.CheckReadonly` to each affected `ginServer.Handle` call, mirroring the gated siblings and the patches for `GHSA-6r88-8v7q-q4p2` and `GHSA-4j3x-hhg2-fm2x`.
CVSS v4.0 score: 7.2