modelcontextprotocol/servers · error · Error

Access denied - parent directory outside allowed directories

Error message

Access denied - parent directory outside allowed directories: ${realParentPath} not in ${allowedDirectories.join(', ')}

What it means

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.

Source

Thrown at src/filesystem/lib.ts:131

  // Security: Handle symlinks by checking their real path to prevent symlink attacks
  // This prevents attackers from creating symlinks that point outside allowed directories
  try {
    const realPath = await fs.realpath(absolute);
    const normalizedReal = normalizePath(realPath);
    if (!isPathWithinAllowedDirectories(normalizedReal, allowedDirectories)) {
      throw new Error(`Access denied - symlink target outside allowed directories: ${realPath} not in ${allowedDirectories.join(', ')}`);
    }
    return realPath;
  } catch (error) {
    // Security: For new files that don't exist yet, verify parent directory
    // This ensures we can't create files in unauthorized locations
    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
      const parentDir = path.dirname(absolute);
      try {
        const realParentPath = await fs.realpath(parentDir);
        const normalizedParent = normalizePath(realParentPath);
        if (!isPathWithinAllowedDirectories(normalizedParent, allowedDirectories)) {
          throw new Error(`Access denied - parent directory outside allowed directories: ${realParentPath} not in ${allowedDirectories.join(', ')}`);
        }
        return absolute;
      } catch {
        throw new Error(`Parent directory does not exist: ${parentDir}`);
      }
    }
    throw error;
  }
}


// File Operations
export async function getFileStats(filePath: string): Promise<FileInfo> {
  const stats = await fs.stat(filePath);
  return {
    size: stats.size,
    created: stats.birthtime,
    modified: stats.mtime,

View on GitHub (pinned to 76d64c822f)

Solutions

  1. Write to a path whose parent directory is inside the allowed directories (by realpath).
  2. Add the real parent directory to the allowed directories if the write is legitimate.
  3. Resolve and normalize the intended target client-side to confirm it stays inside the sandbox.

Example fix

// before (parent resolves outside allowlist)
write_file({ path: '/home/me/projects/../../tmp/x.txt', content: '...' })
// after
write_file({ path: '/home/me/projects/x.txt', content: '...' })
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs/promises';
async function parentInside(p: string, allowed: string[]): Promise<boolean> {
  try {
    const parent = await fs.realpath(path.dirname(p));
    return allowed.some(d => parent === path.resolve(d) || parent.startsWith(path.resolve(d) + path.sep));
  } catch { return false; }
}

Try / catch

try {
  await writeFile({ path, content });
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Access denied - parent directory')) {
    // parent resolves outside allowlist; write to a path whose parent is inside
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of modelcontextprotocol/servers@76d64c822f (2026-08-12). Data as JSON: /api/errors/ac783cf06e019301. Report an issue: GitHub.