Skip to main content

SiYuan CVE-2026-45148

| EUVDEUVD-2026-30353 MEDIUM
Incorrect Authorization (CWE-863)
2026-05-13 https://github.com/siyuan-note/siyuan GHSA-fmh9-gpqh-g53g
4.3
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
4.3 MEDIUM
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 08, 2026 - 11:17 vuln.today
Analysis Generated
Jun 08, 2026 - 11:17 vuln.today

DescriptionGitHub Advisory

Summary

The advisory GHSA-c77m-r996-jr3q patched getBookmark so that, when invoked by a publish-mode RoleReader, results are filtered through FilterBlocksByPublishAccess to remove entries from password-protected / publish-ignored notebooks. Four sibling search handlers in the same file did not receive the equivalent treatment and continue to expose metadata across the publish-access boundary.

Details

Affected files / lines (v3.6.5):

kernel/api/router.go:181-190 - all four endpoints registered with CheckAuth only, which the publish-service RoleReader JWT passes:

go
ginServer.Handle("POST", "/api/search/searchTag",      model.CheckAuth, searchTag)
ginServer.Handle("POST", "/api/search/searchTemplate", model.CheckAuth, searchTemplate)
ginServer.Handle("POST", "/api/search/searchWidget",   model.CheckAuth, searchWidget)
ginServer.Handle("POST", "/api/search/searchAsset",    model.CheckAuth, searchAsset)

kernel/api/search.go - none of the four handlers branches on model.IsReadOnlyRoleContext(c) to filter the response, while their *peers* in the same file do. Compare:

go
// :29-65  listInvalidBlockRefs - DOES filter:
if model.IsReadOnlyRoleContext(c) {
    publishAccess := model.GetPublishAccess()
    blocks = model.FilterBlocksByPublishAccess(c, publishAccess, blocks)
}

// :67-93  getAssetContent - DOES filter (FilterAssetContentByPublishAccess)
// :95-115 fullTextSearchAssetContent - DOES filter
// :250-285 getEmbedBlock - DOES filter (FilterEmbedBlocksByPublishAccess)

// :156-176 searchAsset - does NOT filter
ret.Data = model.SearchAssetsByName(k, exts)

// :178-196 searchTag - does NOT filter
tags := model.SearchTags(k)
ret.Data = map[string]any{"tags": tags, "k": k}

// :198-213 searchWidget - does NOT filter
widgets := model.SearchWidget(keyword)

// :233-248 searchTemplate - does NOT filter
templates := model.SearchTemplate(keyword)

model.SearchAssetsByName, model.SearchTags, model.SearchWidget, model.SearchTemplate operate over the entire workspace database / filesystem, not just the publish-visible subset. A FilterTagsByPublishIgnore helper *already exists* in kernel/model/ and is used by getTag itself (kernel/api/tag.go:58-62), confirming the maintainers' intent.

PoC

End-to-end reproduction requires enabling the SiYuan publish service, marking one notebook as private to publish access, and obtaining a RoleReader JWT from the publish reverse-proxy (per kernel/server/proxy/publish.go). Once authenticated as the Reader against the publish port:

bash
# Returns ALL tags across the workspace, including ones drawn only from the publish-private notebook.
curl -X POST https://<publish-host>/api/search/searchTag \
     -H 'Authorization: Bearer <reader-jwt>' \
     -H 'Content-Type: application/json' \
     -d '{"k":""}'
# Returns ALL asset filenames (e.g., CV.pdf, contract.docx, salary-2026.xlsx) regardless of source notebook.
curl -X POST https://<publish-host>/api/search/searchAsset \
     -H 'Authorization: Bearer <reader-jwt>' \
     -H 'Content-Type: application/json' \
     -d '{"k":""}'

curl -X POST https://<publish-host>/api/search/searchWidget   -H '...' -d '{"k":""}'
curl -X POST https://<publish-host>/api/search/searchTemplate -H '...' -d '{"k":""}'

Each call returns the global result set without applying FilterTagsByPublishIgnore / FilterAssetContentByPublishAccess / equivalent.

In this audit I source-confirmed the missing branch in v3.6.5 but did not stand up the full publish-service flow. The fix is straightforward enough that the source-level evidence should be sufficient for triage.

Impact

A publish-service Reader (the role assigned to anonymous publish visitors by default) can enumerate:

  • All tag strings used anywhere in the workspace - frequently contains person names, project codenames, internal identifiers.
  • All asset filenames uploaded to the workspace - frequently contains the contents of CV.pdf, contract.docx, salary-2026.xlsx, etc.
  • All widget names and template names installed in the workspace.

This violates the publish-service trust boundary. Users intentionally mark notebooks as "invisible to publish" specifically to keep this metadata out of public reach.

AnalysisAI

Broken access control in SiYuan's publish-service API allows authenticated RoleReader principals to enumerate workspace metadata from notebooks explicitly marked as private or publish-ignored. Four search endpoints - /api/search/searchTag, /api/search/searchAsset, /api/search/searchWidget, and /api/search/searchTemplate - pass the CheckAuth middleware that a publish-service RoleReader JWT satisfies, but never apply the IsReadOnlyRoleContext filtering logic that sibling endpoints in the same file correctly implement, returning unscoped global workspace results. A proof-of-concept exploit exists per SSVC assessment and is documented in the advisory; the vulnerability is not currently listed in CISA KEV.

Technical ContextAI

SiYuan is a self-hosted, local-first personal knowledge management system with a Go backend kernel (CPE: pkg:go/github.com_siyuan-note_siyuan_kernel). Its publish service exposes selected notebooks via a reverse proxy that issues RoleReader JWTs to anonymous visitors. The CWE-863 (Incorrect Authorization) root cause is an incomplete backport of publish-access filtering logic: sibling handlers in kernel/api/search.go - including listInvalidBlockRefs, getAssetContent, fullTextSearchAssetContent, and getEmbedBlock - correctly branch on model.IsReadOnlyRoleContext(c) and invoke FilterBlocksByPublishAccess or equivalent helpers before returning results. The four affected handlers (searchAsset at lines 156-176, searchTag at 178-196, searchWidget at 198-213, searchTemplate at 233-248) call backend model functions (SearchAssetsByName, SearchTags, SearchWidget, SearchTemplate) that query the entire workspace database and filesystem with no per-caller scoping. Critically, a FilterTagsByPublishIgnore helper already exists in kernel/model/ and is used by getTag in kernel/api/tag.go:58-62, confirming the filtering infrastructure existed but was not wired to these four endpoints. The router (kernel/api/router.go:181-190) registers all four with CheckAuth only, which a valid RoleReader JWT satisfies.

RemediationAI

Upgrade SiYuan to version 3.7.0 or later, which introduces the missing IsReadOnlyRoleContext filtering branches across the four affected endpoints. The vendor security advisory is available at https://github.com/siyuan-note/siyuan/security/advisories/GHSA-fmh9-gpqh-g53g. For users unable to upgrade immediately, the most effective compensating control is disabling the publish service entirely, which prevents RoleReader JWTs from being issued and eliminates the attack surface with no impact on local workspace access. As a more surgical alternative, blocking requests to the four specific paths (/api/search/searchTag, /api/search/searchAsset, /api/search/searchWidget, /api/search/searchTemplate) at the reverse-proxy layer (e.g., nginx location deny rules) prevents exploitation while leaving the broader publish service operational, at the cost of degraded search UX for legitimate publish visitors. Restricting publish-service network exposure to trusted IP ranges or requiring additional authentication at the proxy layer further reduces risk without requiring application changes.

Share

CVE-2026-45148 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy