fix: allow file tree access on Windows cross-drive project paths

FileTree component was not passing baseDir to the file API, causing
it to fall back to restricting access to the home directory. On
Windows, projects on a different drive (e.g., D:\) were blocked
with 403.

Changes:
- FileTree now passes workingDirectory as baseDir parameter
- File API routes no longer require baseDir to be under homeDir
  (allows cross-drive access on Windows)
- Still prevents using filesystem root as baseDir for safety
- Add isRootPath() utility for root path detection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-13 17:33:06 +08:00
1 parent 6aca8c3870
commit d446da9717
4 files changed
+23 -17

No files matched your search

+5 -8
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import path from 'path';
import os from 'os';
import { readFilePreview, isPathSafe } from '@/lib/files';
import { readFilePreview, isPathSafe, isRootPath } from '@/lib/files';
import type { FilePreviewResponse, ErrorResponse } from '@/types';
export async function GET(request: NextRequest) {
@@ -20,17 +20,14 @@ export async function GET(request: NextRequest) {
const homeDir = os.homedir();
// Validate that the file is within the session's working directory.
// The baseDir parameter should be the session's working directory,
// which acts as the trust boundary for file access.
// The baseDir itself must be under the user's home directory to prevent
// attackers from setting baseDir=/ to bypass all restrictions.
// baseDir may be on a different drive than homeDir on Windows.
// Only reject root paths as baseDir to prevent full-disk access.
const baseDir = searchParams.get('baseDir');
if (baseDir) {
const resolvedBase = path.resolve(baseDir);
// Ensure baseDir is within the home directory (prevent baseDir=/ bypass)
if (!isPathSafe(homeDir, resolvedBase)) {
if (isRootPath(resolvedBase)) {
return NextResponse.json<ErrorResponse>(
{ error: 'Base directory is outside the allowed scope' },
{ error: 'Cannot use filesystem root as base directory' },
{ status: 403 }
);
}
+8 -8
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import path from 'path';
import os from 'os';
import { scanDirectory, isPathSafe } from '@/lib/files';
import { scanDirectory, isPathSafe, isRootPath } from '@/lib/files';
import type { FileTreeResponse, ErrorResponse } from '@/types';
export async function GET(request: NextRequest) {
@@ -20,17 +20,17 @@ export async function GET(request: NextRequest) {
const homeDir = os.homedir();
// Use baseDir (the session's working directory) as the trust boundary.
// The baseDir itself must be under the user's home directory to prevent
// attackers from setting baseDir=/ to bypass all restrictions.
// If no baseDir is provided, fall back to the user's home directory
// to prevent scanning arbitrary system directories.
// baseDir is the project root the user explicitly chose — it may be on
// a different drive than the home directory on Windows (e.g., D:\projects).
// We only reject root paths (/, C:\) as baseDir to prevent full-disk scans.
// If no baseDir is provided, fall back to the user's home directory.
const baseDir = searchParams.get('baseDir');
if (baseDir) {
const resolvedBase = path.resolve(baseDir);
// Ensure baseDir is within the home directory (prevent baseDir=/ bypass)
if (!isPathSafe(homeDir, resolvedBase)) {
// Prevent using a filesystem root as baseDir (e.g., /, C:\)
if (isRootPath(resolvedBase)) {
return NextResponse.json<ErrorResponse>(
{ error: 'Base directory is outside the allowed scope' },
{ error: 'Cannot use filesystem root as base directory' },
{ status: 403 }
);
}
+1 -1
View File
@@ -118,7 +118,7 @@ export function FileTree({ workingDirectory, onFileSelect, onFileAdd }: FileTree
setLoading(true);
try {
const res = await fetch(
`/api/files?dir=${encodeURIComponent(workingDirectory)}&depth=4`
`/api/files?dir=${encodeURIComponent(workingDirectory)}&baseDir=${encodeURIComponent(workingDirectory)}&depth=4`
);
if (res.ok) {
const data = await res.json();
+9
View File
@@ -74,6 +74,15 @@ export function isPathSafe(basePath: string, targetPath: string): boolean {
return resolvedTarget.startsWith(resolvedBase + path.sep) || resolvedTarget === resolvedBase;
}
/**
* Check if a path is a filesystem root (e.g., `/`, `C:\`, `D:\`).
* Used to prevent using root as a baseDir for file browsing.
*/
export function isRootPath(p: string): boolean {
const resolved = path.resolve(p);
return resolved === path.parse(resolved).root;
}
export async function scanDirectory(dir: string, depth: number = 3): Promise<FileTreeNode[]> {
const resolvedDir = path.resolve(dir);