Threat database
206 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Medium | 6.9 | EPSS 0.3% | CVE-2026-73605siyuan · Missing Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-73605 SiYuan versions before v3.7.4 contain a path traversal vulnerability in the getUniqueFilename endpoint that allows anonymous readers to probe filesystem existence without validation or confinement. Attackers can supply arbitrary absolute paths to determine whether files and directories exist on the host, enabling reconnaissance of the filesystem layout and installed software. | CVE Database V5 | ||
| High | 7.1 | EPSS 0.3% | CVE-2026-72809siyuan · Authentication Bypass by Spoofing High7.1EPSS 0.3%CVE Database V5CVE-2026-72809 **CVE:** This vulnerability corresponds to [CVE-2026-72809](https://nvd.nist.gov/vuln/detail/CVE-2026-72809). ### Summary The 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). Separately, 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). ### Details **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/*`. **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`. **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. **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. ### Proof of Concept **Part A: the localhost bypass grants admin without auth, outside the auth-code gate (demonstrated live).** On 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: - From a non-loopback source (kernel sees a non-127.0.0.1 address): `HTTP 401`. - From `127.0.0.1` (kernel sees loopback): `{"code":0,"data":{"workspaceDir":"/siyuan/workspace",…}}` admin data, no auth, despite `accessAuthCode` being set. This confirms the localhost-trust bypass grants admin for these endpoints and is not gated by the access auth code. **Part B: remote → proxy → loopback (code inspection only; NOT reproduced).** By 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. ### Impact **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 ma | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.4% | CVE-2026-72808siyuan · Missing Authorization Medium6.9EPSS 0.4%CVE Database V5CVE-2026-72808 **CVE:** This vulnerability corresponds to [CVE-2026-72808](https://nvd.nist.gov/vuln/detail/CVE-2026-72808). ### Summary The `/api/asset/getFileAnnotation` endpoint returns the content of `.sya` PDF-annotation files with no publish-access check. It is gated by `CheckAuth` only, so it is reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. Its sibling, the `/assets/*` asset route does enforce publish access, including the publish password. An anonymous reader who knows an asset path can therefore read the private PDF annotations (highlights, notes) attached to assets in publish-forbidden, password-protected, or unpublished documents. ### Details `getFileAnnotation` resolves the annotation file via `GetAssetAbsPathInBox` (no path traversal) and returns the `.sya` content. Unlike the `/assets/*` route, which applies the publish-access filter including password enforcement before serving asset bytes, `getFileAnnotation` applies no publish-access, publish-ignore, or password check. The guarded-sibling asymmetry indicates the boundary is meant to apply to this data and was omitted here. `.sya` files for encrypted-box assets are fail-closed and not exposed. The gap is limited to non-encrypted assets. **Route / auth tier.** `getFileAnnotation` is registered `CheckAuth`-only. `CheckAuth` admits `RoleReader`; the publish proxy forwards port-6808 traffic with a Reader JWT (anonymous when publish auth is disabled). ### Proof of Concept Reproduced on a local instance (publish mode on 6808, Basic Auth off). **Setup (admin, 6806):** 1. `createNotebook{name:"AnnotPoc"}` → box 2. `createDocWithMd{notebook, path:"/annot-victim", markdown:"doc with a pdf"}` → doc 3. `POST /api/asset/upload` (multipart `id=<doc>`, `file[][email protected]`) → `assets/secret-...pdf` 4. `setFileAnnotation{path:"<asset>.sya", data:"{annotSecret:ANNOT_SECRET_4471,note:private highlight}"}` 5. `setPublishAccess{id:<doc>, visible:false, password:"", disable:true}` → doc forbidden **Exploit (anonymous reader, 6808, no token):** ``` POST http://127.0.0.1:6808/api/asset/getFileAnnotation {"path":"assets/secret-...pdf.sya"} ``` Returns: ``` {"code":0,"data":{"data":"{\"annotSecret\":\"ANNOT_SECRET_4471\",\"note\":\"private highlight\"}"}} ``` The annotation content of a publish-forbidden document is returned to an anonymous reader with no publish-access check, while the `/assets/*` route serving the same asset class enforces publish access and password. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can read the private PDF annotations (highlights and notes) of assets belonging to publish-forbidden, password-protected, or unpublished documents, given the asset path. This defeats the publish-access/password boundary for annotation data. Scope is limited to annotated PDFs in non-encrypted notebooks; encrypted-box annotations are not exposed. Confidentiality-only. ### Suggested fix Apply the same publish-access check the `/assets/*` route uses to `getFileAnnotation` resolve the asset's owning document and enforce the publish-access/publish-ignore / password check before returning `.sya` content. | CVE Database V5 | ||
| High | 8.8 | EPSS 0.3% | CVE-2026-72807siyuan · SQL Injection High8.8EPSS 0.3%CVE Database V5CVE-2026-72807 **CVE:** This vulnerability corresponds to [CVE-2026-72807](https://nvd.nist.gov/vuln/detail/CVE-2026-72807). ### Summary Attribute-view (AV) template columns are live-evaluated on every render and expose the `queryBlocks` template function, which runs raw SQL on the read-write database handle (`SelectBlocksRawStmt`, using `?`→argument string substitution rather than parameter binding). AV mutations are admin-gated, so this is not directly reader-injectable but it is a second-order vector: an attacker distributes a SiYuan document or AV package whose template column contains `.action{queryBlocks "<arbitrary SQL>"}` when a victim imports the package and renders the AV, the attacker's SQL executes on the victim's kernel (read and, via statement stacking, write). ### Details Doc-level `{{…}}` templates are rendered at insert-time and become static, so they are not re-evaluated on reader view. The residual is AV template columns, which are live-evaluated at render. `queryBlocks` passes its argument to `SelectBlocksRawStmt` with `?`→arg string substitution, not a bound parameter, on the main read-write handle (`88250/go-sqlite3` fork, statement-stacking capable) so an attacker-controlled template argument becomes arbitrary SQL. The SSTI surface is otherwise hardened: `BuiltInTemplateFuncs` deletes `env`, `expandenv`, and `getHostByName` so there is no environment/host/file/exec SSTI. `queryBlocks`-to-SQL is the remaining live-evaluated sink. ### Proof of Concept An AV template column set to `.action{queryBlocks "<sql>"}` executes `<sql>` against the read-write handle when the AV is rendered. Delivered second-order: a shared/imported document or AV package carrying such a column runs the embedded SQL on any kernel that imports and renders it. ### Proof of Concept Reproduced on a live instance. **1. Host doc + AV (admin, 6806):** ``` POST /api/filetree/createDocWithMd {notebook, path:"/ssti-poc", markdown:"host"} → DOC POST /api/av/renderAttributeView {id:"<AV>"} # materializes the AV ``` **2. Plant the malicious template column (one performTransactions call):** ```json {"reqId":1,"session":"poc","transactions":[{"doOperations":[ {"action":"addAttrViewCol","avID":"<AV>","id":"<COL>","name":"tpl","type":"template"}, {"action":"updateAttrViewColTemplate","avID":"<AV>","id":"<COL>","type":"template", "data":".action{range queryBlocks \"SELECT * FROM blocks WHERE root_id='<PROTECTED_DOC_ID>'\"}.action{.Markdown} .action{end}"}, {"action":"insertAttrViewBlock","avID":"<AV>","isDetached":true} ]}]} ``` **3. Trigger (render evaluates the template):** ``` POST /api/av/renderAttributeView {id:"<AV>"} ``` Result: the template cell renders `## LockedSection TOP_SECRET…` the `queryBlocks` SQL executed and returned the password-protected document's content, arbitrary SQL via template reading across the publish/password boundary. `queryBlocks` uses `?`→arg string substitution (not parameterized), so UNION and stacked writes are also possible. Delivered second-order, the same column executes on any kernel that imports and renders the AV. ### Impact An attacker who gets a victim to import a crafted document/AV package and render it achieves arbitrary SQL execution on the victim's kernel cross-notebook read and via stacking, write. Precondition is content delivery plus render (import of an attacker-supplied package), which bounds severity to Medium. Not directly reachable by an anonymous reader (AV creation is admin-gated); the injection travels in stored/imported template content. ### Suggested fix Parameterize `queryBlocks` bind the argument rather than substituting it into the SQL string or restrict the template function set available in AV columns as was done for the doc-template functions. Treat imported template content as untrusted at render time. | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.4% | CVE-2026-72806siyuan · Missing Authorization Medium6.9EPSS 0.4%CVE Database V5CVE-2026-72806 **CVE:** This vulnerability corresponds to [CVE-2026-72806](https://nvd.nist.gov/vuln/detail/CVE-2026-72806). ### Summary `FilterViewByPublishAccess`, the filter `renderAttributeView` applies for Reader sessions drops rows using only the hidden/forbidden check and never checks the publish password. Its three sibling filters all check both tiers. As a result, a publish `RoleReader` (or the anonymous account when `Publish.Auth.Enable` is `false`) who has not entered a document's password still receives every database/attribute-view row bound to that password-protected document, the primary cell (title/ID) and all column values. ### Details `FilterViewByPublishAccess` (`model/publish_access.go:290`) drops rows on the hidden/forbidden tier only: ```go // Table (line 311), Gallery (348), Kanban (385), all identical: if !CheckPathAccessableByPublishIgnore(bt.BoxID, bt.Path, publishIgnore) { row = nil // hidden/forbidden dropped, but password NEVER checked } ``` The three sibling filters all check both the hidden/forbidden tier and the password tier (`password == "" || CheckPublishAuthCookie(...)`): - `FilterBlockAttributeViewKeysByPublishAccess` (line 412) - `FilterBlockInfoByPublishAccess` (line 457) - `FilterContentByPublishAccess` (line 474) So the password (protected) tier is enforced everywhere except this AV/database-view filter. Table layout masks nothing; Gallery and Kanban mask only the cover, keeping the card and its non-cover values. Reachable via `renderAttributeView`, `getAttributeViewKeys`, and `renderSnapshotAttributeView`, all `CheckAuth`-only. ### Proof of Concept Reproduced on a live instance (publish mode on 6808, anonymous Reader, no password cookie), against a password-protected document with a database/AV row. | Check | Path | Result | |---|---|---| | Control | `getDoc(secretDoc)` | 🔒 placeholder, body withheld password gate works normally | | Test | `renderAttributeView(AV)` | row leaked : `blockID=…rk7jofz`, title `secret-db-row` | | Differential (`disable=true`) | same filter | 0 rows : hidden tier correctly enforced | | Differential (password set) | same filter | 1 row : password tier bypassed | Same filter, same document, same Reader: the hidden tier drops the row, the password tier leaks it isolating the omission. ### Impact An anonymous/Reader publish user who has not supplied a protected document's password receives all attribute-view/database rows bound to that document titles, block IDs, and column values defeating the publish-password control for database views. Confidentiality-only. The hidden/forbidden tier is unaffected (correctly enforced). ### Suggested fix Add the password check to the drop condition in all three layout branches (Table, Gallery, Kanban), mirroring the sibling filters: ```go if !CheckPathAccessableByPublishIgnore(...) || !(password == "" || CheckPublishAuthCookie(c, passwordID, password)) { row = nil } ``` | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | CVE-2026-72805siyuan · Missing Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72805 **CVE:** This vulnerability corresponds to [CVE-2026-72805](https://nvd.nist.gov/vuln/detail/CVE-2026-72805). ### Summary Three block endpoints return document content snippets and metadata without any publish-access check, while their sibling `getBlockInfo` which returns comparable data does enforce one. All three are `CheckAuth`-only, so they are reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. An anonymous reader supplying a block ID receives content and metadata belonging to publish-forbidden and password-protected documents. ### Details `getBlockInfo` (`kernel/api/block.go`) gates on the publish boundary: ```go if !checkBlockPublishAccess(c, id, ret) { return } ``` The following siblings in the same file perform no equivalent check: | Endpoint | Returns | Publish check | |---|---|---| | `getBlockInfo` | root/title/path metadata | `checkBlockPublishAccess`: present | | `getBlockBreadcrumb` | `BlockPath.Name` : root document title plus every ancestor block's content snippet | none | | `getRefText` | the block's reference/anchor text (document content) | none | | `getBlockTreeInfos` | root/title/path metadata for arbitrary block IDs | none | `getBlockBreadcrumb` returns the full ancestor chain including each ancestor block's content snippet, and `getRefText` returns block anchor text both are document content, not just metadata. `getBlockTreeInfos` returns root/title/path for any caller-supplied ID set via `model.GetBlockTreeInfosInBox(...)` with no gate. `getBlockBreadcrumb` and `getRefText` additionally accept a client-supplied notebook argument that routes to the `*InBox` variants, so the same unguarded path applies to encrypted-notebook reads while the notebook is unlocked. The correct primitive already exists in the codebase and is used by `getBlockInfo`; these three handlers simply do not call it. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. A document `D` is marked publish-forbidden (or password-protected) and contains a block `BLOCK_ID` under a heading with distinctive content. **Control: the gated sibling correctly refuses:** ``` POST http://127.0.0.1:6808/api/block/getBlockInfo {"id":"BLOCK_ID"} ``` Blocked by `checkBlockPublishAccess`. **Disclosure: the ungated siblings return the data anyway:** ``` POST http://127.0.0.1:6808/api/block/getBlockBreadcrumb {"id":"BLOCK_ID"} → ancestor chain including the forbidden document's title and ancestor block content snippets POST http://127.0.0.1:6808/api/block/getRefText {"id":"BLOCK_ID"} → the block's reference/anchor text (content of the forbidden document) POST http://127.0.0.1:6808/api/block/getBlockTreeInfos {"ids":["BLOCK_ID"]} → root ID, title, and path for the forbidden document ``` Verified by code inspection at `origin/master` (`eef10568`): `getBlockInfo` contains the `checkBlockPublishAccess` call; `getBlockBreadcrumb`, `getRefText`, and `getBlockTreeInfos` contain no publish-access, publish-ignore, or readonly-role check in their bodies. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can read, for documents explicitly excluded from publishing or protected by a publish password: - the document title and full ancestor chain, including ancestor block content snippets (`getBlockBreadcrumb`); - block reference/anchor text, i.e. document content (`getRefText`); - root ID, title, and path metadata for arbitrary block IDs (`getBlockTreeInfos`). Because `getBlockBreadcrumb` and `getRefText` accept a notebook argument routing to the `*InBox` variants, the same disclosure applies to encrypted notebooks while unlocked. Confidentiality-only; the precondition is a block ID, obtainable from other reader-reachable endpoints. ### Suggested fix Call `checkBlockPublishAccess` (as `getBlockInfo` does) in `getBlockBreadcrumb`, `getRefText`, and `getBlockTreeInfos` before returning data for `getBlockTreeInfos`, apply it per ID and drop unauthorized entries. Confirm the `*InBox` variants (`GetBlockRefTextInBox`, `BuildBlockBreadcrumbInBox`) enforce the same boundary so the notebook-argument path is covered. | CVE Database V5 | ||
| Critical | 9.2 | EPSS 0.4% | Critical9.2EPSS 0.4%CVE Database V5CVE-2026-72804 **CVE:** This vulnerability corresponds to [CVE-2026-72804](https://nvd.nist.gov/vuln/detail/CVE-2026-72804). ### Summary `getGraph` and `getLocalGraph` filter reader sessions against the *visibility* tier only and never check the publish password. Password-protected documents are `Visible = true`, so their graph nodes survive the filter and graph nodes are block-level and carry the block's actual content. An anonymous reader who has never supplied a document's password can therefore retrieve that document's per-block content and its reference/backlink topology. Both endpoints are `CheckAuth`-only, so they are reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. ### Details Both handlers filter with the invisible-tier set only: ```go publishIgnore := model.GetInvisiblePublishAccess(publishAccess) // Visible tier ONLY nodes, links = model.FilterGraphByPublishIgnore(publishIgnore, nodes, links) ``` `FilterGraphByPublishIgnore` (`kernel/model/publish_access.go:849`) drops a node only when `!CheckPathAccessableByPublishIgnore(node.Box, node.Path, publishIgnore)`. There is no password evaluation anywhere in the graph code grepping `graph.go` for `CheckPublishAuthCookie` or `password` returns nothing. Because password-protected documents are `Visible = true`, their nodes pass the filter unchanged. **The nodes carry content, not just titles.** `genTreeNodes` emits one node per block, and `nodeTitleLabel` sets `node.Title = node.Label = block.Content` (`kernel/model/graph.go:673-678`). So each surviving node exposes the block's text. This is materially broader than the by-design listing behaviour: `listDocsByPath` intentionally exposes protected document *titles* (protected means "publicly visible, password required to access"), but the graph endpoints expose per-block *content* and the full link/reference topology, which listing never does. Verified at `origin/master`: `FilterGraphByPublishIgnore` contains only the `CheckPathAccessableByPublishIgnore` call; `graph.go` contains no password check; both routes are registered `CheckAuth` without `CheckAdminRole`. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. A document is marked **protected** (visible, password set) and contains blocks with distinctive content. No password is supplied in any request below. **Targeted: one protected document's content and reference structure:** ``` POST http://127.0.0.1:6808/api/graph/getLocalGraph {"id":"<PROTECTED_DOC_ID>"} ``` Returns nodes whose `Title`/`Label` contain the protected document's per-block content, plus its links/backlinks. **Workspace-wide: every protected document:** ``` POST http://127.0.0.1:6808/api/graph/getGraph {} ``` Returns block-level nodes for every protected document in the workspace, together with the complete link graph. **Control:** requesting the same document through the normal content path returns the password placeholder confirming the password gate is enforced elsewhere and omitted here. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can read the block-level content of every password-protected document in the workspace, plus the full reference/backlink topology, without ever supplying a password. This defeats the publish-password control for the graph subsystem. Confidentiality-only, no modification. ### Suggested fix Extend the graph filter to evaluate the password tier in addition to visibility: in `FilterGraphByPublishIgnore`, drop a node unless the document's publish password is unset or the caller presents a valid publish-auth cookie for it (`password == "" || CheckPublishAuthCookie(...)`), matching the sibling filters that enforce both tiers. Consider also excluding block content from node `Title`/`Label` for reader sessions, so the graph conveys structure without document text. | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | CVE-2026-72803siyuan · Missing Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72803 **CVE:** This vulnerability corresponds to [CVE-2026-72803](https://nvd.nist.gov/vuln/detail/CVE-2026-72803). ### Summary `POST /api/attr/getBlockAttrs` and `POST /api/attr/batchGetBlockAttrs` return a block's full attribute set (IAL) with no publish-access check. Both are `CheckAuth`-only, so they are reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. An anonymous reader supplying a block ID receives the block's name, alias, memo, bookmark, tags, and every `custom-*` attribute including for blocks in publish-forbidden and password-protected documents. The batch variant accepts an ID list, making it a bulk extraction primitive. ### Details Neither handler applies a filter: ```go // /api/attr/getBlockAttrs (router line 301) ret.Data = sql.GetBlockAttrs(id) // /api/attr/batchGetBlockAttrs (router line 302) ret.Data = sql.BatchGetBlockAttrs(idList) ``` `sql.GetBlockAttrs` is a direct database read returning the block's entire IAL: `name`, `alias`, `memo`, `bookmark`, `tags`, and any `custom-*` key/value the user has set. Several of these are user-authored free text memos in particular are freeform notes attached to a block so this is document content, not merely structural metadata. `batchGetBlockAttrs` takes an arbitrary list of block IDs in a single request, so an attacker holding a set of block IDs can sweep attributes across the entire workspace in one call. **Guarded-sibling asymmetry.** The sibling metadata endpoint `getBlockInfo` calls `checkBlockPublishAccess(c, id, ret)` before returning; `getBlockAttrs` and `batchGetBlockAttrs` call nothing. Verified at `origin/master`: both handler bodies contain no publish-access, publish-ignore, or readonly-role check, and both routes are registered `CheckAuth` without `CheckAdminRole`. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. A document is marked publish-forbidden (or password-protected) and contains a block with a memo and a custom attribute set. **Single-block disclosure:** ``` POST http://127.0.0.1:6808/api/attr/getBlockAttrs {"id":"<BLOCK_ID_IN_PROTECTED_DOC>"} ``` Returns the block's IAL: `name`, `alias`, `memo`, `bookmark`, `tags`, and all `custom-*` values. **Bulk disclosure:** ``` POST http://127.0.0.1:6808/api/attr/batchGetBlockAttrs {"ids":["<ID1>","<ID2>","<ID3>"]} ``` Returns the attribute sets for every supplied ID in one response, with no per-ID authorization. **Control:** the sibling `getBlockInfo` with the same block ID is refused by `checkBlockPublishAccess`, confirming the boundary is enforced elsewhere and omitted here. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can read block attributes belonging to publish-forbidden and password-protected documents, including user-authored memos and arbitrary `custom-*` values. The batch endpoint turns this into a bulk primitive: given a set of block IDs, an attacker retrieves attributes across the whole workspace in a single request. Confidentiality-only. ### Suggested fix Call `checkBlockPublishAccess` in `getBlockAttrs` before returning, matching `getBlockInfo`. For `batchGetBlockAttrs`, apply the check per ID and drop unauthorized entries from the response rather than failing the whole batch. | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72802 **CVE:** This vulnerability corresponds to [CVE-2026-72802](https://nvd.nist.gov/vuln/detail/CVE-2026-72802). ### Summary `POST /api/asset/resolveAssetPath` returns the resolved **absolute** filesystem path of an asset, unmodified. The route is `CheckAuth`-only, so it is reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. An anonymous reader who knows any asset's relative path trivially harvested from an `<img src="assets/…">` in any published document receives the server's absolute workspace path, disclosing the operating-system username and the installation layout. ### Details ```go // kernel/api/asset.go: resolveAssetPath p, err := model.GetAssetAbsPathInBox(path, "") // boxID="" → absolute workspace path ... ret.Data = p // returned raw, no stripping ``` `GetAssetAbsPathInBox(path, "")` resolves under `util.DataDir` / `util.WorkspaceDir`, producing a full host path such as `C:\Users\<username>\SiYuan\data\assets\foo.png` or `/home/<user>/…`. The handler returns it directly with no redaction and no publish-scope check. **This is data the project already treats as sensitive.** `getConf` explicitly zeroes `System.WorkspaceDir`, `AppDir`, `ConfDir`, `DataDir`, and `HomeDir` when `util.IsBrowserRequest(c)`, a change made specifically to avoid leaking the username (issue #17410). `resolveAssetPath` performs no equivalent stripping, so it re-exposes precisely the values `getConf` was patched to hide. **Related unfiltered siblings** in the same file, also `CheckAuth`-only with no publish scoping: - `getMissingAssets`: workspace-wide list of missing asset references - `getUnusedAssets`: every unused asset filename in the assets directory Both return asset inventory spanning all documents, including publish-forbidden ones. Verified at `origin/master`: `resolveAssetPath` returns the absolute path with no redaction, `getConf` contains the `IsBrowserRequest` stripping; all three routes are registered `CheckAuth` without `CheckAdminRole`. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. **1. Harvest an asset path**: open any published document and read a relative asset path from its markup, e.g. `assets/foo-20260101120000-abcdefg.png`. **2. Resolve it as an anonymous reader:** ``` POST http://127.0.0.1:6808/api/asset/resolveAssetPath {"path":"assets/foo-20260101120000-abcdefg.png"} ``` **3. Result:** the response returns the absolute host path, e.g. `C:\Users\<username>\SiYuan\data\assets\foo-...png` disclosing the OS username and the full workspace/installation layout. **Control:** `getConf` from the same anonymous session returns `WorkspaceDir`/`DataDir`/`HomeDir` blanked, confirming the project intends these values to be withheld from browser requests. **Related:** ``` POST http://127.0.0.1:6808/api/asset/getUnusedAssets {} POST http://127.0.0.1:6808/api/asset/getMissingAssets {} ``` Return workspace-wide asset inventory with no publish scoping. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` obtains the server's absolute workspace path, which typically embeds the OS username, plus the installation directory layout. This is useful for targeting subsequent attacks (path construction, user enumeration, social engineering) and directly contradicts the redaction the project applies in `getConf`. The related endpoints additionally disclose workspace-wide asset inventory, including assets referenced only by publish-forbidden documents. Confidentiality-only. ### Suggested fix Apply the same redaction `getConf` uses: for browser/reader requests, return the asset path relative to the workspace root rather than the absolute host path (or omit it entirely). Add publish-access scoping to `getUnusedAssets` and `getMissingAssets` so their results are limited to documents the caller may see. | CVE Database V5 | ||
| High | 8.7 | EPSS 0.4% | CVE-2026-72801siyuan · Insufficiently Protected Credentials High8.7EPSS 0.4%CVE Database V5CVE-2026-72801 **CVE:** This vulnerability corresponds to [CVE-2026-72801](https://nvd.nist.gov/vuln/detail/CVE-2026-72801). ### Summary Two `CheckAuth`-only endpoints disclose the complete offline attack material for the encrypted-notebook master password, plus the wrapped per-notebook key needed to use it. Both are reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. An unauthenticated remote client can retrieve the Argon2id salt and cost parameters, a verifier that confirms a correct password offline, and the encrypted per-notebook data key reducing the security of every encrypted notebook to the master password's resistance to offline GPU cracking. ### Details **(1) `POST /api/system/getConf` leaks `NotebookCrypto`.** `getConf` → `GetMaskedConf()` marshals the full configuration including `NotebookCrypto *conf.NotebookCrypto` (JSON tag `notebookCrypto`, not `-`, so it survives the deep copy). For non-administrators `HideConfSecret()` is applied, which nulls a dozen secret-bearing fields like AI, MCPOAuth, Api, Flashcard, Publish, Repo, Sync, Secrets, Variables, System paths but contains **no reference to `NotebookCrypto`**. `FilterConfByPublishIgnore()` for readers only touches `UILayout`. The reader therefore receives: | Field | What it is | |---|---| | `MasterSalt` | global Argon2id salt | | `KDFParams` | Argon2id memory/time/parallelism cost | | `KEKVerifier` + `VerifierNonce` | AES-GCM-encrypted fixed magic, the in-code comment states it exists for offline master-password verification | | `KEKMAC` | HMAC-SHA256 of the KEK | Either `KEKVerifier` or `KEKMAC` is a self-contained offline oracle: ``` KEK = Argon2id(guess, MasterSalt, KDFParams) correct if AES-GCM-decrypt(KEKVerifier, VerifierNonce) == magic or HMAC(KEK) == KEKMAC ``` No server round-trips are required, so there is no rate limiting, lockout, or logging on guesses, and the work is fully GPU-parallelisable. **(2) `POST /api/notebook/getNotebookConf` leaks the wrapped data key.** `box.GetConf()` returns the full `BoxConf` including `BoxCrypt.WrappedDEK`, the per-notebook data-encryption key wrapped under the KEK via AES-GCM together with `WrapNonce`. `getNotebookInfo` is the same class. Once (1) yields the master password, the attacker derives the KEK, decrypts `WrappedDEK` to recover the real data-encryption key, and decrypts every `.sy` file in that notebook. **Why this matters beyond the at-rest threat model.** Storing verifier and KDF material alongside the ciphertext is reasonable against a *local* attacker who already has filesystem access. Serving `MasterSalt` + `KDFParams` + `KEKVerifier` + `WrappedDEK` to an *anonymous remote reader* converts that at-rest assumption into a remote pre-authentication cracking opportunity. **Guarded-sibling asymmetry.** `HideConfSecret` nulls a dozen secret fields but omits `NotebookCrypto`. `lsNotebooks` filters notebook visibility for readers, while `getNotebookConf` and `getNotebookInfo` apply no reader filter at all. Verified at `origin/master` (`eef105683`): handler bodies as described; `HideConfSecret` contains zero `NotebookCrypto` matches; `FilterConfByPublishIgnore` touches only `UILayout`; all relevant struct JSON tags are non-`-`; all three routes are registered `CheckAuth` without `CheckAdminRole`. ### Proof of Concept Precondition: publish mode enabled (default port 6808) with at least one encrypted notebook configured; anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. **1. Retrieve the key-derivation material as an anonymous reader:** ``` POST http://127.0.0.1:6808/api/system/getConf {} ``` The response's `notebookCrypto` object contains `MasterSalt`, `KDFParams`, `KEKVerifier`, `VerifierNonce`, and `KEKMAC` while the same response has the other secret fields (Api, Repo, Sync, Publish, System paths) correctly blanked, demonstrating the omission. **2. Retrieve the wrapped notebook key:** ``` POST http://127.0.0.1:6808/api/notebook/getNotebookConf {"notebook":"<NOTEBOOK_ID>"} ``` The response contains `BoxCrypt.WrappedDEK` and `WrapNonce`. **3. Offline:** candidate passwords are verified locally against `KEKVerifier`/`KEKMAC` using `MasterSalt` and `KDFParams`, with no further server interaction. A recovered password yields the KEK, which unwraps `WrappedDEK` to the notebook's data-encryption key. *Verification status:* the leak paths are confirmed by code inspection at `origin/master`. A live end-to-end demonstration requires a build from HEAD with an encrypted notebook enabled; the test instance available predates the encrypted-notebook feature, so no runtime reproduction is claimed here. ### Impact An unauthenticated remote client (publish mode with auth disabled) or any publish `RoleReader` obtains everything needed to mount an unlimited, unthrottled, GPU-parallel offline attack on the encrypted-notebook master password, plus the wrapped data key to decrypt notebook contents o | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | CVE-2026-72800siyuan · Missing Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72800 **CVE:** This vulnerability corresponds to [CVE-2026-72800](https://nvd.nist.gov/vuln/detail/CVE-2026-72800). ### Summary `POST /api/av/getAttributeViewKeysByID` returns a database's full column schema with no publish-access filtering, while its sibling `getAttributeViewKeys` applies the filter for reader sessions. Two further endpoints, `getBlockDefIDsByRefText` and `getBlockRelevantIDs` return workspace-wide block IDs with no publish scoping. All three are `CheckAuth`-only, so they are reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. ### Details **(a) `getAttributeViewKeysByID`: ungated column-schema disclosure** The entire handler (`kernel/api/av.go`, router line 548): ```go ret.Data = model.GetAttributeViewKeysByID(avID, keyIDs...) // no publish gate ``` With an empty `keyIDs`, `GetAttributeViewKeysByID` calls `av.ParseAttributeView(avID)` and returns every column's `*av.Key`: | Field | Discloses | |---|---| | `Name`, `Desc` | column title and user-authored description | | `Options` | the full single/multi-select vocabulary and colours (e.g. status labels such as "Fired", "Confidential") | | `Template` | the column's Sprig template expression — logic and field references | | `Relation` | the target `avID`, allowing pivot to another database | | `Rollup`, `NumberFormat`, date config | further schema | **Guarded-sibling asymmetry, same file:** `getAttributeViewKeys` (router line 526) runs `FilterBlockAttributeViewKeysByPublishAccess(...)` when `IsReadOnlyRoleContext`. `getAttributeViewKeysByID` applies nothing. A reader who knows an `avID` — trivially harvested from the `data-av-id` attribute of any published document embedding a database obtains the schema of any database in the workspace, including those whose host documents are hidden, password-protected, or publish-forbidden. `Relation` targets allow walking to sibling databases. **(b) Two unscoped block-ID enumeration oracles** Both `CheckAuth`-only with no publish gate. Neither returns content of its own, but both yield valid block IDs that other endpoints turn into content: - `getBlockDefIDsByRefText` (router line 237) → `GetBlockDefIDsByRefText(anchor)` returns the block IDs, workspace-wide and including private documents, whose reference/anchor text equals a caller-supplied string: a ref-text → ID oracle. - `getBlockRelevantIDs` (router line 274) → `GetBlockRelevantIDsInBox(id, <notebook from request>)` returns parent/previous/next block IDs and traverses decrypted encrypted-notebook structure while the notebook is unlocked, a tree-walk oracle. Verified at `origin/master` (`eef105683`): all three handler bodies contain no publish-access, publish-ignore, or readonly-role check; all three routes are registered `CheckAuth` without `CheckAdminRole`. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. A database exists whose host document is publish-forbidden or password-protected. **(a) Column schema of any database:** ``` POST http://127.0.0.1:6808/api/av/getAttributeViewKeysByID {"avID":"<AV_ID>"} ``` Returns every column's key object names, descriptions, select vocabularies, template expressions, and `Relation` target `avID`s for a database whose host document the reader may not access. **Control:** the sibling `getAttributeViewKeys` with the same `avID` returns filtered results for the same reader session, confirming the boundary is enforced there and omitted here. **(b) ID oracles:** ``` POST http://127.0.0.1:6808/api/block/getBlockDefIDsByRefText {"anchor":"<known ref text>"} → block IDs workspace-wide, including blocks in private documents POST http://127.0.0.1:6808/api/block/getBlockRelevantIDs {"id":"<BLOCK_ID>","notebook":"<BOX_ID>"} → parent/previous/next block IDs; traverses encrypted-notebook structure when unlocked ``` ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can read the complete column schema of any database in the workspace, including column descriptions, select-option vocabularies (which frequently encode sensitive category labels), template logic, and relation targets that permit pivoting to further databases regardless of whether the host document is hidden, password-protected, or excluded from publishing. The two enumeration endpoints additionally supply valid block IDs across the publish boundary, including from encrypted notebooks while unlocked. This removes the "attacker must already know a valid ID" precondition from other block-read endpoints, converting ID knowledge into content disclosure. Confidentiality-only. ### Note getAttributeViewKeysByID is a distinct handler from getAttributeView; although column definitions also appear within the latter's response payload, a fix applied to one handler does not remediate the other, and getAttributeViewKeysByID has its own filtered si | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | CVE-2026-72799siyuan · Missing Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72799 **CVE:** This vulnerability corresponds to [CVE-2026-72799](https://nvd.nist.gov/vuln/detail/CVE-2026-72799). ### Summary Five filetree endpoints resolve arbitrary document IDs and paths with no publish-access check of any kind. All are `CheckAuth`-only, so they are reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. An anonymous reader can map the complete private document tree every notebook, folder, and document title, and which notebook holds each document for documents marked hidden, password-protected, or publish-forbidden, and can resolve titles to document IDs. ### Details None of the following handlers invokes `IsReadOnlyRoleContext`, `CheckBlockIdAccessableByPublishAccess`, or `CheckPathAccessableByPublishIgnore`: | Endpoint | Returns | Discloses | |---|---|---| | `getFullHPathByID` (router 159) | `GetFullHPathByID(id)` | full title path including notebook, e.g. `/MySecretNotebook/Confidential/Q3 Layoffs Plan` | | `getHPathByID` (router 157) | `GetHPathByID(id)` | document-relative title path | | `getPathByID` (router 158) | `{path, notebook}` | which notebook a document lives in, plus its `.sy` storage path | | `getIDsByHPath` (router 160) | `GetIDsByHPath(path, notebook)` | title-path → document-ID enumeration | | `getHPathByPath` (router 155) | HPath from a storage path | title path from a storage path | Each accepts an arbitrary ID or HPath and resolves it identically for hidden, publish-forbidden, and password-protected documents. This enables two operations for an unauthenticated caller: 1. **Map the private document tree**: `getFullHPathByID` and `getPathByID` yield every notebook/folder/document title and its containing notebook. 2. **Resolve titles to IDs**: `getIDsByHPath` converts a known or guessed title path into document IDs, which are the required input for other block-read endpoints. Document titles and HPaths are precisely the block metadata that the project's block-metadata restriction (commit `ffde3b21e`) set out to protect; that change gated `getBlockInfo`, `getDocInfo`, and `getDocsInfo` but left this entire path-resolution family open. **Guarded-sibling asymmetry.** `getRecentDocs` (`FilterRecentDocsByPublishAccess`), `getCriteria` (`FilterCriteriaByPublishAccess`), and `getLocalStorage` (`FilterLocalStorageByPublishAccess`) all filter document references for reader sessions. The codebase clearly publish-scopes reader-visible metadata elsewhere; these five endpoints do not. Verified at `origin/master` (`eef105683`): all five handler bodies contain no publish-access call; the storage-family siblings contain their filters; all five routes are registered `CheckAuth` without `CheckAdminRole`. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. A document exists in a notebook marked publish-forbidden or password-protected. **Resolve a private document's full title path:** ``` POST http://127.0.0.1:6808/api/filetree/getFullHPathByID {"id":"<DOC_ID>"} → /MySecretNotebook/Confidential/Q3 Layoffs Plan ``` **Identify its notebook and storage path:** ``` POST http://127.0.0.1:6808/api/filetree/getPathByID {"id":"<DOC_ID>"} → {"path":"/....sy","notebook":"<BOX_ID>"} ``` **Enumerate IDs from a title path:** ``` POST http://127.0.0.1:6808/api/filetree/getIDsByHPath {"path":"/Confidential","notebook":"<BOX_ID>"} → document IDs under a folder the reader cannot otherwise access ``` Each returns data for documents excluded from publishing; no password or membership is required. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can enumerate the complete private document structure: notebook names, folder hierarchy, and document titles for content the administrator marked hidden, password-protected, or excluded from publishing. Document titles alone are frequently sensitive (project names, personnel actions, client identifiers). The ID-resolution direction additionally supplies valid document IDs, removing the "attacker must know an ID" precondition for other block-read endpoints. Confidentiality-only. ### Suggested fix For `IsReadOnlyRoleContext` sessions, gate each of the five handlers with `CheckBlockIdAccessableByPublishAccess` (or restrict resolution to publish-visible documents), mirroring the treatment already applied in `getRecentDocs`, `getCriteria`, and `getLocalStorage`. | CVE Database V5 | ||
| Critical | 9.2 | EPSS 0.4% | CVE-2026-72798siyuan · Missing Authorization Critical9.2EPSS 0.4%CVE Database V5CVE-2026-72798 **CVE:** This vulnerability corresponds to [CVE-2026-72798](https://nvd.nist.gov/vuln/detail/CVE-2026-72798). ### Summary `renderAttributeView` correctly applies the reader publish-access filter, but the filter's row-accessibility decision is keyed solely to the row's **first cell**, and it never inspects the remaining cells' values. Relation and Rollup cells carry mirrored content from a *different* database, so a row belonging to a published database can hand an anonymous reader the contents of a related database whose host document is hidden, publish-forbidden, or password-protected. Separately, when the first column is not a block value the accessibility check is skipped entirely and the row is returned unchecked. ### Note: This is distinct from the previously reported password-tier omission in the same function that concerns the row's own primary block, whereas these two defects concern (a) other cells' related-database content, which no row-level check covers and (b) rows where the first cell is not a block at all. A fix to the row-drop condition alone would close neither. ### Details `renderAttributeView` applies the filter (`kernel/api/av.go:68`): ```go retDataMap["view"] = model.FilterViewByPublishAccess(c, publishAccess, retDataMap["view"].(av.Viewable)) ``` Inside `FilterViewByPublishAccess` (`kernel/model/publish_access.go`): ```go if row.Cells[0].Value.Block != nil { bt = treenode.GetBlockTree(row.Cells[0].Value.Block.ID) } if bt != nil { if !CheckPathAccessableByPublishIgnore(bt.BoxID, bt.Path, publishIgnore) { row = nil // drop } } // every other cell in the row is returned as-is ``` **(a) Relation and Rollup cells leak the related database.** These value types carry mirrored content, not just references: ```go type ValueRelation struct { BlockIDs []string; Contents []*Value } type ValueRollup struct { Contents []*Value } ``` The render pipeline populates them from a different attribute view e.g. `kernel/model/attribute_view.go:2731`: ```go v.GroupVal.Relation.Contents = []*av.Value{ relationDestAv.GetBlockValue(groupValue) } ``` `relationDestAv` is a separate database that may live in a hidden, publish-forbidden, or password-protected document. When a published database's row survives the filter (because its column-0 document is public), its Relation and Rollup columns return the related, non-published database's content block text, titles, and mirrored column values. `FilterViewByPublishAccess` performs no publish-access evaluation on `Relation.Contents` or `Rollup.Contents`. **(b) Fail-open when column 0 is not a block.** `bt` is assigned only when `row.Cells[0].Value.Block != nil`. If the first column is a non-block type (Relation, Text, …) or the row is detached, `bt` remains `nil`, the `if bt != nil` guard is skipped, and the row is returned with no accessibility check at all. Column order is user-reorderable, so any database whose first column is not the document block bypasses row filtering entirely. Verified at `origin/master` (`eef105683`). ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`. Two databases: **DB-A** hosted in a published document, **DB-B** hosted in a publish-forbidden or password-protected document, with a Relation column in DB-A pointing at DB-B and containing a distinctive marker value. **(a) Related-database content disclosure:** ``` POST http://127.0.0.1:6808/api/av/renderAttributeView {"id":"<DB_A_AV_ID>"} ``` Rows of DB-A are returned (correctly, since its host document is public), and their Relation/Rollup cell `Contents` include DB-B's block text and mirrored column values, despite DB-B's host document being excluded from publishing. **(b) Fail-open row:** Reorder DB-A so its first column is a non-block type (or use a detached row), mark its host document publish-forbidden, and request the same endpoint the row is returned without any accessibility evaluation. *Verification status:* both defects are confirmed by code inspection at `origin/master`. A live demonstration requires a build from HEAD with two linked databases; available on request. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can read content from databases whose host documents are hidden, publish-forbidden, or password-protected, by requesting a *published* database that relates to them. Because relation graphs are commonly used to link a public index to private detail records, this exposes exactly the data the publish boundary is meant to withhold. The fail-open path additionally returns rows with no accessibility check whenever the first column is not a block value, which is a user-controlled layout property. Confidentiality-only. ### Suggested fix In `FilterViewByPublishAccess`: 1. For each retained row, evaluate every `Relation.Contents` and `Rollup.Contents` entry against `CheckBlockIdAccessableByPublishAccess` (including the pub | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | CVE-2026-72797siyuan · Missing Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72797 **CVE:** This vulnerability corresponds to [CVE-2026-72797](https://nvd.nist.gov/vuln/detail/CVE-2026-72797). ### Summary `POST /api/notebook/getEncryptedNotebookStatus` returns the identifier, name, and current lock state of every encrypted notebook, with no publish-access filtering. The route is registered `CheckAuth` only, no `CheckReadonly`, no `CheckAdminRole` so it is reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. Encrypted notebooks are private by design; their names frequently reveal the sensitive topic that motivated encrypting them. ### Details Route registration (`kernel/api/router.go:131`): ```go ginServer.Handle("POST", "/api/notebook/getEncryptedNotebookStatus", model.CheckAuth, getEncryptedNotebookStatus) ``` The handler (`kernel/api/notebook.go`) accepts no arguments and contains no `IsReadOnlyRoleContext` branch and no publish-access filter of any kind. It returns, for every encrypted notebook, `{id, name, unlocked}`, the notebook's `box.Name` and its live lock state together with `enabled`, `count`, `migrationPending`, `migrationBoxes`, and `hasHistoryDependency`. **Guarded-sibling asymmetry.** The primary notebook listing `lsNotebooks`, in the same file and also reader-reachable, *does* filter: it skips notebooks that are `Closed` and any notebook whose `publishAccess` entry is not `Visible`. The project therefore publish-scopes notebook listings but `getEncryptedNotebookStatus` enumerates all encrypted notebooks unconditionally. **Still unfixed at HEAD.** The encrypted-notebook hardening series (issue #18034 idle auto-lock, lock-on-background, key handling, atomic unlock) is local-threat work. Commit `f2d966659` ("Expose encrypted notebook unlock status to plugins") introduced this exposure, and no subsequent commit gates it for the publish/reader boundary. **Secondary effect.** `unlocked: true` is a live indicator of exactly when an encrypted notebook's plaintext is resident in memory, the window during which reader-reachable code paths that accept a notebook argument can read its decrypted content. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. At least one encrypted notebook exists. ``` POST http://127.0.0.1:6808/api/notebook/getEncryptedNotebookStatus {} ``` Returns the full set of encrypted notebooks with their IDs, names, and current `unlocked` state including notebooks that `lsNotebooks` withholds from the same reader session. No arguments and no privileged access are required. ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` learns which encrypted notebooks exist, what they are called, and whether each is currently unlocked. Notebook names are themselves sensitive: a user encrypts a notebook precisely because its subject matter is private, and the name commonly states that subject. Disclosing existence and naming to unauthenticated parties defeats that expectation, and the live lock state additionally reveals when the notebook's contents are decrypted in memory. Confidentiality-only. ### Suggested fix For `IsReadOnlyRoleContext` sessions, either reject the request or restrict the response to notebooks that are publish-visible, mirroring the filtering already applied in `lsNotebooks`. Encrypted notebooks arguably should never be enumerated to a reader at all. | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.4% | CVE-2026-72796siyuan · Missing Authorization Medium6.9EPSS 0.4%CVE Database V5CVE-2026-72796 **CVE:** This vulnerability corresponds to [CVE-2026-72796](https://nvd.nist.gov/vuln/detail/CVE-2026-72796). ### Summary Several static-file routes in the server mux (`kernel/server/serve.go`) are registered with `CheckAuth` only and serve directories directly, without the publish-access checks, sensitive-path blocklist, or `refuseToAccess` rules that the REST API applies to the same data. They are therefore reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`. Most notably, `/templates/` serves `data/templates` a directory the REST file API explicitly refuses to serve to non-administrators. ### Details | Route | Registration | Guard | Exposed to a reader | |---|---|---|---| | `/templates/*` (line 424) | `Group("/templates/", model.CheckAuth).Static("", data/templates)` | none beyond path cleaning | the templates directory | | `/snippets/*` (line 434) | `CheckAuth` | blocks only `conf.json` | any snippet's JS/CSS content by name | | `/widgets/`, `/plugins/`, `/emojis/` (409/414/419) | `Group(CheckAuth).Static(dir)` | none | whole directory trees | | `/export/*` (line 320) | `Group("/export/", CheckAuth)` | traversal, sensitive-path and DEK guards: **no publish check** | export artifacts (PDF/HTML/DOCX/CSV) | **`/templates/` directly contradicts an existing control.** The REST file path runs `refuseToAccess` (`kernel/api/file.go:551-553`), which explicitly returns 403 for `data/templates/` to non-administrators the project has already decided readers must not read templates. The `/templates/` static route serves that same directory to any `RoleReader`, with no `IsSensitivePath` check, no publish-access check, and no `refuseToAccess`. Templates are user-authored Markdown documents containing template/Sprig syntax and are not covered by publish-access controls. `gin`'s `.Static` disables directory listing, so a filename is required but the contradiction with `refuseToAccess` is the defect. **`/export/` versus `/assets/`.** `/assets/*path` (line 692) correctly gates non-administrators via `CheckAbsPathAccessableByPublishAccess`, plus `IsSensitivePath` and encrypted-asset handling. `/export/` carries traversal, sensitive-path and DEK guards but no publish-access check, so a reader can retrieve export artifacts of arbitrary documents, including private ones. Artifact naming is mixed, some use a random export ID, but code and CSV exports use the document name (`export/code/<name>`, `export/csv/<name>/<name>.csv`), which a reader can derive from `listDocsByPath` titles while the artifact exists. This path is therefore conditional. **Confirmed correctly gated, for contrast:** `/assets/*` (publish-gated), `/history/*` and `/repo/diff/*` (`CheckAdminRole`), `/debug/pprof/*` (disabled in production builds), `/public/` (intentionally public). All four routes above were verified to carry `CheckAuth` only, no `CheckReadonly`, no `CheckAdminRole`. ### Proof of Concept Precondition: publish mode enabled (default port 6808), anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. **Templates: served despite the REST API refusing them:** ``` GET http://127.0.0.1:6808/templates/<template-file>.md → 200, template content ``` The equivalent REST request is refused: ``` POST http://127.0.0.1:6808/api/file/getFile {"path":"data/templates/<template-file>.md"} → 403 (refuseToAccess) ``` The same reader session obtains the file through the static route. **Snippets:** ``` GET http://127.0.0.1:6808/snippets/<snippet-name>.js → 200, snippet source ``` **Export artifacts (conditional on a predictable name while the artifact exists):** ``` GET http://127.0.0.1:6808/export/csv/<doc-name>/<doc-name>.csv → 200, exported content of a document the reader has no publish access to ``` ### Impact An anonymous reader (publish mode with auth disabled) or any publish `RoleReader` can read user-authored template documents that the REST API explicitly withholds from non-administrators, snippet source (JS/CSS), and the contents of the widgets, plugins and emoji directories. Where an export artifact exists under a derivable name, they can additionally retrieve exported content of documents outside their publish scope. Confidentiality-only. ### Suggested fix Apply the same treatment these routes' REST counterparts already receive: for non-administrator roles, enforce publish-access scoping plus `IsSensitivePath` and the `refuseToAccess` blocklist on the static routes, mirroring `/assets/*`. Alternatively, block `/templates/`, non-public `/snippets/`, and `/export/` for reader roles entirely. | CVE Database V5 | ||
| Critical | 9.2 | EPSS 0.4% | CVE-2026-72795siyuan · Missing Authorization Critical9.2EPSS 0.4%CVE Database V5CVE-2026-72795 **CVE:** This vulnerability corresponds to [CVE-2026-72795](https://nvd.nist.gov/vuln/detail/CVE-2026-72795). ### Summary `/api/block/getBlockDOMWithEmbed` and `/api/block/getBlockDOMsWithEmbed` gate only the *requested* block against publish access. The blocks pulled in by that block's embed (transclusion) query are inlined into the returned DOM with no publish-access check at all, so a reader who requests a legitimately-published block containing an embed query receives the content of every block that query matched including blocks in hidden, forbidden and password-protected documents. The dedicated embed endpoint `getEmbedBlock` filters these same results for reader roles. The DOM-with-embed path does not. ### Details | Item | Detail | |---|---| | Routes | `kernel/api/router.go:226` `POST /api/block/getBlockDOMWithEmbed` → `model.CheckAuth` → `getBlockDOMWithEmbed`; `:227` `POST /api/block/getBlockDOMsWithEmbed` → `model.CheckAuth` → `getBlockDOMsWithEmbed` | | Middleware | `CheckAuth` only — no `CheckReadonly`, no `CheckAdminRole` → reachable by the publish `RoleReader` token, and anonymously when `Publish.Auth.Enable` is `false` | | Guard present | Top-level requested block only. Singular: password + disable checks on the requested id. Plural: `filterBlockDOMsByPublishAccess` over the requested ids. | | Guard absent | The embedded blocks resolved from the query | | Exposed | Content HTML of every block matched by the embed query, regardless of that block's publish visibility or password tier | **The leak path.** `getBlockDOMWithEmbed` → `GetBlockDOMsWithEmbedInBox` → `resolveEmbedContentInBox` (`kernel/model/block.go:976`). That function walks the tree for `NodeBlockQueryEmbed`, extracts the embed SQL, executes `sql.SelectBlocksRawStmtInBox(stmt, ...)`, builds `embedContents` from each matched block's content HTML, and inlines the result via `SetIALAttr("embed-content", ...)`. `resolveEmbedContentInBox` takes no `publishAccess` parameter and contains no filtering: `FilterEmbedBlocks`, `CheckBlockIdAccessableByPublishAccess`, `CheckPathAccessable`, `publishAccess` and `IsReadOnlyRoleContext` all return zero matches in that function and its call path. The top-level gate therefore passes on the requested block, and the response body carries content the reader has no access to. Embed queries in published documents routinely span the entire workspace by design, dashboard documents, "all my TODOs" aggregations, tag or attribute rollups. The reader does not need to construct anything: requesting an already-published document that contains such an embed is sufficient. If the publish frontend renders embedded blocks through this endpoint, the disclosure occurs passively during normal viewing of any published document containing an embed block. **Guarded sibling.** `getEmbedBlock` (`kernel/api/router.go:212`), which is also reader-reachable and returns embed query results, applies `model.FilterEmbedBlocksByPublishAccess(c, publishAccess, blocks)` for reader roles (`kernel/model/search.go:38`). The same class of data is filtered there and unfiltered here. **Scope note.** A prior review of `getBlockDOMsWithEmbed` confirmed that the per-id publish gate is correctly applied to the *requested* ids. That observation is accurate and is not in dispute, this report concerns the *embedded* blocks resolved downstream in `resolveEmbedContentInBox`, which are not covered by that gate. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. A published document containing an embed query whose results include at least one block in a non-published or password-protected document. ``` POST http://127.0.0.1:6808/api/block/getBlockDOMWithEmbed {"id":"<id of a published block containing an embed query>"} → 200 The returned DOM contains embed-content attributes carrying the content HTML of blocks in documents the reader has no publish access to. ``` Differential check against the filtered sibling, using the same reader session and the same underlying query: ``` POST http://127.0.0.1:6808/api/search/getEmbedBlock {"stmt":"<the same embed query>"} → 200, results filtered by FilterEmbedBlocksByPublishAccess ``` The filtered endpoint withholds the private blocks; the DOM endpoint returns their content. The plural route behaves identically for each requested id: ``` POST http://127.0.0.1:6808/api/block/getBlockDOMsWithEmbed {"ids":["<published block containing an embed query>"]} ``` ### Impact An anonymous reader in publish mode or any publish `RoleReader` can read the content of blocks in documents that are not published, are marked hidden or forbidden, or are protected by a publish password, by requesting a published block that transcludes them. Because embed queries in aggregation-style published documents commonly match across the whole workspace, the volume of exposed content is bounded only by wh | CVE Database V5 | ||
| Critical | 9.2 | EPSS 0.4% | CVE-2026-72794siyuan · Insufficiently Protected Credentials Critical9.2EPSS 0.4%CVE Database V5CVE-2026-72794 **CVE:** This vulnerability corresponds to [CVE-2026-72794](https://nvd.nist.gov/vuln/detail/CVE-2026-72794). ### Summary `/api/system/getConf` returns `Conf.CookieKey`, the key used to sign the server's session cookies in its response body. The endpoint is registered with `CheckAuth` only, so the field reaches the publish `RoleReader` token and the anonymous account when `Publish.Auth.Enable` is `false`. The configuration-export endpoint in the same file strips this exact field before returning config, so the project already treats it as secret. The reader-facing masking path does not. ### Details | Item | Detail | |---|---| | Route | `kernel/api/router.go:70` `POST /api/system/getConf` → `model.CheckAuth` → `getConf` | | Middleware | `CheckAuth` only — no `CheckReadonly`, no `CheckAdminRole` | | Leaked field | `AppConf.CookieKey`, serialized as `cookieKey` | | Purpose of the field | Signing key for the `siyuan` session cookie | **The field survives every stage of the masking chain.** `getConf` masks through `GetMaskedConf()` → `HideConfSecret()` (non-administrators) → `FilterConfByPublishIgnore()` (readers) → the browser-side System-path strip. `CookieKey` is removed by none of them: - `GetMaskedConf` masks `UserData`, `MCPOAuth` and `AccessAuthCode` only. - `HideConfSecret` nulls `AI`, `Api`, `Flashcard`, `ServerAddrs`, `Publish`, `Repo`, `Sync`, `Secrets`, `Variables` and the System paths. It contains no reference to `CookieKey`. - `FilterConfByPublishIgnore` touches `UILayout` only. - The browser-side strip removes System paths only. **The key is live, not vestigial.** It is passed straight into the session store at startup: ``` cli/cmd/serve.go:67 go server.Serve(false, model.Conf.CookieKey) kernel/server/serve.go:152 sessionStore = cookie.NewStore([]byte(cookieKey)) kernel/server/serve.go:159 ginServer.Use(sessions.Sessions("siyuan", sessionStore)) ``` `gin-contrib/sessions/cookie.NewStore` constructed with a single key uses that key as the `gorilla/securecookie` HMAC key. The `siyuan` session cookie is therefore signed with the value the endpoint hands out. An attacker holding it can mint and modify session cookies that the server accepts as authentic. **Guarded sibling, in the same file.** `exportConf` (`kernel/api/system.go:299`) clones the configuration before returning it and explicitly clears both secrets: ``` kernel/api/system.go:360 clonedConf.CookieKey = "" clonedConf.NotebookCrypto = nil ``` The project has already classified `CookieKey` as a value that must not leave the server. The `getConf` masking path omits the same field. **Ceiling is environment-dependent, floor is not.** Escalating a forged session to administrator additionally requires the forged `SessionData` to carry the matching `AccessAuthCode`, which `GetMaskedConf` does mask or the instance to have no access-auth code configured, which is a common deployment. The unconditional impact, present on every instance, is the disclosure of a persistent cryptographic secret to an unauthenticated party together with the cookie-forgery capability that follows from it. Rotating the key invalidates all existing sessions, so this is not a secret that can be quietly refreshed. ### Proof of Concept Precondition: publish mode enabled (default port 6808); anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. ``` POST http://127.0.0.1:6808/api/system/getConf {} → 200 The response body's conf object contains a cookieKey field holding the server's session-signing key in cleartext. ``` Differential check against the endpoint that does strip it: ``` POST http://127.0.0.1:6808/api/system/exportConf → cookieKey is empty, notebookCrypto is null ``` The same value is withheld by one endpoint and returned by the other. ### Impact An anonymous reader in publish mode or any publish `RoleReader` obtains the server's session-cookie signing key. This is a persistent cryptographic secret whose disclosure cannot be remediated without invalidating every active session. Possession of the key permits forging and tampering with `siyuan` session cookies that the server will validate as authentic. On instances with no access-auth code configured, or where a forged session's contents otherwise satisfy the server's checks, this extends to authenticating as a privileged user. ### Suggested fix Clear `CookieKey` in `HideConfSecret` for all non-administrator responses. The more durable fix is to route non-administrator `getConf` through the `exportConf` cloner, which already handles `CookieKey`, `NotebookCrypto`, `Account`, `Stat`, `System.ID`, `Export.PandocBin` and the AI keys replacing the current blocklist, which fails open on every field nobody thought to add, with the allowlist-style cloner the project already maintains. | CVE Database V5 | ||
| Critical | 9.2 | EPSS 0.4% | CVE-2026-72793siyuan · Insufficiently Protected Credentials Critical9.2EPSS 0.4%CVE Database V5CVE-2026-72793 **CVE:** This vulnerability corresponds to [CVE-2026-72793](https://nvd.nist.gov/vuln/detail/CVE-2026-72793). ### Summary `/api/system/getConf` is registered with `CheckAuth` only and is reachable by the publish `RoleReader` token, and anonymously when `Publish.Auth.Enable` is `false`. Its non-administrator masking chain is a blocklist that enumerates fields individually. Three fields that the configuration-export endpoint in the same file deliberately clears are absent from that blocklist and are returned to readers: | Field | JSON | What it is | Cleared by `exportConf` at | |---|---|---|---| | `Conf.CookieKey` | `cookieKey` | The session-cookie signing key | `kernel/api/system.go:360` | | `Conf.Export.PandocBin` | `export.pandocBin` | Absolute path embedding the OS username | `kernel/api/system.go:338` | | `Conf.NotebookCrypto` | `notebookCrypto` | Encrypted-notebook key material | `kernel/api/system.go:360` | The project has already classified all three as values that must not leave the server. The reader-facing path returns them. ### Details **Route.** `kernel/api/router.go:70` : `POST /api/system/getConf` → `model.CheckAuth` → `getConf`. No `CheckReadonly`, no `CheckAdminRole`. **The masking chain, and what each stage covers.** `getConf` masks through `GetMaskedConf()` → `HideConfSecret()` (non-administrators) → `FilterConfByPublishIgnore()` (readers) → a browser-request path strip. - `GetMaskedConf`: `UserData`, `MCPOAuth`, `AccessAuthCode`. - `HideConfSecret`: `AI`, `Api`, `Flashcard`, `ServerAddrs`, `Publish`, `Repo`, `Sync`, `Secrets`, `Variables`, and the System paths. No reference to `CookieKey` or `Export.PandocBin`. - `FilterConfByPublishIgnore`: `UILayout` only. - Browser strip (`kernel/api/system.go:630-631`): System paths only. Each stage names fields explicitly, so any field nobody thought to add is returned by default. --- **1. `CookieKey`: the live session-signing key.** The value is passed straight into the session store at startup: ``` cli/cmd/serve.go:67 go server.Serve(false, model.Conf.CookieKey) kernel/server/serve.go:152 sessionStore = cookie.NewStore([]byte(cookieKey)) kernel/server/serve.go:159 ginServer.Use(sessions.Sessions("siyuan", sessionStore)) ``` `gin-contrib/sessions/cookie.NewStore` constructed with a single key uses that key as the `gorilla/securecookie` HMAC key. The `siyuan` session cookie is signed with the value this endpoint hands out, so an attacker holding it can mint and modify session cookies the server accepts as authentic. Escalating a forged session to administrator additionally requires the forged `SessionData` to carry the matching `AccessAuthCode` which *is* masked or the instance to have no access-auth code configured, which is a common deployment. The unconditional impact, present on every instance, is disclosure of a persistent cryptographic secret to an unauthenticated party. Rotating it invalidates every active session, so it cannot be quietly refreshed. --- **2. `Export.PandocBin`: bypasses a shipped privacy control.** The field is an absolute path that embeds the OS username by construction: ``` conf/export.go:35 PandocBin string `json:"pandocBin"` model/conf.go:430-431 if "" == Conf.Export.PandocBin { Conf.Export.PandocBin = util.PandocBinPath } util/pandoc.go:154 PandocBinPath = filepath.Join(tempPandocDir, "bin", "pandoc.exe") util/pandoc.go:134 tempPandocDir = filepath.Join(TempDir, "pandoc") util/working.go:359 TempDir = filepath.Join(WorkspaceDir, "temp") util/working.go:312 defaultWorkspaceDir = filepath.Join(userProfile, "SiYuan") ``` → `C:\Users\<username>\SiYuan\temp\pandoc\bin\pandoc.exe` This one is notable beyond the disclosure itself, because a control was shipped specifically to prevent it. `kernel/api/system.go:630` adds, for browser requests: ```go if util.IsBrowserRequest(c) { maskedConf.System.WorkspaceDir = "" maskedConf.System.AppDir = "" maskedConf.System.ConfDir = "" maskedConf.System.DataDir = "" maskedConf.System.HomeDir = "" } // 避免泄露用户名等敏感信息 ``` The comment states the goal plainly: avoid leaking the username and other sensitive information. The block enumerates only `System.*` and misses `Export.PandocBin`, which carries the same username through the same response. A publish reader is a browser request, so the System paths are blanked while `export.pandocBin` passes through intact. Where an administrator has configured a custom pandoc location, that path is disclosed instead still a filesystem-layout disclosure. --- **3. `NotebookCrypto`.** Encrypted-notebook key material is likewise absent from `HideConfSecret` while `exportConf` sets it to `nil`. Reported previously and included here only because it is the third instance of the same root cause; the fix below closes all three together. --- **The root cause is the blocklist itself.** `exportConf` (`kernel/api/system.go:299`) clones the configuration and clears each secret before | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | CVE-2026-72792siyuan · Incorrect Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72792 **CVE:** This vulnerability corresponds to [CVE-2026-72792](https://nvd.nist.gov/vuln/detail/CVE-2026-72792). ### Summary `/api/tag/getTag` filters its results for reader roles through `FilterTagsByPublishIgnore`, which checks only the *visible* publish tier. Documents that are published but password-protected pass that check, so a reader who has never entered a document's publish password receives every tag label used inside it, together with occurrence counts. The project has already treated this exact tier mismatch as a vulnerability on a sibling path: commit `82e9ded42` ("Enforce publish access for graph nodes") moved the graph endpoint off the visible-only filter and onto a password-aware one. `FilterTagsByPublishIgnore` was not upgraded alongside it. ### Details | Item | Detail | |---|---| | Route | `kernel/api/router.go:194` `POST /api/tag/getTag` → `model.CheckAuth` → `getTag` | | Middleware | `CheckAuth` only: no `CheckReadonly`, no `CheckAdminRole` | | Guard | Present but at the wrong tier: visible-only, no password check | | Exposed | Every tag label (`#label#` text) and its occurrence count inside password-protected but visible documents | **The tier mismatch.** For reader roles, `getTag` filters via: ```go publishIgnore := GetInvisiblePublishAccess(publishAccess) tags = FilterTagsByPublishIgnore(publishIgnore, tags) ``` `FilterTagsByPublishIgnore` counts a label if `CheckPathAccessableByPublishIgnore(span.Box, span.Path, publishIgnore)` holds, then reads `label := util.UnescapeHTML(span.Content)`. `CheckPathAccessableByPublishIgnore` (`kernel/model/publish_access.go:183`) consults the invisible set only. It returns true for any visible document, including one carrying a publish password. The project's complete gate is `checkBlockTreeAccessableByPublishAccess` (`kernel/model/publish_access.go:225`), which ANDs that same visibility test with `password == "" || CheckPublishAuthCookie(...)`. The tag filter implements the first half and omits the second. The disclosed value is user-authored content, not a structural identifier: `span.Content` is the tag text an author wrote inside the document project codenames, personal names, client names, subject matter and the accompanying count reveals how heavily each appears. **Established as a defect by the project's own remediation.** Commit `82e9ded42` replaced the graph endpoint's visible-only `FilterGraphByPublishIgnore` with a filter that additionally resolves the document's publish password and validates `CheckPublishAuthCookie`. The reasoning that motivated that change applies unchanged here: same input shape, same visible-only body, same class of leaked material. `FilterTagsByPublishIgnore` retains the pre-fix pattern. **Verified unfixed.** At `v3.7.4-alpha.1`, `getTag` still calls `FilterTagsByPublishIgnore`, and that function's body still tests only `CheckPathAccessableByPublishIgnore`. No commit on the development branch touches `kernel/api/tag.go` or the tag filter (`git log` and `git log -S` both empty over that range). **Scope note on the remaining visible-only call sites.** Two further consumers of the `*ByPublishIgnore` tier were reviewed and are not claimed as vulnerabilities in this report: `listDocsByPath` (`kernel/model/filetree.go:1160`), which exposes document titles and is plausibly intentional for navigation, and the identifier-only filter, which returns no authored content. They are noted because the visible-only tier is now a repeated source of defects, and an audit of every `*ByPublishIgnore` call site is the durable remedy rather than a third individual patch. ### Proof of Concept Precondition: publish mode enabled (default port 6808); a published document that carries a publish password and contains at least one tag; a reader session that has not authenticated to that document. ``` POST http://127.0.0.1:6808/api/tag/getTag {} → 200 The returned tag tree includes labels sourced from the password-protected document, with counts, despite the session holding no publish auth cookie for it. ``` Differential check: request the document itself through a password-gated endpoint with the same session and observe that access is refused, while its tag labels remain present in the `getTag` response. ### Impact An anonymous reader in publish mode or any publish `RoleReader` enumerates the tag vocabulary of documents they are not authorized to open, along with usage counts. Tags are author-written free text, so this discloses subject matter, names and internal terminology from documents whose contents the publish password was configured to protect. Confidentiality-only. ### Suggested fix Replace `FilterTagsByPublishIgnore` with a password-aware filter mirroring the one introduced in `82e9ded42` for graph nodes: resolve each span's document password via `GetPathPasswordByPublishAccess` and require `CheckPublishAuthCookie` before the label is counted, in addition to the existing visibility test. More durably, audit | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | CVE-2026-72791siyuan · Missing Authorization Medium6.9EPSS 0.3%CVE Database V5CVE-2026-72791 SiYuan v3.7.4-alpha.1 (a pre-release; the endpoint does not exist in stable v3.7.3 or earlier) contains an information disclosure vulnerability in the /api/av/getAttributeViewFieldViews endpoint. The route is registered with CheckAuth only and applies no publish-access filtering, so reader-role callers can retrieve the complete database view structure — every view's name, icon, layout type, and per-field Hidden flag — for any database whose avID is supplied, regardless of authorization. The issue was introduced by commit acfc02ee8 and fixed in v3.7.4. | CVE Database V5 |
Showing 101–120 of 206 results