abhigyanpatwari/GitNexus · error

Path traversal denied

Error message

Path traversal denied

What it means

HTTP 403 from the repo file-read API when the requested path resolves outside the repository root. The check is the canonical CodeQL-recognized sanitizer: it resolves repoRoot and the joined path, computes path.relative(repoRoot, fullPath), and denies when the result starts with '..' or is absolute. This blocks both traversal (../ chains) and absolute-path injection (leading / or Windows drive letters) before the fs.readFile sink.

Source

Thrown at gitnexus/src/server/api.ts:604

    // parameter-tampering, same class as the /api/grep critical fix).
    const rawFilePath = req.query.path;
    if (rawFilePath === undefined || rawFilePath === '') {
      res.status(400).json({ error: 'Missing path' });
      return;
    }
    const filePath = assertString(rawFilePath, 'path');

    // Path-injection containment — inline at the sink with the canonical
    // path.relative idiom that CodeQL's js/path-injection sanitizer
    // recognizes. assertSafePath in validation.ts performs the equivalent
    // check, but cross-module helpers are not followed by CodeQL's
    // interprocedural analysis for path-traversal sanitization in JS, so
    // the barrier must be visible inline at the readFile sink.
    const repoRoot = path.resolve(repoPath);
    const fullPath = path.resolve(repoRoot, filePath);
    const fullRel = path.relative(repoRoot, fullPath);
    if (fullRel.startsWith('..') || path.isAbsolute(fullRel)) {
      res.status(403).json({ error: 'Path traversal denied' });
      return;
    }

    const raw = await fs.readFile(fullPath, 'utf-8');

    // Optional line-range support: ?startLine=10&endLine=50
    // Returns only the requested slice (0-indexed), plus metadata.
    const startLine = req.query.startLine !== undefined ? Number(req.query.startLine) : undefined;
    const endLine = req.query.endLine !== undefined ? Number(req.query.endLine) : undefined;

    if (startLine !== undefined && Number.isFinite(startLine)) {
      const lines = raw.split('\n');
      const start = Math.max(0, startLine);
      const end =
        endLine !== undefined && Number.isFinite(endLine)
          ? Math.min(lines.length, endLine + 1)
          : lines.length;
      res.json({

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Send paths relative to the repo root without a leading slash: ?path=README.md, ?path=src/index.ts
  2. Strip leading slashes and reject '..' segments client-side before building the URL
  3. If you genuinely need a file outside the repo, re-index that location as its own repository instead of traversing
  4. Treat this 403 in security tests as a pass — the containment is working; fix the caller, not the server

Example fix

// before
fetch(`${base}/api/file?path=${absolutePathOnDisk}`); // 403

// after
const rel = path.relative(repoRoot, absolutePathOnDisk); // normalize first
if (rel.startsWith('..') || path.isAbsolute(rel)) throw new Error('outside repo');
fetch(`${base}/api/file?path=${encodeURIComponent(rel)}`);
Defensive patterns

Strategy: validation

Validate before calling

// Normalize to a repo-relative path and reject escapes before the request.
function toRepoRelative(repoRoot: string, p: string): string {
  const rel = path.relative(path.resolve(repoRoot), path.resolve(repoRoot, p));
  if (rel.startsWith('..') || path.isAbsolute(rel)) throw new Error(`path escapes repo root: ${p}`);
  return rel;
}

Try / catch

const res = await fetch(url);
if (res.status === 403) {
  const { error } = await res.json();
  if (error === 'Path traversal denied') throw new TypeError(`path must be repo-relative: ${requestedPath}`);
}

Prevention

When it happens

Trigger: GET ?path=../../etc/passwd or any ..-chain escaping the repo; ?path=/etc/passwd (absolute path: path.relative yields an absolute-ish result on other roots); Windows-shaped input like ?path=C:\Windows\system.ini; encoded traversal (%2e%2e%2f) which express decodes before the check, so it is caught the same way.

Common situations: Frontends concatenating a user-typed absolute path onto the query; file trees containing symlinks whose textual representation is stored as an absolute path; security scanners (CodeQL, Burp) probing the endpoint — this 403 is the expected, correct response; client code accidentally sending a leading slash for repo-root files (?path=/README.md).

Related errors


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