Skip to content

Commit 8021546

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 5f82367 commit 8021546

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
@@ -246,25 +246,25 @@ export async function validatePath(requestedPath: string): Promise<string> {
246246
throw new Error(`Path not allowed: ${requestedPath}. Must be within one of these directories: ${(await getAllowedDirs()).join(', ')}`);
247247
}
248248

249+
// SECURITY: Always return the resolved path (with symlinks resolved) so that
250+
// all subsequent file operations (read, write, mkdir, etc.) operate on the
251+
// canonical target, not on a symlink that could point outside allowed directories.
252+
// pathForNextCheck already holds resolvedRealPath ?? absoluteOriginal from above.
253+
249254
// Check if path exists
250255
try {
251256
// fs.stat() will automatically follow symlinks, so we get existence info
252-
const stats = await fs.stat(absoluteOriginal);
253-
// If path exists, resolve any symlinks
254-
if (resolvedRealPath) {
255-
return resolvedRealPath;
256-
}
257-
258-
return absoluteOriginal;
257+
await fs.stat(pathForNextCheck);
258+
return pathForNextCheck;
259259
} catch (error) {
260260
// Path doesn't exist - validate parent directories
261-
if (await validateParentDirectories(absoluteOriginal)) {
262-
// Return the path if a valid parent exists
261+
if (await validateParentDirectories(pathForNextCheck)) {
262+
// Return the resolved path if a valid parent exists
263263
// This will be used for folder creation and many other file operations
264-
return absoluteOriginal;
264+
return pathForNextCheck;
265265
}
266-
// If no valid parent found, return the absolute path anyway
267-
return absoluteOriginal;
266+
// If no valid parent found, return the resolved path anyway
267+
return pathForNextCheck;
268268
}
269269
};
270270

0 commit comments

Comments
 (0)