{"record":{"id":"d6adf862f857a629","repo":"abhigyanpatwari/GitNexus","slug":"path-must-not-contain-null-bytes","errorCode":null,"errorMessage":"Path must not contain null bytes","messagePattern":"Path must not contain null bytes","errorType":"exception","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"gitnexus/src/server/validation.ts","lineNumber":82,"sourceCode":"  return value;\n}\n\n/**\n * Resolve a user-supplied relative path against an allowed root and verify it\n * stays inside that root. Mirrors the existing guard at api.ts:1067-1077.\n *\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}","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/validation.ts#L64-L100","documentation":"NUL bytes are rejected before path resolution because C-based syscalls truncate at NUL — the classic 'safe.txt\\0../../etc/passwd' injection trick — and Node itself fails with an opaque ERR_INVALID_ARG_VALUE on such paths. assertSafePath converts that into a clean, named 400 so file-serving endpoints never hand a NUL-bearing path to fs.","triggerScenarios":"URL-encoded nulls in a path parameter: ?path=docs%00/../../etc/passwd; binary junk or control characters pasted into the field; fuzzer-generated payloads.","commonSituations":"Deliberate path-traversal probes against file endpoints; scripts concatenating Buffers into strings; input from sources that don't strip control characters.","solutions":["Reject or strip control characters (< 0x20) in path fields client-side before sending","Treat occurrences as hostile input (log/WAF), not as something to retry unchanged","Keep assertSafePath as the server-side last line of defense — never decode user input and pass it raw to fs"],"exampleFix":"// before\nsendPath(userPath); // userPath = 'docs\\u0000/../../etc/passwd'\n\n// after\nconst CTRL = /[\\u0000-\\u001f]/;\nif (CTRL.test(userPath)) throw new Error('control characters in path');\nsendPath(userPath);","handlingStrategy":"validation","validationCode":"const CTRL = /[\\u0000-\\u001f]/;\nif (CTRL.test(userPath)) {\n  throw new Error('control characters in path — rejecting');\n}","typeGuard":"function isCleanPath(p: string): boolean {\n  return p.length > 0 && !/[\\u0000-\\u001f]/.test(p);\n}","tryCatchPattern":null,"preventionTips":["Reject control characters (< 0x20) in every path field at the client boundary","Treat NUL-bearing input as hostile (log it), never as a retry candidate","Never decode user input and pass it directly to fs APIs — keep assertSafePath as the last line of defense"],"tags":["security","null-byte","path-traversal","validation"],"backgroundTag":"null-byte-injection","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}