SiYuan: Cross-Origin WebSocket Hijacking via Authentication Bypass — Unauthenticated Information Disclosure
Description
SiYuan is a personal knowledge management system. In versions 3.6.0 and below, the WebSocket endpoint (/ws) allows unauthenticated connections when specific URL parameters are provided (?app=siyuan&id=auth&type=auth). This bypass, intended for the login page to keep the kernel alive, allows any external client — including malicious websites via cross-origin WebSocket — to connect and receive all server push events in real-time. These events leak sensitive document metadata including document titles, notebook names, file paths, and all CRUD operations performed by authenticated users. Combined with the absence of Origin header validation, a malicious website can silently connect to a victim's local SiYuan instance and monitor their note-taking activity. This issue has been fixed in version 3.6.1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/siyuan-note/siyuan/kernelGo | <= 0.0.0-20260313024916-fd6526133bb3 | — |
Affected products
1- Range: < 3.6.1
Patches
11e370e373597:lock: https://github.com/siyuan-note/siyuan/security/advisories/GHSA-xp2m-98x8-rpj6
1 file changed · +38 −12
kernel/util/websocket.go+38 −12 modified@@ -29,7 +29,8 @@ var ( WebSocketServer *melody.Melody // map[string]map[string]*melody.Session{} - sessions = sync.Map{} // {appId, {sessionId, session}} + sessions = sync.Map{} // {appId, {sessionId, session}} + authSessions = sync.Map{} ) func BroadcastByTypeAndExcludeApp(excludeApp, typ, cmd string, code int, msg string, data interface{}) { @@ -113,12 +114,22 @@ func AddPushChan(session *melody.Session) { typ := session.Request.URL.Query().Get("type") session.Set("type", typ) - if appSessions, ok := sessions.Load(appID); !ok { - appSess := &sync.Map{} - appSess.Store(id, session) - sessions.Store(appID, appSess) + if "auth" == id { + if appSessions, ok := authSessions.Load(appID); !ok { + appSess := &sync.Map{} + appSess.Store(id, session) + authSessions.Store(appID, appSess) + } else { + (appSessions.(*sync.Map)).Store(id, session) + } } else { - (appSessions.(*sync.Map)).Store(id, session) + if appSessions, ok := sessions.Load(appID); !ok { + appSess := &sync.Map{} + appSess.Store(id, session) + sessions.Store(appID, appSess) + } else { + (appSessions.(*sync.Map)).Store(id, session) + } } } @@ -130,12 +141,23 @@ func RemovePushChan(session *melody.Session) { return } - appSess, _ := sessions.Load(app) - if nil != appSess { - appSessions := appSess.(*sync.Map) - appSessions.Delete(id) - if 1 > lenOfSyncMap(appSessions) { - sessions.Delete(app) + if "auth" == id { + appSess, _ := authSessions.Load(app) + if nil != appSess { + appSessions := appSess.(*sync.Map) + appSessions.Delete(id) + if 1 > lenOfSyncMap(appSessions) { + authSessions.Delete(app) + } + } + } else { + appSess, _ := sessions.Load(app) + if nil != appSess { + appSessions := appSess.(*sync.Map) + appSessions.Delete(id) + if 1 > lenOfSyncMap(appSessions) { + sessions.Delete(app) + } } } } @@ -451,6 +473,10 @@ func CountSessions() (ret int) { ret++ return true }) + authSessions.Range(func(key, value interface{}) bool { + ret++ + return true + }) return }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-xp2m-98x8-rpj6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-32815ghsaADVISORY
- github.com/siyuan-note/siyuan/commit/1e370e37359778c0932673e825182ff555b504a3ghsax_refsource_MISCWEB
- github.com/siyuan-note/siyuan/releases/tag/v3.6.1ghsax_refsource_MISCWEB
- github.com/siyuan-note/siyuan/security/advisories/GHSA-xp2m-98x8-rpj6ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.