abhigyanpatwari/GitNexus · error · ForbiddenError

Path traversal denied

Error message

Path traversal denied

What it means

The core anti-traversal guard of assertSafePath: after resolving root + relative path, the result must equal the root or sit under it (prefix check with a platform path separator). If path.resolve escapes the root — via ../ chains, or an absolute path that shadows the join — the request is refused with ForbiddenError (HTTP 403). It mirrors the guard historically at api.ts:1067-1077 and is deliberately applied before any filesystem call.

Source

Thrown at gitnexus/src/server/validation.ts:88

 *
 * Returns the absolute resolved path. Rejects empty paths, null bytes, and
 * paths that resolve outside the root (e.g., `../../../etc/passwd`).
 *
 * @throws BadRequestError when the path is empty or contains a null byte
 * @throws ForbiddenError when the resolved path escapes the root
 */
export function assertSafePath(rawPath: string, root: string): string {
  if (rawPath.length === 0) {
    throw new BadRequestError('Path must not be empty');
  }
  if (rawPath.includes('\0')) {
    throw new BadRequestError('Path must not contain null bytes');
  }
  const resolvedRoot = path.resolve(root);
  const fullPath = path.resolve(resolvedRoot, rawPath);
  const safePrefix = resolvedRoot.endsWith(path.sep) ? resolvedRoot : resolvedRoot + path.sep;
  if (fullPath !== resolvedRoot && !fullPath.startsWith(safePrefix)) {
    throw new ForbiddenError('Path traversal denied');
  }
  return fullPath;
}

/**
 * Escape regex metacharacters in a user-supplied string so it can be safely
 * embedded as a literal in `new RegExp(...)`. Used by /api/grep's literal mode
 * and any future endpoint that constructs a regex from caller input.
 */
export function escapeRegExp(input: string): string {
  return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

/**
 * Default rate-limit policy for FS-touching API routes (CodeQL
 * js/missing-rate-limiting). Tuned for the local-bound HTTP server's expected
 * traffic — interactive web UI use stays well under the limit; abusive loops
 * trip 429.

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Send paths relative to the endpoint's documented root (the repo/index root)
  2. If only a filename is meaningful, send just the basename and let the server join it
  3. Server-side, keep using assertSafePath (or path.basename) ahead of every fs call — never rely on client promises

Example fix

// before
api.readFile({ path: '/home/me/repo/src/a.ts' }); // absolute → 403

// after
api.readFile({ path: 'src/a.ts' }); // relative to the repo root
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';
if (path.isAbsolute(rel) || rel.split(/[\\/]+/).includes('..')) {
  throw new Error('send a path relative to the repo root without ".." segments');
}

Prevention

When it happens

Trigger: ?path=../../../etc/passwd, ?path=/etc/passwd (an absolute path resolves outside the allowed root), or Windows drive-relative forms like ?path=..\..\c:\windows.

Common situations: Clients sending absolute local paths where a repo-relative path is expected; UI file trees passing the full local path instead of the displayed relative one; deliberate traversal probes.

Related errors


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