Security Implications of `allowedDirectories` in Desktop Commander MCP: A Complete Guide

The allowedDirectories whitelist in Desktop Commander MCP determines which filesystem paths an AI assistant can access, with an empty array defaulting to full system access and explicit paths enforcing least-privilege isolation.

Desktop Commander MCP implements a path-based access control model that gates every file operation—read, write, move, and search—behind a configurable whitelist. This article examines the security architecture of allowedDirectories as implemented in wonderwhy-er/DesktopCommanderMCP, including how the whitelist is defined, enforced, and what risks emerge from common misconfigurations.

How allowedDirectories Is Defined and Initialized

The whitelist originates in two configuration files with conflicting defaults that create a security-relevant transition state.

Configuration Schema and UI Labeling

The user-facing field appears as "Allowed Folders" in the configuration interface. The schema description explicitly warns about the security trade-off:

// src/config-field-definitions.ts
allowedDirectories: {
  label: 'Allowed Folders',
  description: 'These are the folders Desktop Commander is allowed to read and edit. Think of this as a permission list. Keeping it small is safer. If this list is empty, Desktop Commander can access your entire filesystem.',
  valueType: 'array',
}

This definition establishes allowedDirectories as an array of path strings with security-critical semantics.

Default Value vs. Runtime Fallback

The configuration manager initializes new configs with an empty array:

// src/config-manager.ts – default config
allowedDirectories: [],                // ← empty = full‑filesystem access

However, getAllowedDirs() in the filesystem tools implements a runtime fallback that modifies this behavior:

// src/tools/filesystem.ts
async function getAllowedDirs(): Promise<string[]> {
  const config = await configManager.getConfig();
  if (config.allowedDirectories && Array.isArray(config.allowedDirectories)) {
    allowedDirectories = config.allowedDirectories;
  } else {
    allowedDirectories = [os.homedir()];  // Fallback to home directory
    await configManager.setValue('allowedDirectories', allowedDirectories);
  }
  return allowedDirectories;
}

This means:

  • An explicit empty array ([]) in config → no restrictions
  • A missing or malformed value → auto-constrained to home directory
  • Any populated array → enforces explicit whitelist

Path Validation Architecture: isPathAllowed()

The core security enforcement occurs in isPathAllowed(), called by validatePath() before every filesystem operation.

Whitelist Evaluation Logic

// src/tools/filesystem.ts
async function isPathAllowed(pathToCheck: string): Promise<boolean> {
  const allowedDirectories = await getAllowedDirs();
  
  // Bypass conditions: "/" or empty array grants full access
  if (allowedDirectories.includes('/') || allowedDirectories.length === 0) {
    return true;
  }
  
  const isAllowed = allowedDirectories.some(allowedDir => {
    const normAllowed = normalizePath(allowedDir);
    const normCheck   = normalizePath(pathToCheck);
    
    if (normCheck === normAllowed) return true;
    
    // Sub-directory check with separator to prevent prefix collisions
    if (normCheck.startsWith(normAllowed + path.sep)) return true;
    
    // Windows drive root special case
    if (normAllowed === 'c:' && process.platform === 'win32')
      return normCheck.startsWith('c:');
      
    return false;
  });
  
  return isAllowed;
}

The implementation addresses several attack vectors:

  • Prefix collision mitigation: Adding path.sep ensures /home/user does not match /home/userdata
  • Path normalization: Resolves ., .., and duplicate separators before comparison
  • Platform handling: Special-cases Windows drive letters

Enforcement Failure Mode

When validation fails, the error reveals the whitelist contents (potentially information-leaking sensitive paths):

if (!(await isPathAllowed(pathForNextCheck))) {
  throw new Error(
    `Path not allowed: ${requestedPath}. Must be within one of these directories: ${allowedDirectories.join(', ')}`);
}

Critical Security Scenarios

