MCPcopy
hub / github.com/garrytan/gstack / validateOutputPath

Function validateOutputPath

browse/src/path-security.ts:33–75  ·  view source on GitHub ↗
(filePath: string)

Source from the content-addressed store, hash-verified

31
32/** Validate a file path for writing (screenshot, pdf, download, scrape, archive). */
33export function validateOutputPath(filePath: string): void {
34 const resolved = path.resolve(filePath);
35
36 // If the target already exists and is a symlink, resolve through it.
37 // Without this, a symlink at /tmp/evil.png → /etc/crontab passes the
38 // parent-directory check (parent is /tmp, which is safe) but the actual
39 // write follows the symlink to /etc/crontab.
40 try {
41 const stat = fs.lstatSync(resolved);
42 if (stat.isSymbolicLink()) {
43 const realTarget = fs.realpathSync(resolved);
44 const isSafe = SAFE_DIRECTORIES.some(dir => isPathWithin(realTarget, dir));
45 if (!isSafe) {
46 throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
47 }
48 return; // symlink target verified, no need to check parent
49 }
50 } catch (e: any) {
51 // ENOENT = file doesn't exist yet, fall through to parent-dir check
52 if (e.code !== 'ENOENT') throw e;
53 }
54
55 // For new files (no existing symlink), verify the parent directory.
56 // The file itself may not exist yet (e.g., screenshot output).
57 // This also handles macOS /tmp → /private/tmp transparently.
58 let dir = path.dirname(resolved);
59 let realDir: string;
60 try {
61 realDir = fs.realpathSync(dir);
62 } catch {
63 try {
64 realDir = fs.realpathSync(path.dirname(dir));
65 } catch {
66 throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
67 }
68 }
69
70 const realResolved = path.join(realDir, path.basename(resolved));
71 const isSafe = SAFE_DIRECTORIES.some(dir => isPathWithin(realResolved, dir));
72 if (!isSafe) {
73 throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
74 }
75}
76
77/** Validate a file path for reading (eval command). */
78export function validateReadPath(filePath: string): void {

Callers 4

handleWriteCommandFunction · 0.90
handleMetaCommandFunction · 0.90
writeEvalResultFunction · 0.90

Calls 1

isPathWithinFunction · 0.90

Tested by

no test coverage detected