Case-Sensitive Chaos: Bypassing SiYuan Note's Security with a Capital Letter
Jan 28, 2026·5 min read·1 visit
Executive Summary (TL;DR)
Developers utilized case-sensitive string comparisons in Go to protect 'conf.json'. On Windows/macOS, 'Conf.json' is the same file but a different string. Attackers can bypass the blacklist and read the configuration file containing secrets.
SiYuan Note, a privacy-first personal knowledge management tool, suffered from a classic disconnect between rigid programming logic and lax filesystem standards. By failing to account for case-insensitivity on Windows (NTFS) and macOS (APFS), the kernel allowed attackers to bypass a blacklist designed to protect critical configuration files. A simple change from 'conf.json' to 'Conf.json' was all it took to exfiltrate sensitive secrets.
The Hook: Privacy-First, Security-Maybe?
SiYuan Note pitches itself as a privacy-first, local-first knowledge base. It's the kind of tool used by people who don't trust the cloud—security-conscious folks, developers, and writers. The application runs a local kernel (server) that handles file operations, exposing an API endpoint /api/file/getFile to fetch note data.
The developers knew that some files were too dangerous to share. Specifically, conf.json. This file holds the keys to the kingdom: authentication tokens, sync credentials (potentially AWS S3 keys for backups), and system paths. Naturally, they wrote code to block access to it. But in the world of cross-platform development, assuming the file system behaves the way you want it to is a fatal error.
This vulnerability is a perfect example of 'Logic vs. Reality.' In the logical world of Go, "A" does not equal "a". In the messy reality of Windows NTFS or macOS APFS, "A" is just "a" with a hat on. And that discrepancy is where we live.
The Flaw: A Tale of Two Filesystems
The root cause here is CWE-178: Improper Handling of Case Sensitivity. It’s a bug class that has existed since the dawn of Windows NT, yet it keeps claiming victims in modern software. The SiYuan kernel implemented a blacklist (always a red flag) to protect specific files.
Here is the logic they roughly employed:
// Pseudo-code of the vulnerable logic
if requestedPath == "conf.json" {
return Error("Forbidden")
}
serveFile(requestedPath)On a Linux server (case-sensitive), this is fine. Conf.json doesn't exist, so requesting it returns a 404. conf.json is blocked. Secure.
But on Windows, the file system is case-insensitive but case-preserving. If an attacker requests Conf.json:
- The Go runtime says:
"Conf.json" != "conf.json". The check passes. - The OS says: "You want
Conf.json? Sure, here is the file descriptor for that file on disk (which happens to beconf.json)."
It’s like a bouncer checking a guest list for "John". You show up with a nametag saying "JOHN", and he lets you in because "names are case-sensitive in my rulebook," completely ignoring that you are the same person.
The Code: The Smoking Gun
Let's look at the fix, which inadvertently highlights just how broken the original logic was. The patch was pushed in commit 1f02650b3892d2ea3896242dd2422c30bda55e11. The developers introduced a dedicated refuseToAccess function in kernel/api/file.go.
Before the patch, the check was a loose string comparison against the input path. The patch attempts to formalize this by checking absolute paths, but it still relies on Go's strict equality operators.
The Patch Logic:
func refuseToAccess(c *gin.Context, fileAbsPath string, ret *gulu.Result) bool {
// Forbidden access to config file conf/conf.json
if filepath.Join(util.ConfDir, "conf.json") == fileAbsPath {
return true
}
// ... checks for snippets and templates ...
return false
}> [!NOTE]
> Critical Analysis: Even this patch is conceptually shaky on Windows. Unless fileAbsPath has been fully canonicalized (e.g., via filepath.EvalSymlinks or Windows API calls to get the "true" name), fileAbsPath might still carry the attacker's casing. If the attacker sends .../Conf.json, fileAbsPath might end in Conf.json. The check compares it against filepath.Join(..., "conf.json"). Since Conf.json != conf.json, the blacklist might still fail depending on how the upstream code resolves the absolute path.
The Exploit: Asking Politely
Exploiting this does not require shellcode, heap spraying, or complex ROP chains. It requires curl and a Shift key. This is trivial exploitation at its finest.
Scenario: You identify a SiYuan instance running in "publish" mode on a Windows server.
Step 1: The Standard Request (Blocked)
curl "http://target:6806/api/file/getFile?path=conf.json"
# Output: {"code": 403, "msg": "Forbidden"}Step 2: The Bypass (Success)
curl "http://target:6806/api/file/getFile?path=Conf.json"
# Output: {"conf": {"repo": {...}, "sync": {"s3": {"accessKey": "AKIA..."} ...}}}By simply capitalizing the 'C', we walk right past the security guard. The kernel reads the file because the OS allows it, and the API returns the JSON content. We now have the cloud storage credentials and can pivot to attacking their backups.
The Impact: Why This Hurts
While SiYuan is a note-taking app, it often stores highly sensitive intellectual property, personal journals, or technical documentation. The conf.json file is the master key.
Exposure Radius:
- Cloud Credentials: Many users configure S3 or WebDAV for syncing. These credentials (Access Keys, Secrets) are stored in plain text in the config.
- System Paths: The config reveals the directory structure of the host, aiding further traversal attacks.
- Snippets & Templates: The vulnerability also exposes
data/snippetsanddata/templates. If the user stores secrets in custom script snippets, those are gone too.
For a "privacy-focused" tool, this is a catastrophic failure of its primary promise. It turns the application into an open file server for anyone who knows how to spell "Conf".
The Fix: A Band-Aid or a Cure?
The official fix in version 2.12.x (specifically commit 1f02650b3892d2ea3896242dd2422c30bda55e11) adds the checks we discussed. However, as noted in the analysis, developers working in Go on Windows must be extremely careful with path normalization.
For Users: Update immediately. If you are running SiYuan as a public-facing service (Docker or direct binary), ensure you are on the latest version. Do not expose the kernel port to the raw internet without a reverse proxy (Nginx/Caddy) handling authentication, as SiYuan's built-in protections have historically been... optimistic.
For Developers:
Stop using blacklists for file access control. Use whitelists. Define exactly what can be accessed (e.g., *.md files in specific folders) and block everything else. Furthermore, when dealing with paths, always resolve to the canonical representation on disk (using filepath.EvalSymlinks and OS-specific realpath equivalents) before performing security checks.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
SiYuan Note SiYuan | < Commit 1f02650b | Commit 1f02650b3892d2ea3896242dd2422c30bda55e11 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-178 (Case Sensitivity) |
| Attack Vector | Network (API) |
| CVSS (Est.) | 7.5 (High) |
| Platform | Windows / macOS |
| Impact | Sensitive Data Exposure |
| Fix Status | Patched (Commit 1f02650b) |
MITRE ATT&CK Mapping
The software performs a security check based on a case-sensitive comparison of a filename, but the underlying filesystem is case-insensitive, allowing attackers to bypass the check.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.