Configuration Security Posture Risk Level
[] (empty array) Full filesystem access — any path readable/writable Critical
['/'] (Unix root) Functionally equivalent to empty array on Unix Critical
['C:'] (Windows root) Only C: drive root, not subdirectories — misleading High
[os.homedir()] Home directory and all descendants Medium
['/home/user/project'] Single project isolation — recommended Low

The Empty Array Default: A Backwards-Compatibility Risk

The default allowedDirectories: [] prioritizes functionality over security. Per the source comment in config-manager.ts, this preserves behavior for existing users, but new installations inherit unrestricted access until explicitly configured.

The set_config_value tool documentation marks this as destructive:

"Setting allowedDirectories to an empty array grants full access to the entire file system."

validatePath() implements multi-layer protections in src/tools/filesystem.ts:

  1. Symlink resolution: fs.realpath() resolves symbolic links before whitelist checking
  2. Path normalization: Collapses navigation sequences that could escape boundaries
  3. Directory traversal blocking: The startsWith(normAllowed + path.sep) check prevents ../ escapes

The test suite test/test-allowed-directories.js verifies these protections, including a dedicated prefix blocking test (lines 308-353) confirming that sibling directories with overlapping names are correctly rejected.

Practical Configuration Examples

{
  "name": "set_config_value",
  "arguments": {
    "key": "allowedDirectories",
    "value": ["/home/you/projects/my-app"]
  }
}

Result: Any access outside /home/you/projects/my-app produces:


Path not allowed: /etc/passwd. Must be within one of these directories: /home/you/projects/my-app

Verify Current Whitelist

{
  "name": "get_config",
  "arguments": {}
}

Inspect the allowedDirectories array before granting sensitive operations.

Multiple Isolated Directories

{
  "name": "set_config_value",
  "arguments": {
    "key": "allowedDirectories",
    "value": [
      "/home/you/work/project-a",
      "/home/you/work/project-b"
    ]
  }
}

Directories remain isolated—project-a cannot access project-b files.

Key Security Files in Desktop Commander MCP

File Security Function
src/config-field-definitions.ts Schema definition and user-facing description of allowedDirectories
src/config-manager.ts Default value ([]) and configuration persistence
src/tools/filesystem.ts getAllowedDirs(), isPathAllowed(), validatePath() — the enforcement engine
src/server.ts Tool documentation embedding whitelist warnings
test/test-allowed-directories.js Automated verification of boundary conditions and attack scenarios

Summary

  • allowedDirectories is the single security boundary for all filesystem operations in Desktop Commander MCP
  • Empty array = no restrictions: The default configuration grants full system access for backwards compatibility
  • Explicit paths enforce least privilege: Each filesystem call validates against the resolved, normalized path before execution
  • Multiple defensive layers: Symlink resolution, path normalization, and separator-aware prefix checking prevent common escape techniques
  • Configuration is mutable at runtime: Use set_config_value to tighten restrictions without restart, but recognize changes apply prospectively

Frequently Asked Questions

What happens if I leave allowedDirectories empty?

The assistant gains unrestricted read/write access to your entire filesystem. This is the default configuration in src/config-manager.ts for backwards compatibility, but it exposes system files, SSH keys, and sensitive documents. You should explicitly restrict this list before handling confidential data.

No. The validatePath() function in src/tools/filesystem.ts resolves symlinks via fs.realpath() and normalizes all paths before whitelist checking. The isPathAllowed() implementation specifically uses path.sep suffix matching to prevent /home/user from matching /home/userdata, blocking both traversal and prefix collision attacks.

Why does ['/'] behave differently than ['C:'] on Windows?

On Unix systems, '/' represents the filesystem root and grants full access (equivalent to an empty array). On Windows, 'C:' only matches the drive root itself due to the special-case handler in isPathAllowed() — subdirectories like C:\Users would be rejected. This platform inconsistency makes explicit directory names safer than root entries.

How do I check what directories are currently allowed?

Call the get_config tool with no arguments, or inspect the allowedDirectories field in the returned configuration. The array contents are displayed directly, though error messages from rejected operations also leak the whitelist for debugging purposes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →