refactor(server): own the requested project directory in the runtime

Drop the canonical-containment 403 guard and the extra realpath(base) the
read routes (stat/read/raw/serve) had gained. Every workspace resolution
returns insideWorkspace: true and outside-file grants use
base = dirname(canonicalPath), so the guard could never fire; the flag had
no remaining reader and is gone with it. The read routes are back to the
single realpath(resolved.resolved) they had before.

Move the lexical-base fallback out of the inline header parsing in
routes.js. x-opencode-directory decoding belongs to
project-directory-runtime, so resolveProjectDirectory now also returns
requestedDirectory, the pre-realpath candidate that validated.
resolveWorkspacePathFromContext retries against it when the canonical base
rejects a path, which keeps files under a symlinked project root
addressable without a second copy of the header/query parsing.
This commit is contained in:
Bohdan Triapitsyn
2026-08-29 00:53:06 +03:00
parent 55ae41bde2
commit 3e0a9622aa
5 changed files with 88 additions and 106 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ Own filesystem API behavior for the web server runtime, including workspace-boun
- `GET /api/fs/list`
- Owns exec job queue state (`execJobs`) and lifecycle/TTL pruning.
- Enforces workspace boundary checks with active project + worktree fallback support.
- Read-family endpoints (`stat`, `read`, `raw`, `serve`) resolve symlinks before serving and allow paths that are lexically inside the active workspace even when their canonical (realpath) target lives outside it through a workspace-internal symlink (for example `~/test_folder -> /shared/test_folder`). Paths that are not inside the workspace lexically are still rejected before any symlink resolution, and write/exec operations keep the strict canonical containment check.
- The active project directory is validated with `fs.realpath`, so when the project root is itself a symlink the workspace base no longer matches the paths the client sends. Workspace resolution therefore retries against the raw directory the client requested (`requestedDirectory` from `resolveProjectDirectory`) before falling back to worktree roots. Symlinks are still resolved afterwards, and write/exec routes keep their canonical containment check against the resolved base.
- `createFsSearchRuntime({ fsPromises, path, spawn, resolveGitBinaryForSpawn })` from `search.js`
- Returns `{ searchFilesystemFiles(rootPath, options) }`.
- Supports fuzzy matching, hidden-file handling, and optional `git check-ignore` filtering.