GHSA-62gx-5q78-wrvx: obsidian-local-rest-api: Authenticated path traversal via URL-encoded %2F in /vault/{path} — arbitrary host file read/write/delete
Summary
The Local REST API's /vault/{path} endpoints (GET/PUT/PATCH/POST/DELETE) percent-decode the request path *inside the handler — after* Express has already routed and normalized it, then hand it to the Obsidian vault adapter with no confinement check. A literal ../ is resolved/rejected at the routing layer (→ 404), but %2F is not a separator there, so ..%2F..%2F survives routing and is only turned into a real / by the handler's decodeURIComponent, reconstituting a ../ traversal that walks out of the vault. An authenticated client can read, write, or delete arbitrary files on the host with the Obsidian process's privileges.
Details
Framework: Express (import express from "express"; routes registered via this.api.route("/vault/*")…).
The vulnerable line — in src/requestHandler.ts, every vault handler (vaultGet, vaultPut, vaultPatch, vaultPost, vaultDelete) derives the path like this:
const rawPath = decodeURIComponent(
req.path.slice(req.path.indexOf("/", 1) + 1),
);
The path is decodeURIComponent'd after Express routing. A literal ../ is collapsed/rejected at the routing layer, but %2F isn't a separator there — so ..%2F..%2F reaches the handler intact and this decodeURIComponent turns it into a real ../../. The string routing saw (…%2F…) is not the string the handler uses (…/…), and %2e%2e behaves the same way.
No confinement on the decoded path. The handlers pass rawPath straight to the vault adapter — e.g. this.app.vault.adapter.readBinary(filePath) / this.app.vault.getAbstractFileByPath(filePath) — with no path.resolve + vault-root prefix check, so the reconstituted ../../ escapes.
The fix already exists in your code — for MOVE only. vaultMove confines correctly:
const syntheticRoot = "/vault";
const resolved = posix.resolve(syntheticRoot, normalized);
if (resolved !== syntheticRoot && !resolved.startsWith(syntheticRoot + "/")) {
this.returnCannedResponse(res, { errorCode: ErrorCode.PathTraversalNotAllowed });
return;
}
GET/PUT/PATCH/POST/DELETE lack
Details
Original advisory: https://github.com/advisories/GHSA-62gx-5q78-wrvx
More from GitHub Security Advisories
- mediumGHSA-jr6p-8pjj-mfx6: Capsule has an incomplete fix of CVE-2026-22872: TenantResource RawItems and Generators s…2026-07-31
- mediumGHSA-68cj-mvg9-rgm2: Capsule: CapsuleConfiguration NodeMetadata regex fields lack webhook validation, allowing…2026-07-31
- mediumGHSA-ff84-5f28-78qj: re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex`…2026-07-31
- mediumGHSA-6hxr-mr5r-9836: re2: Global `String.prototype.match` with an empty-matchable pattern never advances → inf…2026-07-31
- mediumGHSA-x83g-979r-f5fh: Sylius Mollie Plugin has unauthenticated IDOR that leaks order token and customer PII2026-07-31