{"record":{"id":"423c811c019dda69","repo":"abhigyanpatwari/GitNexus","slug":"path-traversal-denied","errorCode":null,"errorMessage":"Path traversal denied","messagePattern":"Path traversal denied","errorType":"exception","errorClass":"ForbiddenError","httpStatus":403,"severity":"error","filePath":"gitnexus/src/server/validation.ts","lineNumber":88,"sourceCode":" *\n * Returns the absolute resolved path. Rejects empty paths, null bytes, and\n * paths that resolve outside the root (e.g., `../../../etc/passwd`).\n *\n * @throws BadRequestError when the path is empty or contains a null byte\n * @throws ForbiddenError when the resolved path escapes the root\n */\nexport function assertSafePath(rawPath: string, root: string): string {\n  if (rawPath.length === 0) {\n    throw new BadRequestError('Path must not be empty');\n  }\n  if (rawPath.includes('\\0')) {\n    throw new BadRequestError('Path must not contain null bytes');\n  }\n  const resolvedRoot = path.resolve(root);\n  const fullPath = path.resolve(resolvedRoot, rawPath);\n  const safePrefix = resolvedRoot.endsWith(path.sep) ? resolvedRoot : resolvedRoot + path.sep;\n  if (fullPath !== resolvedRoot && !fullPath.startsWith(safePrefix)) {\n    throw new ForbiddenError('Path traversal denied');\n  }\n  return fullPath;\n}\n\n/**\n * Escape regex metacharacters in a user-supplied string so it can be safely\n * embedded as a literal in `new RegExp(...)`. Used by /api/grep's literal mode\n * and any future endpoint that constructs a regex from caller input.\n */\nexport function escapeRegExp(input: string): string {\n  return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Default rate-limit policy for FS-touching API routes (CodeQL\n * js/missing-rate-limiting). Tuned for the local-bound HTTP server's expected\n * traffic — interactive web UI use stays well under the limit; abusive loops\n * trip 429.","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/validation.ts#L70-L106","documentation":"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.","triggerScenarios":"?path=../../../etc/passwd, ?path=/etc/passwd (an absolute path resolves outside the allowed root), or Windows drive-relative forms like ?path=..\\..\\c:\\windows.","commonSituations":"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.","solutions":["Send paths relative to the endpoint's documented root (the repo/index root)","If only a filename is meaningful, send just the basename and let the server join it","Server-side, keep using assertSafePath (or path.basename) ahead of every fs call — never rely on client promises"],"exampleFix":"// before\napi.readFile({ path: '/home/me/repo/src/a.ts' }); // absolute → 403\n\n// after\napi.readFile({ path: 'src/a.ts' }); // relative to the repo root","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nif (path.isAbsolute(rel) || rel.split(/[\\\\/]+/).includes('..')) {\n  throw new Error('send a path relative to the repo root without \"..\" segments');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always send repo-relative paths, never absolute local paths","When only a filename matters, send the basename and let the server join it","Never bypass or catch-and-ignore the 403 — it means the path would escape the allowed root"],"tags":["security","path-traversal","filesystem","http-403"],"backgroundTag":"path-traversal-attempt","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}