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
+6 -3
View File
@@ -165,7 +165,7 @@ const registerUpload = (fsPromises) => {
return getRoute('POST', '/api/fs/upload');
};
const registerRead = (fsPromises) => {
const registerRead = (fsPromises, resolveProjectDirectory = async () => ({ directory: '/repo' })) => {
const { app, getRoute } = createRouteRegistry();
registerFsRoutes(app, {
os: { homedir: () => '/home/user' },
@@ -177,7 +177,7 @@ const registerRead = (fsPromises) => {
spawn: vi.fn(),
crypto: { randomUUID: () => 'job-0' },
normalizeDirectoryPath: (p) => p,
resolveProjectDirectory: async () => ({ directory: '/repo' }),
resolveProjectDirectory,
buildAugmentedPath: () => '/usr/bin',
resolveGitBinaryForSpawn: () => 'git',
openchamberUserConfigRoot: '/home/user/.config',
@@ -733,7 +733,10 @@ describe('fs read', () => {
stat: vi.fn(async () => ({ isFile: () => true, size: 4 })),
readFile: vi.fn(async () => 'data'),
};
const handler = registerRead(fsPromises);
const handler = registerRead(fsPromises, async () => ({
directory: '/real/proj',
requestedDirectory: '/home/user/proj',
}));
const res = createMockResponse();
await handler({