# Symlink Traversal Prevention in Desktop Commander: How validatePath Secures MCP File Operations

> Desktop Commander prevents symlink traversal by resolving and validating paths against an allowlist. Learn how validatePath secures MCP file operations against malicious attacks. Read more now.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: security
- Published: 2026-07-10

---

**Desktop Commander prevents symlink traversal attacks by resolving all symbolic links in a path before any filesystem operation and validating the canonical result against a whitelist of allowed directories.**

Desktop Commander is an MCP (Model Context Protocol) client that exposes filesystem operations to AI assistants, making robust symlink traversal prevention essential for security. The `validatePath` function in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts) serves as the primary security gate, ensuring all file operations remain confined to permitted directories even when symbolic links attempt to redirect access to sensitive system locations like `/etc/passwd`.

## How Desktop Commander Mitigates Symlink Attacks

If an attacker places a symbolic link inside an allowed folder that points to a restricted location, an unprotected MCP client might unwittingly read or write outside its sandbox. Desktop Commander implements a three-step defense: resolve every symlink in the requested path, normalize the absolute path, and compare it against a whitelist of allowed directories before executing any operation.

### The validatePath Security Gate

All public filesystem APIs—including `readFile`, `writeFile`, `createDirectory`, and `moveFile`—call `validatePath` as their first operation. According to the source code in [`src/handlers/filesystem-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/handlers/filesystem-handlers.ts), these handlers immediately pass user input through this validation layer before touching the disk.

The function performs three decisive actions:

1. **Canonicalization** – Expands `~` and resolves relative components via `normalizePath` (L 34‑36) and `expandHome` (L 38‑42).
2. **Symlink Resolution** – Uses `fs.realpath` for existing files and a parent-directory walk for new paths (the "SECURITY FIX" at L 58‑86).
3. **Whitelist Enforcement** – Validates the resolved path against `allowedDirectories` via `isPathAllowed` (L 71‑115), throwing an error and emitting `server_path_validation_error` telemetry if the path escapes the sandbox.

### Canonical Path Resolution

Before whitelist comparison, the path undergoes rigorous normalization. The `expandHome` utility converts `~` to the actual home directory, while `normalizePath` produces an absolute path and handles case-insensitive platforms by lowercasing paths when necessary. This ensures that `/home/user/../projects` and `~/projects` resolve to identical canonical strings before security checks apply.

## Step-by-Step Symlink Resolution Logic

The `validatePath` implementation distinguishes between existing file paths and new file paths, applying different resolution strategies to handle symlinks in parent directories.

### Resolving Existing Files

For paths where the target already exists, the function calls `fs.realpath` (L 44‑48). This native Node.js method automatically follows all symbolic links in the path chain and returns the true canonical location. If the file exists, this single call reveals any redirection attempts hidden in the path.

### Resolving New File Paths (The Critical Security Fix)

When `fs.realpath` throws `ENOENT` (file does not exist), the code executes the security-critical logic at L 58‑86. Instead of failing, it:

1. Walks up the directory tree to find the nearest existing ancestor.
2. Resolves that ancestor using `fs.realpath` to reveal any symlinks in parent directories.
3. Re-attaches the non-existent leaf component to the resolved parent path.

This prevents a bypass where an attacker places a symlink in a parent directory of a new file. For example, if `~/projects/malicious` points to `/etc` and the user attempts to create `~/projects/malicious/passwd`, the resolution catches the symlink in the parent chain and reveals the true target path `/etc/passwd`.

## Whitelist Validation and Enforcement

After resolution, `isPathAllowed` (L 71‑115) validates the canonical path against the whitelist retrieved from `getAllowedDirs` (L 11‑25). The whitelist checks include:

- **Exact match** – The resolved path exactly equals an allowed directory.
- **Sub-directory validation** – Ensures the resolved path is genuinely a child of an allowed root (preventing `/home/user` from matching `/home/username` via prefix checks).
- **Windows drive handling** – Special logic for Windows drive letters (e.g., `c:`).

If validation fails, the function throws an error at L 92‑100 and logs a `server_path_validation_error` event via the telemetry helper in [`src/utils/capture.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/capture.ts). Only after passing all checks does `validatePath` return the safe, resolved path (L 101‑106) for use by downstream operations.

