{"record":{"id":"ac783cf06e019301","repo":"modelcontextprotocol/servers","slug":"access-denied-parent-directory-outside-allowed-d","errorCode":null,"errorMessage":"Access denied - parent directory outside allowed directories: ${realParentPath} not in ${allowedDirectories.join(', ')}","messagePattern":"Access denied - parent directory outside allowed directories: (.+?) not in (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/filesystem/lib.ts","lineNumber":131,"sourceCode":"  // Security: Handle symlinks by checking their real path to prevent symlink attacks\n  // This prevents attackers from creating symlinks that point outside allowed directories\n  try {\n    const realPath = await fs.realpath(absolute);\n    const normalizedReal = normalizePath(realPath);\n    if (!isPathWithinAllowedDirectories(normalizedReal, allowedDirectories)) {\n      throw new Error(`Access denied - symlink target outside allowed directories: ${realPath} not in ${allowedDirectories.join(', ')}`);\n    }\n    return realPath;\n  } catch (error) {\n    // Security: For new files that don't exist yet, verify parent directory\n    // This ensures we can't create files in unauthorized locations\n    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n      const parentDir = path.dirname(absolute);\n      try {\n        const realParentPath = await fs.realpath(parentDir);\n        const normalizedParent = normalizePath(realParentPath);\n        if (!isPathWithinAllowedDirectories(normalizedParent, allowedDirectories)) {\n          throw new Error(`Access denied - parent directory outside allowed directories: ${realParentPath} not in ${allowedDirectories.join(', ')}`);\n        }\n        return absolute;\n      } catch {\n        throw new Error(`Parent directory does not exist: ${parentDir}`);\n      }\n    }\n    throw error;\n  }\n}\n\n\n// File Operations\nexport async function getFileStats(filePath: string): Promise<FileInfo> {\n  const stats = await fs.stat(filePath);\n  return {\n    size: stats.size,\n    created: stats.birthtime,\n    modified: stats.mtime,","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/modelcontextprotocol/servers/blob/76d64c822f5125032f89eb71dbdb94e42b434821/src/filesystem/lib.ts#L113-L149","documentation":"Thrown by `validatePath` in the ENOENT branch (new file that doesn't yet exist) when the to-be-created file's parent directory resolves — via realpath — outside the allowed directories. Because the file itself has no realpath yet, the server validates its parent dir to prevent creating files in unauthorized locations through symlinks or escaping paths.","triggerScenarios":"Calling a write/create tool (`write_file`, `create_directory`) for a path whose parent (after symlink resolution) is outside the allowlist — e.g. creating `/home/me/projects/../../../tmp/x` where `/tmp` is not allowed, or a parent dir that is itself a symlink escaping the sandbox.","commonSituations":"Write tools whose target path traverses out via `..`, parent directories that are symlinks to disallowed locations, or operators who allowlist a dir but not the actual realpath parent of a write target.","solutions":["Write to a path whose parent directory is inside the allowed directories (by realpath).","Add the real parent directory to the allowed directories if the write is legitimate.","Resolve and normalize the intended target client-side to confirm it stays inside the sandbox."],"exampleFix":"// before (parent resolves outside allowlist)\nwrite_file({ path: '/home/me/projects/../../tmp/x.txt', content: '...' })\n// after\nwrite_file({ path: '/home/me/projects/x.txt', content: '...' })","handlingStrategy":"validation","validationCode":"import fs from 'node:fs/promises';\nasync function parentInside(p: string, allowed: string[]): Promise<boolean> {\n  try {\n    const parent = await fs.realpath(path.dirname(p));\n    return allowed.some(d => parent === path.resolve(d) || parent.startsWith(path.resolve(d) + path.sep));\n  } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await writeFile({ path, content });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Access denied - parent directory')) {\n    // parent resolves outside allowlist; write to a path whose parent is inside\n  }\n}","preventionTips":["Write targets whose parent dir (by realpath) is inside the allowlist.","Avoid '..' in write paths; normalize first.","Allowlist the real parent directory for legitimate cross-dir writes."],"tags":["mcp","typescript","filesystem-server","security","access-control","symlink","validation"],"backgroundTag":null,"analyzedSha":"76d64c822f5125032f89eb71dbdb94e42b434821","analyzedAt":"2026-08-12T10:02:41.718Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}