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
- Send paths relative to the repo root without a leading slash: ?path=README.md, ?path=src/index.ts
- Strip leading slashes and reject '..' segments client-side before building the URL
- If you genuinely need a file outside the repo, re-index that location as its own repository instead of traversing
- 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
- Always request repo-relative paths without leading slashes
- Strip '..' segments client-side before building URLs
- Never send absolute filesystem paths to the file API
- In security tests, treat this 403 as the pass condition
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
- Path traversal denied
- Path traversal blocked: ${filePath}
- Path must not contain null bytes
- Write queries are not allowed via the HTTP API
- origin_not_allowed
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/879a0aebb96d4abf.
Report an issue: GitHub.