{"id":"CVE-2026-72809","aliases":["GO-2026-6386"],"url":"https://o3.security/vulnerability/CVE-2026-72809","summary":"SiYuan: Localhost-trust admin bypass on auth-code-gated endpoints, with potential remote reachability via the fixed-port proxy","details":"**CVE:** This vulnerability corresponds to [CVE-2026-72809](https://nvd.nist.gov/vuln/detail/CVE-2026-72809).\n\n### Summary\n\nThe kernel's `CheckAuth` grants `RoleAdministrator` to any request whose `RemoteAddr` is loopback (`127.0.0.1`), for a specific set of endpoints, and these localhost bypasses sit **outside** the `accessAuthCode` gate so they apply even when an access auth code is configured. This is demonstrated live (Part A below).\n\nSeparately, the fixed-port reverse proxy (`fixedport.go`) forwards requests to the kernel over loopback and injects no authentication token, and no `SetTrustedProxies` is configured, so gin does not derive the client address from forwarding headers. By code inspection, a request forwarded through this proxy would reach the kernel with `RemoteAddr = 127.0.0.1`. **If** the fixed-port proxy is bound to a network interface (via `NetworkServe`) and forwards remote requests to the kernel this way, the localhost bypass would grant a remote unauthenticated caller admin on those endpoints. This second step is established by reading the code but was **not reproduced end-to-end**, and I'm asking the maintainer to confirm the proxy's runtime forwarding behavior (Part B below).\n\n### Details\n\n**Localhost-trust admin bypass (proven).** `CheckAuth` (`session.go:298-321`) contains localhost-only bypasses that key off `RemoteAddr` and grant `RoleAdministrator`. They sit outside the `accessAuthCode` gate i.e. they apply even when an access auth code is set and cover `/api/system/exit`, `getNetwork`, `getWorkspaceInfo`, `/assets/*`, and `/export/*`.\n\n**Fixed-port proxy behavior (code inspection).** `fixedport.go` is a plain reverse proxy that dials the kernel at `127.0.0.1` and injects no token unlike the publish proxy, which injects a `RoleReader` JWT. There is no `SetTrustedProxies` call, so gin does not rewrite `RemoteAddr` from `X-Forwarded-For`. On this reading, a request forwarded through the fixed-port proxy reaches the kernel with `RemoteAddr = 127.0.0.1`.\n\n**The composition (conditional).** If both hold at runtime, then: remote request → fixed-port proxy on a network interface → forwarded to kernel at `127.0.0.1` (no token) → kernel sees `RemoteAddr = 127.0.0.1` → localhost bypass grants admin for the endpoints above. I have proven the final link (localhost → admin) and read the code for the forwarding link, but have not confirmed at runtime that the fixed-port proxy is instantiated and forwards remote requests as loopback in a shipped configuration.\n\n**Distinct from the previously-dismissed localhost→admin observation.** That observation concerned the *publish* proxy path, where the injected `RoleReader` JWT causes `CheckAuth` to early-return before reaching the localhost bypasses. The fixed-port proxy injects no token, so a request through it would fall through to the `RemoteAddr`-keyed bypass instead. Different proxy, different code path.\n\n### Proof of Concept\n\n**Part A: the localhost bypass grants admin without auth, outside the auth-code gate (demonstrated live).**\nOn a local instance with an access auth code configured, the same no-token `getWorkspaceInfo` request returns different results depending on the source address the kernel sees:\n- From a non-loopback source (kernel sees a non-127.0.0.1 address): `HTTP 401`.\n- From `127.0.0.1` (kernel sees loopback): `{\"code\":0,\"data\":{\"workspaceDir\":\"/siyuan/workspace\",…}}` admin data, no auth, despite `accessAuthCode` being set.\n\nThis confirms the localhost-trust bypass grants admin for these endpoints and is not gated by the access auth code.\n\n**Part B: remote → proxy → loopback (code inspection only; NOT reproduced).**\nBy reading `fixedport.go`, the proxy dials `127.0.0.1`, injects no token, and no `SetTrustedProxies` is set. I was not able to reproduce this end-to-end: the `serve` CLI in the container image tested exposes only `--port` and `--accessAuthCode`, not a flag that instantiates the fixed-port proxy in the `NetworkServe`-on-a-non-default-port shape, so the remote→proxy→loopback chain was not exercised at runtime. I did not invoke the destructive `/api/system/exit` endpoint. **I'm asking the maintainer to confirm: under what conditions is the fixed-port proxy instantiated, does it bind a non-loopback interface under `NetworkServe`, and does it forward to the kernel preserving the client address or as loopback?** That determines whether Part A's bypass is remotely reachable.\n\n### Impact\n\n**Confirmed (Part A):** on any deployment where a caller can cause the kernel to see a loopback `RemoteAddr`, the endpoints `/api/system/exit`, `getNetwork`, `getWorkspaceInfo`, `/assets/*`, and `/export/*` are reachable with admin rights without the access auth code i.e. the auth code does not protect these endpoints against a loopback-sourced caller. On its own this is a local/adjacent bypass of the access-code control for those endpoints. If the fixed-port proxy forwards remote requests to the kernel as loopback (to be confirmed by the maintainer), a *remote* unauthenticated attacker obtains those admin capabilities, remote kernel shutdown (DoS), network/workspace-path disclosure, and admin-level asset/export reads that bypass the publish-access filter. This would be a remote authentication bypass. It does not grant the full admin API only the localhost-trusted endpoints.\n\n### Suggested fix\n\nDo not derive admin trust from `RemoteAddr` when a proxy forwards over loopback. Options: have the fixed-port proxy inject an explicit role/token (as the publish proxy does) so the kernel authorizes on the claim rather than the source address; or configure `SetTrustedProxies` and derive the real client address before applying any localhost bypass; or require the access auth code for these endpoints regardless of source address. The localhost bypass assumes loopback implies a local trusted caller, any loopback-dialing proxy breaks that assumption.","published":"2026-09-03T22:34:06Z","modified":"2026-09-10T15:25:53.562584185Z","cvss":{"score":8,"severity":"HIGH","vector":"CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:H"},"epss":{"score":0.00206,"percentile":0.10995,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/siyuan-note/siyuan/kernel","fixedVersion":"0.0.0-20260723031701-9c16e9851f0b"}],"fix":{"url":"https://github.com/siyuan-note/siyuan/commit/9c16e9851f0b5d7ed179e5c2fca15a7724666871","label":"siyuan-note/siyuan@9c16e98"},"references":[{"type":"WEB","url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-3mp7-4rh5-jrv9"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-72809"},{"type":"WEB","url":"https://github.com/siyuan-note/siyuan/commit/9c16e9851f0b5d7ed179e5c2fca15a7724666871"},{"type":"PACKAGE","url":"https://github.com/siyuan-note/siyuan"},{"type":"WEB","url":"https://www.vulncheck.com/advisories/siyuan-before-authentication-bypass-via-localhost-trust"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-09-10T15:25:53.562584185Z"}}