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.
Step 3: Broken Symlink Handling
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
Testing Symlink Security
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:
validatePathusesfs.realpathto resolve all symlinks before security checks. - Strict directory validation: The function verifies the resolved path starts with an allowed directory from the
allowedDirectorieslist. - 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.tsexplain the attack vectors being mitigated. - Comprehensive testing: The
test/test-symlink-security.jssuite validates protection against symlink traversal attacks.
Frequently Asked Questions
Can symlink traversal bypass directory restrictions in Desktop Commander MCP?
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.
What happens if a symlink points to a non-existent file?
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.
How does validatePath handle nested symbolic links?
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.
Where can I find the test suite for symlink security?
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →