Skip to content

Commit 93c9825

Browse files
sorlen008claude
andcommitted
fix: return resolved path from validatePath to prevent symlink write bypass
Previously, validatePath() resolved symlinks for the allowlist check but returned the original (unresolved) path. This meant callers like writeFile() and createDirectory() would still operate on the symlink path, following it to the restricted target. Now validatePath() consistently returns the resolved (canonical) path so all subsequent file operations use the real target path, closing the symlink write bypass vector. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent afc7a6c commit 93c9825

1 file changed

Lines changed: 12 additions & 12 deletions

File tree

src/tools/filesystem.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -288,25 +288,25 @@ export async function validatePath(requestedPath: string): Promise<string> {
288288
throw new Error(`Path not allowed: ${requestedPath}. Must be within one of these directories: ${(await getAllowedDirs()).join(', ')}`);
289289
}
290290

291+
// SECURITY: Always return the resolved path (with symlinks resolved) so that
292+
// all subsequent file operations (read, write, mkdir, etc.) operate on the
293+
// canonical target, not on a symlink that could point outside allowed directories.
294+
// pathForNextCheck already holds resolvedRealPath ?? absoluteOriginal from above.
295+
291296
// Check if path exists
292297
try {
293298
// fs.stat() will automatically follow symlinks, so we get existence info
294-
const stats = await fs.stat(absoluteOriginal);
295-
// If path exists, resolve any symlinks
296-
if (resolvedRealPath) {
297-
return resolvedRealPath;
298-
}
299-
300-
return absoluteOriginal;
299+
await fs.stat(pathForNextCheck);
300+
return pathForNextCheck;
301301
} catch (error) {
302302
// Path doesn't exist - validate parent directories
303-
if (await validateParentDirectories(absoluteOriginal)) {
304-
// Return the path if a valid parent exists
303+
if (await validateParentDirectories(pathForNextCheck)) {
304+
// Return the resolved path if a valid parent exists
305305
// This will be used for folder creation and many other file operations
306-
return absoluteOriginal;
306+
return pathForNextCheck;
307307
}
308-
// If no valid parent found, return the absolute path anyway
309-
return absoluteOriginal;
308+
// If no valid parent found, return the resolved path anyway
309+
return pathForNextCheck;
310310
}
311311
};
312312

0 commit comments

Comments
 (0)