CSIRTS // UNIFIED SECURITY ADVISORY FEEDSYS ● ONLINE · POWERED BY INTELFUSIONS.COM

GHSA-62gx-5q78-wrvx: obsidian-local-rest-api: Authenticated path traversal via URL-encoded %2F in /vault/{path} — arbitrary host file read/write/delete

highCVSS 8.8
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

Source
GitHub Security Advisories (INTL · database · site)
Severity
high — CVSS 8.8
Published
2026-07-15
Last updated
2026-07-15
Exploitation
Not in CISA KEV at last sync

Original advisory: https://github.com/advisories/GHSA-62gx-5q78-wrvx

More from GitHub Security Advisories