abhigyanpatwari/GitNexus · error

Path traversal blocked: ${filePath}

Error message

Path traversal blocked: ${filePath}

What it means

Inside the rename tool, assertSafePath guards every file_path: it resolves the path against repo.repoPath and requires the result to share the repo root prefix (or equal it). Any input that escapes the root — ../ sequences, absolute paths outside the repo, or paths whose resolved form lands elsewhere — is rejected before any file is touched. It is a path-traversal security guard for a write-capable tool.

Source

Thrown at gitnexus/src/mcp/local/local-backend.ts:5585

    },
  ): Promise<any> {
    await this.ensureInitialized(repo);

    const { new_name, file_path } = params;
    const dry_run = params.dry_run ?? true;

    if (!params.symbol_name && !params.symbol_uid) {
      return { error: 'Either symbol_name or symbol_uid is required.' };
    }

    /** Guard: ensure a file path resolves within the repo root (prevents path traversal) */
    const assertSafePath = (filePath: string): string => {
      const full = path.resolve(repo.repoPath, filePath);
      const safePrefix = repo.repoPath.endsWith(path.sep)
        ? repo.repoPath
        : repo.repoPath + path.sep;
      if (!full.startsWith(safePrefix) && full !== repo.repoPath) {
        throw new Error(`Path traversal blocked: ${filePath}`);
      }
      return full;
    };

    // Step 1: Find the target symbol (reuse context's lookup)
    const lookupResult = await this.context(repo, {
      name: params.symbol_name,
      uid: params.symbol_uid,
      file_path,
    });

    if (lookupResult.status === 'ambiguous') {
      return lookupResult; // pass disambiguation through
    }
    if (lookupResult.error) {
      return lookupResult;
    }

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Pass repo-relative paths (e.g. 'src/core/foo.ts') instead of absolute or ../ paths.
  2. If the repo moved on disk, re-run `gitnexus analyze` from the new location so repoPath matches reality, then retry.
  3. When you must pass a path, derive it by stripping the repo root prefix client-side.
  4. Do not attempt to bypass the guard — it intentionally blocks writes outside the indexed repo.

Example fix

# before: absolute path outside the indexed repoPath
{"tool": "rename", "args": {"symbol_name": "parseRepo", "new_name": "resolveRepo",
  "file_path": "/home/me/other-checkout/src/backend.ts", "dry_run": true}}
# → Path traversal blocked: /home/me/other-checkout/src/backend.ts

# after: repo-relative path under the indexed root
{"tool": "rename", "args": {"symbol_name": "parseRepo", "new_name": "resolveRepo",
  "file_path": "src/backend.ts", "dry_run": true}}
Defensive patterns

Strategy: validation

Validate before calling

// Normalize to a repo-relative POSIX path before calling rename
import { relative, isAbsolute, resolve } from 'node:path';

function toRepoRelative(repoRoot: string, filePath: string): string {
  const abs = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
  const rel = relative(repoRoot, abs);
  if (rel.startsWith('..') || isAbsolute(rel)) {
    throw new Error(`file_path escapes repo root "${repoRoot}": ${filePath}`);
  }
  return rel.split('\\').join('/');
}

Type guard

const isSafeRepoPath = (repoRoot: string, filePath: string): boolean => {
  const full = resolve(repoRoot, filePath);
  const prefix = repoRoot.endsWith('/') ? repoRoot : repoRoot + '/';
  return full === repoRoot || full.startsWith(prefix);
}; // mirrors the server-side assertSafePath contract

Try / catch

try {
  return await client.callTool({ name: 'rename', arguments: params });
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Path traversal blocked')) {
    // client bug or stale repoPath: never bypass — rebase the path onto the indexed root
    const rel = toRepoRelative(indexedRoot, params.file_path);
    return client.callTool({ name: 'rename', arguments: { ...params, file_path: rel } });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the rename tool with file_path like '../../other-project/src/foo.ts', an absolute path that is not under repo.repoPath, or a path built from client-side absolute locations that differ from the indexed repoPath (repo moved/re-cloned since indexing).

Common situations: Clients forwarding absolute editor paths after the repo was moved to a new directory (prefix mismatch, not an attack); agents constructing paths from cwd of a different checkout; genuinely hostile input when the MCP server is exposed beyond loopback; symlinked workspaces resolving outside the root.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/65f673f9d2542e61. Report an issue: GitHub.