## Practical Implementation Examples

The validation happens automatically when using the public API. Here are common scenarios:

### Reading a File Safely

```typescript
import { readFile } from './tools/filesystem.js';

(async () => {
  try {
    const result = await readFile('~/projects/config.json');
    console.log('Content:', result.content);
  } catch (e) {
    console.error('Security validation failed:', e.message);
  }
})();

```

Behind the scenes, `readFile → validatePath` ensures the path resolves inside the permitted directories before `fs.readFile` executes.

### Writing Files Without Symlink Escapes

```typescript
import { writeFile } from './tools/filesystem.js';

await writeFile('~/projects/report.txt', ' confidential data', 'rewrite');

```

If a malicious symlink existed at `~/projects/malicious-link` pointing to `/etc/passwd`, attempting to write to `~/projects/malicious-link` would resolve the parent directory, detect the symlink, and reject the operation because `/etc/passwd` falls outside the allowed directories.

### Creating Nested Directories

```typescript
import { createDirectory } from './tools/filesystem.js';

await createDirectory('~/projects/2024/quarterly/nested');

```

Even though `2024/quarterly/nested` does not exist, `validatePath` walks up the tree, resolves any symlinks in existing ancestors, and validates the final resolved location before `fs.mkdir` creates the folders.

### Moving Files with Dual Validation

```typescript
import { moveFile } from './tools/filesystem.js';

await moveFile('~/projects/temp.txt', '~/projects/archive/final.txt');

```

Both source and destination paths undergo independent validation, ensuring neither can be a symlink pointing outside the trusted area.

## Summary

- **Atomic validation**: `validatePath` in [`src/tools/filesystem.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/tools/filesystem.ts) resolves all symlinks before any filesystem operation, preventing race conditions.
- **Parent-directory resolution**: The "SECURITY FIX" (L 58‑86) handles new files by resolving parent directories to catch symlinks in ancestor paths.
- **Whitelist enforcement**: `isPathAllowed` (L 71‑115) checks against configurable `allowedDirectories` from [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts) with strict sub-directory validation.
- **Telemetry integration**: Failed validations emit `server_path_validation_error` events for security monitoring.
- **Universal coverage**: All public handlers in [`src/handlers/filesystem-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/handlers/filesystem-handlers.ts) invoke this validation, ensuring consistent protection across read, write, move, and delete operations.

## Frequently Asked Questions

### How does Desktop Commander handle broken symlinks?

Desktop Commander resolves symlinks using `fs.realpath` during the validation phase. If a symlink is broken (points to a non-existent target), `fs.realpath` will throw an error, which causes `validatePath` to reject the path before any operation occurs. This prevents exploitation attempts using dangling symlinks that might later be re-pointed to sensitive locations.

### Can the allowed directories be configured?

Yes, the whitelist is configurable via the `allowedDirectories` setting in the configuration, which `getAllowedDirs` (L 11‑25) retrieves from [`src/config-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/config-manager.ts). If no directories are explicitly configured, the system falls back to the user's home directory. Administrators can specify multiple allowed roots to grant access to specific project folders while maintaining sandbox boundaries.

### What happens if a symlink points outside the whitelist?

If `validatePath` resolves a symlink and discovers the canonical path falls outside any allowed directory, it immediately throws a validation error at L 92‑100. The operation aborts before touching the filesystem, and the system logs a `server_path_validation_error` telemetry event. The error message indicates the path is outside the allowed directories without revealing sensitive system paths.

### Is there a race condition between validation and file access?

No. Desktop Commander performs symlink resolution and whitelist validation **before** calling any filesystem mutating methods like `fs.readFile`, `fs.writeFile`, or `fs.mkdir`. By using the resolved canonical path returned from `validatePath` (L 101‑106) for all subsequent operations, the code eliminates the Time-of-Check Time-of-Use (TOCTOU) vulnerability that exists in check-then-act patterns.