Jan 21, 2026·5 min read·26 visits
SiYuan < 3.5.4 has a feature that downloads remote images referenced in Markdown to the local server for offline storage. It fails to validate the URI scheme, allowing attackers to supply `file://` paths or internal IPs. This tricks the server into reading `/etc/passwd` or internal metadata and saving it as a static asset, which the attacker can then download.
A critical Local File Disclosure (LFD) and Server-Side Request Forgery (SSRF) vulnerability in SiYuan note-taking software allows authenticated attackers to read sensitive files from the host server by abusing the 'local assets' conversion feature.
SiYuan markets itself as a "privacy-first" personal knowledge management system. It's the kind of tool where you store your deepest thoughts, your startup ideas, and maybe your API keys. It runs locally or on a private server, promising that your data belongs to you.
But as any security researcher knows, the more a product screams "privacy," the more ironic its inevitable security failure will be. CVE-2026-23850 is a textbook example of a convenience feature tearing a hole through that privacy promise. It turns the application into a glorified file server for anyone with a login.
This isn't a complex buffer overflow or a race condition. It is a logic flaw born from the desire to make the user experience smoother. The developers wanted to let you save external images locally. They just forgot to check if those "images" were actually system files like /etc/passwd.
The vulnerability lives in a feature called netAssets2LocalAssets. The logic is simple: you write a Markdown document with a link to an image (e.g., ), and the server fetches that image and saves it to your local workspace so you don't lose it if the internet goes down.
The problem? The function netAssets2LocalAssets0 in kernel/model/assets.go trusted the input implicitly. It parses the Markdown, extracts the link, and hands it off to a resource fetcher.
It failed to ask two critical questions:
file:// just as happily as http://).localhost, a private IP, or a sensitive system path).If you passed it file:///etc/shadow, the server reasoned: "Ah, the user wants to save a local copy of this file. I shall read it and place it in the public assets folder for them."
Let's look at the logic flow. The vulnerable code effectively treated any string inside []() or ![]() as a target for retrieval.
Here is a conceptual simplification of what was happening server-side:
// Pseudo-code of the vulnerable logic
func netAssets2LocalAssets(link string) {
// No check for 'file://' prefix!
data, err := readFileOrNetwork(link)
if err == nil {
// Saves content to /data/assets/network-asset-xxx
saveToPublicDir(data)
}
}The fix, introduced in version 3.5.4, didn't actually disable the file:// protocol or implement a robust whitelist. Instead, it introduced a utility function IsSensitivePath in kernel/util/path.go.
This function acts as a massive blocklist (a "deny list"). It explicitly checks if the path contains strings like:
/etc/passwd, /etc/shadow.ssh, id_rsa.env, config.yaml> [!ALERT]
> Researcher Note: Blacklists are notoriously difficult to maintain. While this stops the script kiddie copying a /etc/passwd PoC, it leaves the door open for creative attackers to find files that aren't on the list, or to use the SSRF vector to hit internal metadata endpoints (like AWS 169.254.169.254), which are rarely file paths.
Exploiting this requires an authenticated session, but in many self-hosted instances, users share credentials or use weak auth codes. Here is how we turn a note-taking app into a data exfiltration tool.
/api/filetree/createDocWithMd.
{
"markdown": "[loot](file:///etc/passwd)"
}/api/format/netAssets2LocalAssets on the document ID you just created./etc/passwd, thinks it's downloading an asset, and saves it to /data/assets/ with a name like network-asset-passwd-xyz.txt./api/file/readDir to find the generated filename, then download it directly via the web interface.The impact here is twofold: Local File Disclosure (LFD) and Server-Side Request Forgery (SSRF).
LFD: Attackers can grab configuration files. If SiYuan is running in a Docker container, you might just get container configs. But if it's running on bare metal (common for personal servers), you could grab SSH keys (~/.ssh/id_rsa), the app's own database credentials, or environment variables containing API keys for third-party integrations.
SSRF: Since the validator doesn't block IP addresses, an attacker could force the server to scan the internal network.
[router](http://192.168.1.1/admin) -> Saves the router login page.[cloud](http://169.254.169.254/latest/meta-data/) -> If hosted on AWS/GCP, this could leak the instance IAM role credentials, leading to full cloud account compromise.The vendor patched this in version 3.5.4. The patch (Commits b2274bab and f8f4b517) implements the IsSensitivePath check.
Critique: The fix is a "band-aid." It patches the symptom (reading sensitive files) by blocking specific filenames, rather than addressing the root cause (unrestricted protocol usage). A more robust fix would have been to:
http and https.file://.For now, the blocklist stops the obvious attacks, but savvy researchers will likely be fuzzing that IsSensitivePath function looking for bypasses involving symbolic links, alternative encodings, or unlisted sensitive files.
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| Product | Affected Versions | Fixed Version |
|---|---|---|
SiYuan SiYuan | < 3.5.4 | 3.5.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 |
| Attack Vector | Network |
| CVSS v4.0 | 7.8 (High) |
| CVSS v3.1 | 8.8 (High) |
| Impact | Confidentiality Loss (High) |
| Exploit Status | PoC Available |
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
An integer truncation vulnerability (CWE-197) exists in SQLite before version 3.50.2 during the processing of aggregate queries with more than 32,767 distinct column references. This causes an internal 32-bit counter to truncate to a signed 16-bit integer, producing negative values that cause out-of-bounds heap operations in release builds.
An integer overflow vulnerability in the Windows kernel-mode HTTP driver (HTTP.sys) allows an unauthenticated remote attacker to execute arbitrary code with kernel privileges or cause a Denial of Service via a specially crafted sequence of HTTP request headers.
A memory corruption vulnerability exists in the FTS5 (Full-Text Search 5) extension of SQLite prior to version 3.53.2. An attacker can construct a malicious database file containing corrupt FTS5 page data. Querying this database triggers out-of-bounds reads and heap-based buffer overflows, potentially causing a crash or arbitrary code execution.
A mass assignment vulnerability (CWE-915) in n8n's self-service settings API endpoint (PATCH /me/settings) allows authenticated Single Sign-On (SSO) users to disable SSO enforcement for their accounts by injecting administrative parameters. This bypasses organizational identity provider controls and multi-factor authentication (MFA).
CVE-2026-55699 (also identified as GHSA-4gxm-v5v7-fqc4) is a critical path traversal and arbitrary directory deletion vulnerability in the pnpm package manager. The issue exists because the manifest validation process fails to prevent relative path segments within the package 'bin' keys. When a malicious package containing structured path traversal markers is globally installed and later manipulated, pnpm resolves the target paths through path.join() and passes the resolved paths to a recursive deletion function, resulting in arbitrary directory removal.
A path traversal vulnerability in pnpm stage download allows malicious registries or compromised package manifests to overwrite arbitrary files on the victim's filesystem via unvalidated package name and version fields.