obsidian-local-rest-api: Authenticated Path Traversal via Percent-Encoded Slash in /vault/{path}
A bug in the Obsidian Local REST API plugin lets an authenticated caller read, write, or delete any file on the host machine by sneaking encoded slashes (%2F) past the URL router and into the file…
The problem
Every /vault/{path} endpoint (GET, PUT, PATCH, POST, DELETE) calls decodeURIComponent on the path segment inside the handler, after Express has already routed the request. Express sees %2F as a literal percent-encoded character, not a separator, so it does not collapse or reject ..%2F sequences during routing.
Once the handler decodes the path, ..%2F..%2F becomes ../../, which walks outside the vault root. The decoded path is passed directly to Obsidian vault adapter calls (readBinary, getAbstractFileByPath, etc.) with no posix.resolve confinement check, giving an authenticated client arbitrary file read, write, and delete at the OS privileges of the Obsidian process.
Proof of concept
A working proof-of-concept for this issue in obsidian-local-rest-api, with the exact payload below.
# READ: returns /etc/passwd bytes with HTTP 200
curl --path-as-is -k \
-H "Authorization: Bearer $API_KEY" \
"https://127.0.0.1:27124/vault/..%2F..%2F..%2F..%2Fetc%2Fpasswd"
# WRITE: creates a file outside the vault root
curl --path-as-is -k -X PUT \
-H "Authorization: Bearer $API_KEY" \
--data "pwned" \
"https://127.0.0.1:27124/vault/..%2F..%2F..%2Ftmp%2Fcanary.txt"
# NOTE: a literal ../ (unencoded) returns 404 -- only the %2F form bypasses routing.The root cause is a decode-after-routing gap (CWE-22). The string Express routes on (..%2F..%2F) is not the string the handler operates on (../../), so the router's path-normalization never sees the traversal.
The fix, already present in vaultMove and now applied to all vault handlers, resolves the decoded path against a synthetic vault root using posix.resolve(syntheticRoot, decoded) and rejects any result that does not start with syntheticRoot + '/'. This makes traversal impossible regardless of encoding variant (%2F, %2e%2e, or combinations).
The fix
Upgrade obsidian-local-rest-api to 4.1.3 or later. The patch applies a posix.resolve confinement check to every vault handler (GET, PUT, PATCH, POST, DELETE, MOVE) and returns ErrorCode.PathTraversalNotAllowed on any path that resolves outside the vault root.
No configuration change is required after upgrading.
Reported by Caleb Brisbin (@AgenticWizard).
Related research
- high · 7.1CVE-2026-54545CVE-2026-54545: @wakaru/cli Arbitrary File Write via Path Traversal in --unpack
- highCVE-2026-55607CVE-2026-55607: @anthropic-ai/claude-code Sandbox Escape via Git Worktree Path Confusion
- high · 7.5CVE-2026-15074CVE-2026-15074: @fastify/static Route Guard Bypass via Dot-Dot Path Traversal
- high · 7.5PostCSS Path Traversal via sourceMappingURL Leads to Arbitrary .map File Disclosure