6 min read

SiYuan: Self-Hosted, Privacy-First Knowledge Base (GitHub, Scanned)

A self-hostable, privacy-first notes workspace with block references, Markdown WYSIWYG and AI.

SiYuan logo
✅
Scan: safe. Nothing malicious. The notes: a Docker install is only as safe as the access code you set, and the AI, web-fetch and paid sync features contact outside services when you turn them on. Scanned Oct 26, 2026; the full report is below.

SiYuan is a personal knowledge base that treats everything as blocks: any paragraph, heading or list item can be referenced, embedded, linked both ways and queried with SQL. The editor is WYSIWYG Markdown with outlines, maths, charts, flowcharts and Gantt charts, and around it sit PDF annotation with links, web clipping, flashcards with spaced repetition, a database table view, OCR, and AI writing and Q&A through an OpenAI-compatible API.

It runs on Windows, macOS, Linux, Android, iOS and HarmonyOS, or as a Docker container you open in a browser, and a built-in command-line tool can search and export a workspace directly. Data lives in a workspace folder as JSON .sy files plus your assets. It is AGPL-3.0, with a Go kernel and a TypeScript interface, and has about 47,000 stars, at v3.8.8 when scanned.

It is freemium: most features are free, even for commercial use, but some, including connecting third-party cloud storage for sync, need a paid membership. Do not keep the workspace in a Dropbox-style sync folder, which the README warns can corrupt data, and note that the Docker version is browser-only and cannot export to PDF, HTML or Word.

  • Repository: github.com/siyuan-note/siyuan
  • Licence: AGPL-3.0 (GNU Affero General Public License v3.0)
  • Language: TypeScript. Stars: 46.9K. Forks: 3,090. Last push: Oct 26, 2026.
  • Scan: safe, Oct 26, 2026, commit bfe3610

Who it is for

Note-takers who want Roam or Notion-style linked blocks without handing their notes to a cloud service, and home-lab users who want a notes app on their own server.

Getting started

1. Self-host with Docker (replace xxx with a strong lock-screen password; it protects your notes)

docker run -d -v /siyuan/workspace:/siyuan/workspace -p 6806:6806 b3log/siyuan serve --workspace=/siyuan/workspace/ --accessAuthCode=xxx

2. Search a workspace from the built-in CLI

siyuan search "keyword" -w ~/SiYuan -f json

Then open http://localhost:6806. For the desktop and mobile apps, the project recommends the App Store, Google Play, F-Droid or the Microsoft Store so updates are one click; installers are also on the GitHub releases page. For remote access, put the container behind an HTTPS reverse proxy; OIDC sign-in can replace the access code.

Safety scan

We cloned siyuan-note/siyuan at commit bfe3610 on Oct 26, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.

  • The 3 secret hits are the text -----BEGIN PRIVATE KEY----- used as a marker, in kernel/bazaar/plugin_project.go and two test files; there is no real key. The 16 bare-IP URLs are all in Go tests, using 8.8.8.8 and the reserved documentation ranges 192.0.2.1 and 203.0.113.10 to test the web-fetch tool and login sessions.
  • The 2 pattern hits are long lines in bundled front-end code: highlight.js language definitions and the editor's own protyle-html.js.
  • 3 committed binaries: app/elevator/elevator-amd64.exe and elevator-arm64.exe, a small Windows helper for actions that need elevated rights, built from the project's own siyuan-note/elevator repository (the folder's README links to it), and nsProcess.dll, a standard plugin for the NSIS Windows installer.
  • 113 known advisories: 89 in app/pnpm-lock.yaml (1 critical, a node-tar decompression issue) and 24 in kernel/go.mod (9 high). The npm ones are dominated by XML and archive parsers such as xmldom and node-tar, typical of Electron packaging tools.
  • 7 workflows. One uses pull_request_target (target-branch.yml), and it only moves new pull requests to the dev branch and leaves a comment; it does not check out the submitted code. None of 16 third-party actions is pinned to a commit. Security policy, Dependabot, contributing guide and code of conduct present. OpenSSF Scorecard 5 of 10.

What the scanner counted

CheckResult
Secrets3 candidates found and read; see the notes above.
Suspicious code2 pattern hits found and read; every one is listed under the raw findings.
Install-time codeNone: nothing runs at install beyond the package manager itself.
Committed binaries3 executable or compiled objects committed; listed under the raw findings.
CI workflows7 workflows. 1 uses pull_request_target, none check out the pull request head. 16 of 16 third-party actions pinned to a tag rather than a commit.
Network hosts40 distinct hosts referenced from source; most often github.com, www.gnu.org, ld246.com, www.apache.org. 16 URLs to a bare IP address, listed under the raw findings.
Known vulnerabilities113 advisories across 938 pinned packages: 1 critical, 74 high, 26 moderate, 4 low, 8 unrated. app/pnpm-lock.yaml: 708 packages, 89 advisories; kernel/go.mod: 230 packages, 24 advisories.
Project hygieneHas security policy, automated dependency updates, licence file, contributing guide. Missing CodeQL.
OpenSSF Scorecard5.0 out of 10, as of Oct 19, 2026.

The raw findings

Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.

Secret candidates (3, redacted)
WhereRuleMatch
kernel/bazaar/plugin_project.go:70private-key-----B…--- (27 chars)
kernel/bazaar/plugin_project_test.go:123private-key-----B…--- (35 chars)
kernel/util/path_guard_test.go:155private-key-----B…--- (30 chars)
Pattern hits (2)
WhereRuleMatch
app/stage/protyle/js/highlight.js/third-languages.js:113very-long-line4154 chars
app/stage/protyle/js/protyle-html.js:2very-long-line23035 chars
URLs to bare IP addresses (16)
WhereRuleMatch
kernel/mcp/tools/web_test.go:33ip-literal-urlfetch, _ := webFetchHandler(context.Background(), map[string]any{"url": "https://8.8.8.8"})
kernel/mcp/tools/web_test.go:39ip-literal-urlfetch, _ = webFetchHandler(context.Background(), map[string]any{"url": "https://8.8.8.8"})
kernel/model/session_test.go:276ip-literal-urlloginRequest := httptest.NewRequest(http.MethodGet, "http://192.0.2.1:6806/login", nil)
kernel/model/session_test.go:285ip-literal-urlrequest := httptest.NewRequest(http.MethodPost, "http://192.0.2.1:6806/api/notebook/lsNotebooks", nil)
kernel/model/session_test.go:296ip-literal-urlif recorder := request("http://192.0.2.1:6806"); recorder.Code != http.StatusNoContent {
kernel/model/session_test.go:438ip-literal-url{name: "authenticated same-origin API", method: http.MethodPost, path: "/api/test", origin: "http://192.0.2.1:6806", site: "same-origin", authenticated: true, w…
kernel/util/deepseek_web_test.go:105ip-literal-urlif request.URL.Path != "/v1/tools/fetch" || len(payload) != 1 || payload["url"] != "https://8.8.8.8/page" {
kernel/util/deepseek_web_test.go:109ip-literal-urltext, err := DeepSeekWebFetch(context.Background(), DeepSeekWebOptions{APIKey: "test-key"}, "https://8.8.8.8/page", format)
kernel/util/deepseek_web_test.go:124ip-literal-urlif _, err := DeepSeekWebFetch(context.Background(), DeepSeekWebOptions{APIKey: "test-key"}, "https://8.8.8.8", "markdown"); err == nil {
kernel/util/deepseek_web_test.go:174ip-literal-urlmarkdown, err := DeepSeekWebFetch(context.Background(), DeepSeekWebOptions{APIKey: "test-key"}, "https://8.8.8.8", "markdown")
kernel/util/deepseek_web_test.go:178ip-literal-urltext, err := DeepSeekWebFetch(context.Background(), DeepSeekWebOptions{APIKey: "test-key"}, "https://8.8.8.8", "text")
kernel/util/deepseek_web_test.go:195ip-literal-urltext, err := DeepSeekWebFetch(context.Background(), DeepSeekWebOptions{APIKey: "test-key"}, "https://8.8.8.8", "text")
kernel/util/web_timeout_test.go:55ip-literal-urlif _, err := WebFetchContext(ctx, "https://8.8.8.8", "markdown", input); err != nil {
kernel/util/web_timeout_test.go:64ip-literal-urlif _, err := DeepSeekWebFetch(ctx, options, "https://8.8.8.8", "markdown"); err != nil {
kernel/util/web_timeout_test.go:93ip-literal-url_, err = WebFetchContext(ctx, "https://8.8.8.8", "markdown", 600)
kernel/util/webfetch_test.go:133ip-literal-urltext, err := WebFetch("https://203.0.113.10/", "markdown")
Committed binaries (3)
  • app/elevator/elevator-amd64.exe: PE (Windows executable), 4 MB
  • app/elevator/elevator-arm64.exe: PE (Windows executable), 4 MB
  • app/nsis-resources/x86-unicode/nsProcess.dll: PE (Windows executable), 5 KB
Worst known vulnerabilities (24 of 113)
AdvisorySeverityPackageSummary
GHSA-23hp-3jrh-7fpwcriticaltar@7.5.16node-tar: Decompression/parse DoS via unlimited input
GHSA-27p8-2357-5qqvhigh@xmldom/xmldom@0.8.13xmldom: DocType `name` Injection Bypasses requireWellFormed
GHSA-4w3w-2rp5-g8jmhigh@xmldom/xmldom@0.8.13xmldom: Attribute name injection via setAttribute() bypasses requireWellFormed
GHSA-8344-3jmq-59r6high@xmldom/xmldom@0.8.13xmldom: Quadratic-time attribute deduplication
GHSA-93r5-fhx6-vmg9high@xmldom/xmldom@0.8.13xmldom: Quadratic-time parsing via the malformed-input recovery path - `parseElementStartPart` re-scan and `normalize()`…
GHSA-965w-775f-mr7ghigh@xmldom/xmldom@0.8.13xmldom: Quadratic-memory consumption
GHSA-c7q8-3ch8-vqpvhigh@xmldom/xmldom@0.8.13xmldom: Processing Instruction Target Injection Bypasses requireWellFormed
GHSA-w2rr-34g9-rvrjhigh@xmldom/xmldom@0.8.13xmldom: Element name injection via createElement() bypasses requireWellFormed
GHSA-x4fp-j954-r2f4high@xmldom/xmldom@0.8.13xmldom: End-tag Whitespace-Trim Regex ReDoS - quadratic backtracking in the 0.8.x end-tag parser
GHSA-27p8-2357-5qqvhigh@xmldom/xmldom@0.9.10xmldom: DocType `name` Injection Bypasses requireWellFormed
GHSA-3px3-54cx-rmw9high@xmldom/xmldom@0.9.10xmldom: Creation-time XML Name/QName validation is bypassable via an embedded line terminator, allowing injection on the…
GHSA-4w3w-2rp5-g8jmhigh@xmldom/xmldom@0.9.10xmldom: Attribute name injection via setAttribute() bypasses requireWellFormed
GHSA-6mj3-qw4j-hgrwhigh@xmldom/xmldom@0.9.10xmldom: HTML raw-text closing-tag case mismatch causes output amplification
GHSA-8344-3jmq-59r6high@xmldom/xmldom@0.9.10xmldom: Quadratic-time attribute deduplication
GHSA-93r5-fhx6-vmg9high@xmldom/xmldom@0.9.10xmldom: Quadratic-time parsing via the malformed-input recovery path - `parseElementStartPart` re-scan and `normalize()`…
GHSA-965w-775f-mr7ghigh@xmldom/xmldom@0.9.10xmldom: Quadratic-memory consumption
GHSA-c7q8-3ch8-vqpvhigh@xmldom/xmldom@0.9.10xmldom: Processing Instruction Target Injection Bypasses requireWellFormed
GHSA-g53g-w8rj-fmg7high@xmldom/xmldom@0.9.10xmldom PI grammar regex ReDoS: quadratic backtracking on unterminated processing instructions
GHSA-vr34-hp96-76pphigh@xmldom/xmldom@0.9.10xmldom: requireWellFormed DocType publicId/systemId validation is bypassable via an embedded line terminator
GHSA-w2rr-34g9-rvrjhigh@xmldom/xmldom@0.9.10xmldom: Element name injection via createElement() bypasses requireWellFormed
GHSA-3jxr-9vmj-r5cphighbrace-expansion@1.1.14brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups
GHSA-6j4f-fj2g-mc7phighbrace-expansion@1.1.14brace-expansion: DoS via uncontrolled recursion in parseCommaParts causing stack exhaustion
GHSA-mh99-v99m-4gvghighbrace-expansion@1.1.14brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash
GHSA-qhr7-859c-m2p7highbrace-expansion@1.1.14brace-expansion: DoS via uncontrolled recursion on nested brace groups causing stack exhaustion
Workflows worth a look

By the numbers

Stars46.9K
Forks3,090
Contributors130
Commits30.1K
Open issues32
Open pull requests0
Releases1,036
Latest releasev3.8.8
LicenceAGPL-3.0
Main languageTypeScript
Project age6 years
Last pushOct 26, 2026
Tracked files6,160
Lines of code1.2M
Checkout size334 MB

Lines by language: Go 473.8K, TypeScript 392.7K, JSON 103.7K, Markdown 77.2K, JavaScript 74.9K, SCSS 26.4K.

Questions

Is SiYuan free?

Mostly. SiYuan is open source under AGPL-3.0 and most features are free, even for commercial use. Some features, such as using third-party cloud storage for sync, are paid membership privileges; the pricing page on b3log.org lists them.

How does SiYuan store my data?

In a workspace folder on your device: notebooks as folders of .sy files in JSON, plus folders for assets, templates, widgets and plugins. Do not sync that folder with a third-party sync drive, which the project says can corrupt it.

SiYuan or Logseq?

Both are open source, local-first and built on linked blocks. Logseq is outliner-first, centred on a daily journal. SiYuan edits documents in a WYSIWYG Markdown editor and adds SQL queries, database views, flashcards and an official Docker image for self-hosting. Choose by whether you think in bullets or in documents.


This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.