How Desktop Commander MCP Prevents Symlink Traversal Attacks in File System Operations

Desktop Commander MCP prevents symlink traversal attacks through a multi-layered validation routine in src/tools/filesystem.ts that resolves real paths, validates parent directories for new files, and enforces strict allowlist checks before any file operation.

Desktop Commander MCP is a Model Context Protocol (MCP) server that exposes desktop file system operations to AI agents. Because exposing file system access creates significant security risks, the codebase implements rigorous protections against symlink traversal attacks—attacks where malicious symbolic links redirect operations outside permitted directories.

Symlink traversal attacks exploit symbolic links to bypass directory restrictions. An attacker might place a symlink inside an allowed directory that points to sensitive system files (like /etc/passwd), tricking the application into reading or writing to unauthorized locations. Desktop Commander MCP mitigates this threat through a validation pipeline that resolves every path to its canonical form before processing.

Core Security Implementation in validatePath

The validatePath function in src/tools/filesystem.ts serves as the gatekeeper for all file operations. It implements a four-stage verification process that ensures paths cannot escape the configured sandbox.

Real-Path Resolution with fs.realpath

When a path is provided to any file operation, validatePath first calls fs.realpath on the full absolute path. If the path exists, this resolves every component of the chain—including intermediate symlinks—to its canonical target. According to the source code in src/tools/filesystem.ts lines 39-47, this prevents attackers from using relative path components or symlink chains to obfuscate the final destination.

Parent Directory Validation for Non-Existent Targets

For operations targeting new files that do not yet exist, the function cannot resolve the full path. Instead, it walks up the directory tree until it finds the deepest existing ancestor, resolves that parent directory via fs.realpath, and then reconstructs the intended path. As implemented in lines 58-86 of src/tools/filesystem.ts, this prevents an attacker from placing a symlink inside an allowed directory that points to a restricted location, then writing through a non-existent sub-path.

Strict Allowlist Enforcement

After obtaining the resolved real path, the isPathAllowed helper compares it against the configured allowedDirectories. The comparison uses normalized, lower-cased paths with trailing-separator guards to prevent partial-name matches (lines 77-100). Only paths that are literal subdirectories of allowed entries proceed. If validation fails, the function throws an error and logs a server_path_validation_error event (lines 92-99), blocking the operation before any file access occurs.

Automated Security Testing

The repository includes a dedicated test suite at test/test-symlink-security.js that validates these protections. Lines 13-30 create realistic attack scenarios—including directory and file symlinks pointing outside the allowed tree—and assert that validatePath correctly blocks them. Running these tests confirms that the real-path resolution and allowlist checks effectively neutralize symlink-based escape attempts.

Configuration and Practical Usage

To utilize these protections, configure the allowed directories using the configManager before performing operations:

import { configManager } from './config-manager.js';
import { validatePath } from './tools/filesystem.js';

// Restrict operations to a specific safe directory
await configManager.setValue('allowedDirectories', ['/home/claude/projects']);

// Valid access to file within allowed directory
const safePath = await validatePath('projects/readme.md');
// Returns: '/home/claude/projects/readme.md'

// Blocked access through malicious symlink
try {
  await validatePath('projects/link_to_passwd');
} catch (e) {
  console.error(e.message); // "Path not allowed: projects/link_to_passwd ..."
}

You can verify the symlink protection by executing the dedicated test suite:

npm run test:test-symlink-security

# or

node test/test-symlink-security.js

All tests should pass, confirming that symlink traversal is blocked as documented in the project's SECURITY.md (lines 23-27).

Summary

  • Desktop Commander MCP uses a hardened validatePath function in src/tools/filesystem.ts to prevent symlink traversal attacks.
  • Real-path resolution via fs.realpath (lines 39-47) canonicalizes all paths before validation.
  • Parent directory fallback (lines 58-86) secures operations on non-existent files by validating the deepest existing ancestor.
  • Strict allowlist checks (lines 77-100) ensure resolved paths reside within configured allowedDirectories.
  • Automated testing in test/test-symlink-security.js (lines 13-30) continuously verifies attack prevention.

Frequently Asked Questions

When validatePath encounters a path, it calls fs.realpath to resolve all symlinks to their actual targets before checking permissions. If a symlink inside an allowed directory points outside the sandbox (e.g., to /etc/passwd), the resolved path fails the isPathAllowed check and the operation is rejected with a server_path_validation_error.

No. For non-existent targets, the code resolves the parent directory first (lines 58-86 in src/tools/filesystem.ts). If the deepest existing ancestor is a symlink pointing outside the allowed tree, fs.realpath exposes the true location, causing the validation to fail before the new file or directory is created.

What happens if the real-path resolution fails or the path escapes the allowlist?

The validatePath function throws an error indicating the path is not allowed (lines 92-99). This error propagates back to the MCP tool caller, preventing any file system operation from executing on the unauthorized path.

The security design and symlink traversal risks are documented in SECURITY.md (lines 23-27) and the README.md security section. The implementation details reside in src/tools/filesystem.ts, while automated verification lives in test/test-symlink-security.js.

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 →