Skip to content

fix(security): prevent path traversal in getFileSkeleton (CWE-22)#35

Open
sebastiondev wants to merge 1 commit intoforloopcodes:mainfrom
sebastiondev:fix/cwe22-file-skeleton-get-488e
Open

fix(security): prevent path traversal in getFileSkeleton (CWE-22)#35
sebastiondev wants to merge 1 commit intoforloopcodes:mainfrom
sebastiondev:fix/cwe22-file-skeleton-get-488e

Conversation

@sebastiondev
Copy link
Copy Markdown
Contributor

Summary

This PR adds a path-containment check to the getFileSkeleton tool to prevent path traversal (CWE-22) when reading files via the MCP get_file_skeleton tool.

Vulnerability

Data flow

The MCP tool exposes file_path directly to callers. Inside getFileSkeleton:

const fullPath = resolve(options.rootDir, options.filePath);
// ...
const content = await readFile(fullPath, "utf-8");

path.resolve happily collapses .. segments and accepts absolute paths, so a file_path like ../../../etc/passwd or /etc/passwd resolves outside rootDir and is then read. There was no containment check before the readFile call. A symlink inside the project that points outside rootDir would also have been followed.

This is the same class of issue already fixed in sibling tools in this repo:

getFileSkeleton was the remaining unprotected file-read sink of this shape.

Fix

Added an assertInsideRoot(fullPath, rootDir) helper that:

  1. Resolves both rootDir and the requested path, then verifies the requested path is the root or starts with root + "/" (the trailing separator avoids the classic /srv/app-evil vs /srv/app prefix bug).
  2. Calls fs.realpath on both and re-checks containment, so symlinks inside the project that point outside it are also rejected.
  3. If the file doesn't exist (ENOENT from realpath), falls back to the lexical check, preserving the existing error semantics for missing files.

The check runs before isSupportedFile / readFile, so it gates every code path through the function. Behaviour for legitimate in-tree paths is unchanged.

Tests

Added test/main/cwe22-file-skeleton.test.mjs covering:

  • ../ traversal is rejected
  • absolute paths outside rootDir are rejected
  • symlinks pointing outside rootDir are rejected
  • legitimate in-tree files still work

Full suite passes locally: 212/212.

Security analysis

Exploitation requires the attacker to control the file_path argument to get_file_skeleton. In an MCP context this is reachable in two realistic ways:

  1. Prompt injection that causes the connected LLM to call the tool with an attacker-chosen path.
  2. A malicious or compromised MCP client invoking the tool directly.

In either case, before this fix the server would return the contents (or a 20-line preview, for unsupported extensions) of any file readable by the server process — .env, SSH keys, shadow files on misconfigured deployments, etc. The fix constrains reads to rootDir and its descendants, matching the trust boundary the rest of the codebase already enforces.

Adversarial review

Before submitting, we tried to talk ourselves out of this one. The tool name suggests it's only for project files, but nothing in the call path enforced that — path.resolve is not a sandbox, and there's no allowlist, no chroot, and no framework-level filter between the MCP transport and readFile. We also checked whether the project intentionally exposes arbitrary reads elsewhere; it doesn't, and the three prior CWE-22 fixes show the maintainers treat rootDir as the boundary. So the exposure is real and the fix is consistent with existing precedent.

cc @lewiswigmore

…ry traversal (CWE-22)

Validate that resolved file paths stay within rootDir before any file read.
Also resolves symlinks to prevent symlink-based escapes.

Without this check, an agent (or prompt-injected agent) could call
get_file_skeleton with paths like "../../../../etc/passwd" to read
arbitrary files outside the project root.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 1, 2026

@sebastiondev is attempting to deploy a commit to the ForLoopCodes' projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant