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.sepensures/home/userdoes 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."
Symlink and Path Traversal Defenses
validatePath() implements multi-layer protections in src/tools/filesystem.ts:
- Symlink resolution:
fs.realpath()resolves symbolic links before whitelist checking - Path normalization: Collapses navigation sequences that could escape boundaries
- 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
Restrict to Single Project (Recommended)
{
"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
allowedDirectoriesis 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_valueto 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.
Can the assistant escape an allowed directory using ../ or symlinks?
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →