Skip to content

fix: restrict customLibraries file paths to workspace folder#494

Open
theeggorchicken wants to merge 1 commit intohediet:mainfrom
theeggorchicken:fix/path-traversal-customlibraries
Open

fix: restrict customLibraries file paths to workspace folder#494
theeggorchicken wants to merge 1 commit intohediet:mainfrom
theeggorchicken:fix/path-traversal-customlibraries

Conversation

@theeggorchicken
Copy link

Security Fix: Path Traversal via customLibraries Setting

Vulnerable Lines

File: src/Config.ts#L586-L590

} else if ("file" in lib) {
    const file = this.evaluateTemplate(lib.file, "custom libraries");
    const buffer = await workspace.fs.readFile(Uri.file(file));
    // ^^^ No check that `file` is within the workspace folder

evaluateTemplate() only substitutes ${workspaceFolder} - it performs no path containment. Any absolute path or ../ traversal is passed directly to workspace.fs.readFile(). Since the extension declares untrustedWorkspaces.supported: true, VS Code does not warn the user when opening a workspace with these settings.

What could happen

An attacker creates a repository with a .vscode/settings.json like:

{
  "hediet.vscode-drawio.customLibraries": [
    { "libName": "x", "entryId": "x", "file": "/etc/passwd" }
  ]
}

When a victim clones the repo and opens any .drawio file, the extension reads the targeted file. Since the extension runs in the VS Code extension host process, it can read anything the user's VS Code process can: SSH keys, cloud credentials, environment files, etc.

How to verify

  1. Create a temporary workspace with the malicious settings.json shown above and a minimal .drawio file
  2. Open the workspace in VS Code with vscode-drawio installed
  3. Open the .drawio file
  4. Observe that the extension attempts to read /etc/passwd (an error will appear because the content isn't valid library JSON, but the file is read)

Full reproduction traces: evidence gist

End-to-end validation was performed using @vscode/test-electron with VS Code 1.109.5 and vscode-drawio v1.9.0. A canary file placed outside the workspace was read by the extension after opening a .drawio file, confirmed by atime change on the canary (before: 1771710897384, after: 1771714497557).

What this PR does

After evaluateTemplate() resolves the file path, the resolved path is normalized via path.resolve() and checked against the workspace folder root. If the path does not start with the workspace folder (plus a path separator, to avoid prefix collisions with sibling directories), an error is thrown with a clear message.

This is consistent with how VS Code's own workspace trust model works: workspace settings should not be able to escape the workspace boundary.

Tests included

  • test-path-containment.js - six cases:
    • Normal path within workspace is allowed
    • Template-expanded workspace path is allowed
    • Absolute path outside workspace (/etc/passwd) is blocked
    • Relative traversal (../../etc/shadow) is blocked
    • Sibling directory with same prefix (project-secret/) is blocked
    • Workspace root itself is allowed (edge case)

Note: this project has no test runner infrastructure, so the test is a standalone Node.js script (node test-path-containment.js).

Evidence

The `customLibraries` setting accepts a `file` property that is resolved
via `evaluateTemplate()` and read with `workspace.fs.readFile()`. There
was no check that the resolved path stays within the workspace folder,
so a malicious `.vscode/settings.json` could read arbitrary files on
the host (e.g. `/etc/passwd`, SSH keys, cloud credentials).

This adds a path containment check after template evaluation: the
resolved path must start with the workspace folder. Absolute paths
and `../` traversals that escape the workspace are now rejected with
a clear error message.
@hediet
Copy link
Owner

hediet commented Feb 22, 2026

Thanks for your investigation! However, I don't see how this could be turned into an attack.
Even if a malicious workspace can make vscode read that file and make it send the file content into the drawio iframe, I don't see how that file content could leak.

@theeggorchicken
Copy link
Author

The webview CSP in DrawioClientFactory.getOnlineHtml() sets connect-src: *, so any JS in the webview context can exfiltrate data via fetch() — and the online-url workspace setting lets an attacker replace the drawio UI itself with a page that captures all postMessage traffic, including the path-traversed file content.

This is also hard to test overall! However the fix is 11 lines of code (and I've included some tests to show they are non breaking).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants