diff --git a/packages/api/src/routes/workspace.ts b/packages/api/src/routes/workspace.ts index 58835cf48..b99934bda 100644 --- a/packages/api/src/routes/workspace.ts +++ b/packages/api/src/routes/workspace.ts @@ -280,7 +280,7 @@ export const workspaceRoutes: FastifyPluginAsync = async (ap app.get<{ Querystring: { repoRoot?: string } }>('/api/workspace/worktrees', async (request, reply) => { const { repoRoot } = request.query; if (repoRoot) { - if (!repoRoot.startsWith('/')) { + if (!isAbsolute(repoRoot)) { reply.status(400); return { error: 'repoRoot must be an absolute path' }; } diff --git a/packages/api/test/workspace-windows-path.test.js b/packages/api/test/workspace-windows-path.test.js new file mode 100644 index 000000000..add41e52e --- /dev/null +++ b/packages/api/test/workspace-windows-path.test.js @@ -0,0 +1,36 @@ +import assert from 'node:assert/strict'; +import { isAbsolute } from 'node:path'; +import { describe, it } from 'node:test'; + +describe('workspace worktrees repoRoot validation (Windows compat)', () => { + // The route handler uses isAbsolute() to validate repoRoot. + // Previously it used startsWith('/') which rejected Windows paths. + + it('accepts Unix absolute path', () => { + assert.ok(isAbsolute('/home/user/project')); + }); + + it('accepts Windows absolute path with backslash', () => { + assert.ok(isAbsolute('C:\\Personal\\AIProjects\\test-project')); + }); + + it('accepts Windows absolute path with forward slash', () => { + assert.ok(isAbsolute('C:/Personal/AIProjects/test-project')); + }); + + it('accepts UNC path', () => { + assert.ok(isAbsolute('\\\\server\\share\\folder')); + }); + + it('rejects relative path', () => { + assert.ok(!isAbsolute('relative/path')); + }); + + it('rejects dot-relative path', () => { + assert.ok(!isAbsolute('./src/index.ts')); + }); + + it('rejects parent-relative path', () => { + assert.ok(!isAbsolute('../etc/passwd')); + }); +});