-
Notifications
You must be signed in to change notification settings - Fork 808
Description
All WebSocket methods that accept a cwd field pass it directly to service-layer
operations without validating it against registered project workspace roots. A
WebSocket client (authenticated or not, depending on config) can send an arbitrary
absolute path as cwd and perform filesystem writes, directory listings, git
operations, or shell spawns in any directory the server process has access to.
Affected methods
| Method | Impact |
|---|---|
projects.writeFile |
Arbitrary filesystem write |
projects.searchEntries |
Arbitrary directory listing / file enumeration |
git.init |
git init in any directory |
git.status, git.pull, git.checkout, git.createBranch, git.listBranches, git.createWorktree, git.removeWorktree, git.runStackedAction |
Git operations in arbitrary directories |
terminal.open, terminal.restart |
Shell spawn in any existing directory |
shell.openInEditor |
Launch editor pointed at any path |
Root cause
resolveWorkspaceWritePath in apps/server/src/wsServer.ts (line 155) validates
that relativePath does not escape the supplied workspaceRoot via ../ traversal,
but trusts workspaceRoot itself completely. The ProjectWriteFileInput schema in
packages/contracts/src/project.ts only requires cwd to be a non-empty trimmed
string.
No server-side middleware or guard constrains client-supplied cwd to a known project
workspace root (ServerConfig.cwd or orchestration state).
Reproduction
- Start the T3 Code server (no
--auth-token):bun run --cwd apps/server start - Connect a WebSocket client to
ws://localhost:3773. - Send:
{ "id": "1", "body": { "_tag": "projects.writeFile", "cwd": "/tmp", "relativePath": "proof.txt", "contents": "written by unauthenticated ws client" } } - Observe
/tmp/proof.txtis created with the supplied contents.
The same pattern works with projects.searchEntries (to list /etc, ~/.ssh,
etc.) and terminal.open (to spawn a shell in any directory).
Impact
- Local attack: Any process or browser extension that can open a WebSocket to
localhost:3773gets full filesystem access scoped to the server's OS user. - Network attack: In default web mode, the server binds to all interfaces without
requiring an auth token. Any device on the same network can exploit this.
Suggested fix
Add a server-side guard that validates every client-supplied cwd against the set of
known project workspace roots from the orchestration read model (or against
ServerConfig.cwd). Reject requests where cwd does not match a registered project.
// Example guard (pseudocode)
function assertKnownWorkspaceRoot(cwd: string, knownRoots: Set<string>): void {
if (!knownRoots.has(cwd)) {
throw new RouteRequestError({
message: "cwd does not match any registered project workspace.",
});
}
}Apply this to every route handler that accepts a cwd parameter.
Environment
- Version: 0.0.3
- OS: Windows (also affects macOS/Linux)