SiYuan's Sticky Fingers: When 'Copy File' Becomes 'Steal Everything'
Jan 21, 2026·6 min read·4 visits
Executive Summary (TL;DR)
SiYuan Note's API endpoint for copying files failed to validate source paths. An attacker could request the server to copy `/etc/passwd` or `C:\Windows\win.ini` into a public asset directory, effectively turning a file management feature into a full system read primitive. The fix involves a blacklist, which... well, we'll get to that.
A critical logic flaw in SiYuan Note's file handling allows authenticated users to read arbitrary files from the underlying server filesystem. By abusing the 'globalCopyFiles' API, attackers can pull system secrets like SSH keys and password hashes directly into their workspace.
The Hook: Your Knowledge Base Knows Too Much
Personal Knowledge Management (PKM) systems like SiYuan are designed to be the 'second brain' for their users. They store your notes, your diagrams, and your deepest secrets. But in CVE-2026-23851, we find that SiYuan got a little too greedy. It didn't just want to store your secrets; it decided it was perfectly entitled to the server's secrets as well.
The vulnerability lies in a seemingly mundane feature: file management. Specifically, the ability to copy files into your workspace. In a local desktop app, this is harmless convenience. But SiYuan is often deployed as a self-hosted web service (Docker, etc.). When you blur the line between 'desktop app logic' and 'web server security', you usually end up with a disaster. In this case, that disaster is a high-severity Arbitrary File Read.
This isn't a complex memory corruption bug where we have to massage the heap. This is a logic flaw pure and simple. It's the digital equivalent of a bank teller who, when asked to transfer money from 'The Vault' to 'My Pocket', checks if the vault exists, nods, and hands you the cash without asking if you actually own the vault.
The Flaw: Trusting the User's Map
The root cause resides in the /api/file/globalCopyFiles endpoint. This API is intended to let users organize their assets—perhaps moving an image from a temporary folder into their main notes directory. The request body takes a list of source paths (srcs) and a destination directory.
Here is the logic failure: The application validated that the file existed (using filelock.IsExist), but it never validated where that file was located. It assumed that if a user provided a path, it must be a valid asset within the application's scope. There was no 'jail' or chroot logic enforcing that the source path started with the workspace directory.
Because SiYuan runs with the permissions of the user starting the process (often root in poorly configured Docker containers, or a standard user on desktops), it has read access to the entire OS filesystem. When the application receives a command to copy /etc/shadow, it obediently checks "Does /etc/shadow exist?" (Yes), and then proceeds to copy it to the user's web-accessible asset folder. From there, downloading the stolen file is trivial.
The Code: The Smoking Gun
Let's look at the crime scene in kernel/api/file.go. The vulnerable code was startlingly trusting. It iterates through the user-provided list of paths and processes them without sanitization.
Vulnerable Logic
// The 'Before' Code
func globalCopyFiles(c *gin.Context) {
// ... argument parsing ...
for _, src := range srcs {
// The only check: Does the file exist?
if !filelock.IsExist(src) {
// handle error
}
// PROCEED TO COPY
// No check if 'src' is inside the workspace!
}
}The 'Fix'
The developers patched this in commits b2274ba and f8f4b51. Instead of implementing a strict whitelist (e.g., "Source must start with /opt/siyuan/workspace"), they opted for a blacklist approach. They introduced a utility util.IsSensitivePath.
// The 'After' Code
for i, src := range srcs {
absSrc, _ := filepath.Abs(src)
// The new security gate
if util.IsSensitivePath(absSrc) {
// Log error: "refuse to copy sensitive file"
return
}
// Proceed...
}And what does IsSensitivePath do? It checks the path against a hardcoded list of "naughty" strings: /etc/passwd, /root, .ssh, .env, etc. As any security researcher knows, blacklists are like playing Whac-A-Mole with a hydra.
The Exploit: Stealing the Keys to the Castle
Exploiting this is trivially easy once you have authentication. The attack vector is a standard HTTP POST request. You don't need special tools; curl or Burp Suite is enough.
Attack Chain
- Login: Authenticate to the SiYuan web interface to get your API token or session cookie.
- Target: Identify a sensitive file. On Linux,
/etc/passwdor/root/.ssh/id_rsa. On Windows,C:\Windows\win.ini. - Fire: Send the malicious JSON payload.
POST /api/file/globalCopyFiles HTTP/1.1
Host: vulnerable-siyuan.local
Content-Type: application/json
Cookie: session=...
{
"srcs": ["/etc/passwd", "/proc/self/environ"],
"destDir": "/public/assets/stolen_loot"
}The Result
The server responds with 200 OK. The file /etc/passwd is now sitting inside your SiYuan notebook assets. You simply browse to /assets/stolen_loot/passwd in your browser to download it. If you grab /proc/self/environ, you might find AWS keys, database credentials, or API secrets passed to the container.
[!NOTE] Researcher's View: The scariest part of this exploit is its reliability. There are no race conditions or memory offsets. It works 100% of the time on vulnerable versions.
The Mitigation: A Band-Aid on a Bullet Hole?
The official fix in version 3.5.4 is to upgrade. The developers added the IsSensitivePath check. If you are running an older version, you are exposed.
Remediation Steps
- Upgrade: Pull the latest Docker image or update the desktop client immediately.
- Network Segmentation: Why is your Knowledge Base exposed to the internet? Put it behind a VPN or a reverse proxy with Basic Auth (double authentication).
- Container Hardening: Don't run SiYuan as root. Map the container volumes carefully. If the process can't read
/etc/shadowat the OS level, the application vulnerability matters less.
A Critique of the Fix
I have to be critical of the patch method here. Blacklisting is historically fragile. The current patch blocks specific extensions (.pem, .key, .gpg) and specific directories (/etc, /root).
Re-exploitation Potential:
- Symlinks: Does the check resolve symlinks properly before checking the blacklist? If I create a symlink
mysymlink -> /etc/passwdinside the workspace and ask to copy that, does it block it? - Obscure Files: What about application logs in
/var/log? What about/home/user/.bash_history? If it's not on the blacklist, it's fair game. - Procfs: While some things are blocked, Linux's
/procfilesystem is a treasure trove of information that is hard to fully blacklist.
The robust fix would have been to enforce a root directory (Chroot/Jail) and reject any path that resolves outside of the defined workspace. Until then, we trust the blacklist.
Official Patches
Fix Analysis (2)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
SiYuan siyuan-note | < 3.5.4 | 3.5.4 |
| Attribute | Detail |
|---|---|
| CWE | CWE-22 (Path Traversal) |
| CVSS v4.0 | 8.3 (High) |
| Attack Vector | Network (Authenticated) |
| Impact | High (Confidentiality) |
| Patch Status | Fixed in v3.5.4 (Blacklist implementation) |
| EPSS Score | 0.04% (Low immediate mass-exploit risk) |
MITRE ATT&CK Mapping
The application does not properly validate input paths, allowing access to files outside the restricted directory.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.