diff --git a/gitnexus/src/server/api.ts b/gitnexus/src/server/api.ts index 5dc5e4cfb..cea520555 100644 --- a/gitnexus/src/server/api.ts +++ b/gitnexus/src/server/api.ts @@ -569,7 +569,10 @@ export const handleFsListRequest = async ( res.status(400).json({ error: '"dir" must be an absolute path' }); return; } - if (path.normalize(dir) !== path.resolve(dir)) { + // Skip the traversal guard for bare root directories (/ on Linux, C:\ on + // Windows) where path.normalize and path.resolve diverge on Windows. + const isRoot = dir === path.parse(dir).root; + if (!isRoot && path.normalize(dir) !== path.resolve(dir)) { res.status(400).json({ error: '"dir" must not contain traversal sequences' }); return; } diff --git a/gitnexus/test/unit/api-fs-list.test.ts b/gitnexus/test/unit/api-fs-list.test.ts index 500295e42..0cb69157c 100644 --- a/gitnexus/test/unit/api-fs-list.test.ts +++ b/gitnexus/test/unit/api-fs-list.test.ts @@ -54,11 +54,10 @@ describe('GET /api/fs/list — handleFsListRequest', () => { it('defaults to / when dir is omitted (linux server)', async () => { const { status } = await invoke({}); - if (process.platform === 'win32') { - expect(status).toBe(400); - } else { - expect(status).toBe(200); - } + // On Linux root / is the filesystem root; on Windows path.normalize('/') + // normalizes to \ while path.resolve('/') resolves to the CWD drive root. + // Our guard skips the traversal check for bare root paths. + expect(status).toBe(200); }); it('returns 400 for a relative path', async () => {