How Symlink Traversal Prevention Works in Desktop Commander MCP Path Validation

Desktop Commander MCP prevents symlink traversal attacks by resolving all symbolic links to their canonical targets using fs.realpath before validating paths against allowed directories.

The validatePath function in src/tools/filesystem.ts serves as the security gatekeeper for the wonderwhy-er/DesktopCommanderMCP repository. Every file system operation routes through this function, ensuring that symbolic links cannot bypass directory restrictions by revealing their true destinations prior to security evaluation.

The Core Mechanism: Real-Path Resolution

At the heart of the protection lies the fs.realpath (or fs.promises.realpath) call within validatePath. This Node.js API collapses every symbolic link in a path chain to reveal the actual filesystem location. Without this resolution step, a path such as /allowed/evil → /etc/passwd would appear to reside within the allowed directory while actually pointing to a restricted system file.

The function maintains a repository-wide allowedDirectories list and verifies that the resolved canonical path starts with one of these permitted locations. As implemented in src/tools/filesystem.ts, this check occurs only after full symlink resolution, ensuring that security policy evaluates the true target rather than the potentially deceptive link.

Step-by-Step Validation Process

Step 1: Canonical Path Resolution

When validatePath receives a user-supplied path, it immediately calls fs.realpath to obtain the absolute, canonical form. This step traverses any symbolic links in the chain, converting relative segments and link destinations into a concrete filesystem location.

Step 2: Allowed Directory Verification

After resolution, the function iterates over the allowedDirectories array. It checks whether the resolved path starts with any of the permitted directory paths using a strict prefix match. If the resolved location lies outside all allowed directories, the function throws an error and aborts the operation.

If a symlink in the chain points to a non-existent target, fs.realpath throws an error. The catch block in validatePath re-throws this as a clear "Failed to resolve symlink" error. This fail-fast behavior prevents attackers from using dangling symlinks to circumvent security checks or trigger undefined behavior.

Security Implementation Details

The source code contains explicit security comments documenting the threat model. According to lines 260-262 in src/tools/filesystem.ts: "Without this, an attacker could create a symlink inside an allowed directory that symlink—bypassing the directory restriction check."

Additionally, lines 301-303 state: "SECURITY: Always return the resolved path (with symlinks resolved) so that the canonical target, not a symlink that could point outside allowed directories, is used."

These comments emphasize that the function must return the resolved path—not the original input—to ensure that subsequent operations use the verified canonical location rather than a potentially malicious link.

Practical Code Examples

The following example demonstrates safe file reading with automatic symlink traversal prevention:

import { validatePath, readFile } from './tools/filesystem.js';

async function safeRead(userPath: string) {
  // 1️⃣ Resolve and validate the path
  const safePath = await validatePath(userPath);

  // 2️⃣ Perform the read operation on the verified location
  return await readFile(safePath);
}

// Attempting a symlink attack – will be rejected
await safeRead('/allowed/evil'); // ❌ throws "Access outside allowed directories"

This test case from the security suite demonstrates how symlink bypass attempts are blocked:

import { validatePath } from '../dist/tools/filesystem.js';
import * as fs from 'fs/promises';
import * as path from 'path';

const ALLOWED_DIR = path.join(__dirname, 'test_symlink_allowed');
const RESTRICTED_DIR = path.join(__dirname, 'test_symlink_restricted');
const SYMLINK_TO_RESTRICTED = path.join(ALLOWED_DIR, 'evil');

// Create a symlink that points outside the allowed area
await fs.symlink(RESTRICTED_DIR, SYMLINK_TO_RESTRICTED);

// This call fails because `validatePath` resolves the symlink to RESTRICTED_DIR
await validatePath(SYMLINK_TO_RESTRICTED); // ❌ throws

The repository includes a dedicated test suite in test/test-symlink-security.js that validates the effectiveness of these protections. This file attempts various symlink-based directory traversal attacks to ensure that validatePath correctly rejects paths that resolve outside the allowed boundaries.

Limitations and Documentation

While the real-path resolution provides robust protection, the project documentation acknowledges remaining security considerations. The README.md file contains a "Known Security Limitations" section, and FAQ.md specifically addresses directory restriction bypasses via symlinks. These documents provide additional context on the security model and potential edge cases that administrators should monitor.

Summary

  • Canonical resolution first: validatePath uses fs.realpath to resolve all symlinks before security checks.
  • Strict directory validation: The function verifies the resolved path starts with an allowed directory from the allowedDirectories list.
  • Fail-fast on broken links: Dangling symlinks throw explicit errors rather than bypassing security.
  • Source-level documentation: Security comments at lines 260-262 and 301-303 in src/tools/filesystem.ts explain the attack vectors being mitigated.
  • Comprehensive testing: The test/test-symlink-security.js suite validates protection against symlink traversal attacks.

Frequently Asked Questions

No. The validatePath function in src/tools/filesystem.ts resolves all symbolic links using fs.realpath before checking against the allowedDirectories list. This ensures that even if a user creates a symlink inside an allowed directory pointing to a restricted location, the resolved canonical path is evaluated for security, not the symlink itself.

The function throws an error. When fs.realpath encounters a broken symlink, it raises an exception that validatePath catches and re-throws as a "Failed to resolve symlink" error. This prevents attackers from using dangling symlinks to circumvent security checks or exploit race conditions.

fs.realpath recursively resolves the entire symlink chain, regardless of nesting depth. Whether the path contains a single symlink or multiple chained links, the API returns the ultimate canonical target. validatePath then validates this final destination against the allowed directories, ensuring that multi-hop symlink attacks are also blocked.

The dedicated security tests reside in test/test-symlink-security.js. This file demonstrates various symlink traversal attempts and verifies that the validatePath function correctly rejects paths resolving outside permitted directories. The repository also documents known limitations in README.md and attack vectors in FAQ.md.

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 →