Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/filesystem/__tests__/lib.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,30 @@ describe('Lib Functions', () => {
await expect(validatePath(newFilePath))
.rejects.toThrow('Parent directory does not exist');
});

it('resolves relative paths against allowed directories instead of process.cwd()', async () => {
const relativePath = 'test-file.txt';
const originalCwd = process.cwd;

// Mock process.cwd to return a directory outside allowed directories
const disallowedCwd = process.platform === 'win32' ? 'C:\\Windows\\System32' : '/root';
(process as any).cwd = jest.fn(() => disallowedCwd);

try {
const result = await validatePath(relativePath);

// Result should be resolved against first allowed directory, not process.cwd()
const expectedPath = process.platform === 'win32'
? path.resolve('C:\\Users\\test', relativePath)
: path.resolve('/home/user', relativePath);

expect(result).toBe(expectedPath);
expect(result).not.toContain(disallowedCwd);
} finally {
// Restore original process.cwd
process.cwd = originalCwd;
}
});
});
});

Expand Down
25 changes: 24 additions & 1 deletion src/filesystem/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,35 @@ export function createUnifiedDiff(originalContent: string, newContent: string, f
);
}

// Helper function to resolve relative paths against allowed directories
function resolveRelativePathAgainstAllowedDirectories(relativePath: string): string {
if (allowedDirectories.length === 0) {
// Fallback to process.cwd() if no allowed directories are set
return path.resolve(process.cwd(), relativePath);
}

// Try to resolve relative path against each allowed directory
for (const allowedDir of allowedDirectories) {
const candidate = path.resolve(allowedDir, relativePath);
const normalizedCandidate = normalizePath(candidate);

// Check if the resulting path lies within any allowed directory
if (isPathWithinAllowedDirectories(normalizedCandidate, allowedDirectories)) {
return candidate;
}
}

// If no valid resolution found, use the first allowed directory as base
// This provides a consistent fallback behavior
return path.resolve(allowedDirectories[0], relativePath);
}

// Security & Validation Functions
export async function validatePath(requestedPath: string): Promise<string> {
const expandedPath = expandHome(requestedPath);
const absolute = path.isAbsolute(expandedPath)
? path.resolve(expandedPath)
: path.resolve(process.cwd(), expandedPath);
: resolveRelativePathAgainstAllowedDirectories(expandedPath);

const normalizedRequested = normalizePath(absolute);

Expand Down