Improper authorization control for web services In github.com/siyuan-note/siyuan/kernel
Description
SiYuan: Broken access control in /api/tag/getTag — Reader role can mutate Conf.Tag.Sort and persist to disk
Summary
POST /api/tag/getTag is registered with model.CheckAuth only, omitting both model.CheckAdminRole and model.CheckReadonly, despite the handler performing a configuration write that is normally guarded by both. Any authenticated user — including publish-service RoleReader accounts and RoleEditor accounts on a read-only workspace — can call this endpoint with a sort argument to mutate model.Conf.Tag.Sort and trigger model.Conf.Save(), which atomically rewrites the entire workspace conf.json.
Same root-cause class as the patched GHSA-4j3x-hhg2-fm2x (which fixed missing CheckAdminRole + CheckReadonly on /api/template/renderSprig).
Details
Affected files / lines (v3.6.5):
kernel/api/router.go:170 — only CheckAuth:
ginServer.Handle("POST", "/api/tag/getTag", model.CheckAuth, getTag) // Compare the sibling registrations on the next two lines, which DO gate writes: ginServer.Handle("POST", "/api/tag/renameTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameTag) ginServer.Handle("POST", "/api/tag/removeTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeTag)
kernel/api/tag.go:28-64 — handler. The if nil != arg["sort"] block writes config without any role check:
func getTag(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) arg, ok := util.JsonArg(c, ret) if !ok { return } ... if nil != arg["sort"] { // ← unauthorized write path sortVal, ok := util.ParseJsonArg[float64]("sort", arg, ret, true, false)...
Conf.Save() rewrites the entire configuration file, which means a malicious caller racing with a legitimate config change can roll back another user's setting (TOCTOU on the global config object).
PoC
Same Docker setup as Advisory 1.
# 1. Authenticate (any role with CheckAuth pass — admin used here for convenience). curl -s -c /tmp/sy.cookie -X POST http://127.0.0.1:6806/api/system/loginAuth \ -H 'Content-Type: application/json' -d '{"authCode":"audittest"}' >/dev/null # 2. Read current Conf.Tag.Sort. curl -s -b /tmp/sy.cookie -X POST http://127.0.0.1:6806/api/system/getConf \ -H 'Content-Type: application/json' -d '{}' \ | python3 -c "import json,sys;print('Conf.Tag.Sort BEFORE =',json.load(sys.stdin)['data']['conf']['tag']['sort'])"...
The vulnerability is exposed to publish-mode RoleReader (default for any anonymous publish visitor) and to RoleEditor users on workspaces where the administrator has set Editor.ReadOnly = true.
Impact
Limited direct damage — the writable field is only the tag display sort order. The pattern is concerning because:
It demonstrates the same gap that GHSA-4j3x-hhg2-fm2x was meant to flag broadly (missing CheckAdminRole + CheckReadonly on a read-style endpoint that performs writes); each occurrence has to be patched individually.
Conf.Save() rewrites the whole file, so a write-race during a legitimate configuration change can overwrite unrelated user-set values.
A publish-service Reader being able to mutate any server state at all violates the intended trust boundary.
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Package | Affected version | Patched versions |
|---|---|---|---|
go | 0.0.0-20260512140701-d7b77d945e0d |
Aliases
References