{"record":{"id":"3b6858d8b566c5a5","repo":"modelcontextprotocol/servers","slug":"access-denied-path-outside-allowed-directories","errorCode":null,"errorMessage":"Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}","messagePattern":"Access denied - path outside allowed directories: (.+?) not in (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/filesystem/lib.ts","lineNumber":157,"sourceCode":"\nexport async function validatePath(requestedPath: string): Promise<string> {\n  const expandedPath = expandHome(requestedPath);\n  // Do not silently reinterpret a Windows drive path as a relative POSIX path.\n  // This would create a literal filename such as `C:\\\\Users\\\\...` inside the\n  // allowed root and report success for the wrong location.\n  if (process.platform !== 'win32' && /^(?:[A-Za-z]:)(?:[\\\\/]|$)/.test(expandedPath)) {\n    throw new Error(`Access denied - Windows-style path received on a POSIX host: ${requestedPath}`);\n  }\n  const absolute = path.isAbsolute(expandedPath)\n    ? path.resolve(expandedPath)\n    : resolveRelativePathAgainstAllowedDirectories(expandedPath);\n\n  const normalizedRequested = normalizePath(absolute);\n\n  // Security: Check if path is within allowed directories before any file operations\n  const isAllowed = isPathWithinAllowedDirectories(normalizedRequested, allowedDirectories);\n  if (!isAllowed) {\n    throw new Error(`Access denied - path outside allowed directories: ${absolute} not in ${allowedDirectories.join(', ')}`);\n  }\n\n  // 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      try {\n        return await resolveUnicodeEquivalentPath(absolute);\n      } catch (resolutionError) {","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/modelcontextprotocol/servers/blob/579c3903f30044eb702a599a74b3ae77588e722e/src/filesystem/lib.ts#L139-L175","documentation":"Thrown by `validatePath` in the filesystem server when the normalized requested path does not lie within any allowed directory. This is the primary security boundary: every file operation runs through `validatePath`, which checks the resolved absolute path before any I/O. Symlink resolution is then applied separately (error 17).","triggerScenarios":"Any filesystem tool call (`read_text_file`, `write_file`, `list_directory`, etc.) whose `path` resolves outside all configured allowed directories — e.g. requesting `/etc/passwd` when only `/home/me/projects` is allowed, or a relative path that escapes via `..`.","commonSituations":"Clients passing absolute paths outside the allowlist, `..` traversal in relative paths, home shorthand (`~/`) expanding outside the allowlist, or a misconfigured allowlist that omits the needed directory.","solutions":["Request a path inside one of the allowed directories shown in the error message.","Add the needed directory to the server's allowed directories (CLI args or client roots).","Avoid `..` segments; resolve and normalize the path client-side before sending."],"exampleFix":"# before (only /home/me/projects allowed)\nread_text_file({ path: '/etc/passwd' })\n# after\nread_text_file({ path: '/home/me/projects/notes.txt' })","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nfunction isWithinAllowed(p: string, allowed: string[]): boolean {\n  const a = path.resolve(p);\n  return allowed.some(d => a === path.resolve(d) || a.startsWith(path.resolve(d) + path.sep));\n}\nif (!isWithinAllowed(args.path, allowedDirs)) { /* surface error */ }","typeGuard":null,"tryCatchPattern":"try {\n  await readFile({ path });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Access denied - path outside')) {\n    // path resolves outside allowlist; choose an allowed path or extend allowlist\n  }\n}","preventionTips":["Only request paths inside the configured allowed directories.","Avoid '..' traversal; normalize paths client-side first.","Extend the allowlist (CLI or roots) when legitimate access is needed."],"tags":["mcp","typescript","filesystem-server","security","access-control","validation"],"backgroundTag":null,"analyzedSha":"579c3903f30044eb702a599a74b3ae77588e722e","analyzedAt":"2026-08-12T10:02:41.718Z","contentChangedAt":"2026-08-12T10:02:41.718Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}