{"record":{"id":"41e6529177a4fd71","repo":"abhigyanpatwari/GitNexus","slug":"filename-must-be-a-regular-file","errorCode":null,"errorMessage":"${filename} must be a regular file","messagePattern":"(.+?) must be a regular file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gitnexus/src/config/repo-control-file.ts","lineNumber":22,"sourceCode":"export const MAX_REPO_CONTROL_FILE_BYTES = 1024 * 1024;\n\n/** Read a bounded, regular control file owned by the repository root. */\nexport async function readRepoControlFile(\n  repoRoot: string,\n  filename: string,\n): Promise<string | null> {\n  const requestedRoot = path.resolve(repoRoot);\n  const requested = path.resolve(requestedRoot, filename);\n  const relative = path.relative(requestedRoot, requested);\n  if (relative.startsWith('..') || path.isAbsolute(relative)) {\n    throw new Error(`${filename} resolves outside the repository root`);\n  }\n\n  try {\n    const canonicalRoot = fs.realpathSync(requestedRoot);\n    const beforeOpen = fs.lstatSync(requested);\n    if (beforeOpen.isSymbolicLink()) throw new Error(`${filename} must not be a symbolic link`);\n    if (!beforeOpen.isFile()) throw new Error(`${filename} must be a regular file`);\n    if (beforeOpen.nlink !== 1) throw new Error(`${filename} must not be a hard link`);\n    if (beforeOpen.size > MAX_REPO_CONTROL_FILE_BYTES) {\n      throw new Error(`${filename} exceeds ${MAX_REPO_CONTROL_FILE_BYTES} bytes`);\n    }\n    return await new Promise<string>((resolve, reject) => {\n      const stream = fs.createReadStream(requested, {\n        flags: 'r',\n        start: 0,\n        end: MAX_REPO_CONTROL_FILE_BYTES,\n        autoClose: true,\n      });\n      const chunks: Buffer[] = [];\n      let totalBytes = 0;\n      let validated = false;\n      let settled = false;\n\n      const finish = (value: string): void => {\n        if (settled) return;","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/52924ef12c2290ceee4612526a828ec4cdf2047f/gitnexus/src/config/repo-control-file.ts#L4-L40","documentation":"readRepoControlFile validates repository control files (e.g. .gitnexus config files) before reading them to prevent symlink/hardlink-based tampering. Before opening, it lstats the path and rejects anything that is not a regular file. This guard ensures the library never reads content from devices, directories, FIFOs, or other special file types.","triggerScenarios":"Calling readRepoControlFile (directly or via loadAnalyzeConfigStrict / content) when the target path exists but lstat reports a non-regular type: a directory, FIFO, socket, device node, or symlink (symlink has its own message).","commonSituations":"A developer creates a directory where the control file is expected (e.g. mkdir .gitnexusrc by mistake); a misconfigured path points at /dev/stdin or a named pipe in CI; a provisioning script left a socket or fifo at the config path.","solutions":["Check the path with `fs.lstatSync(path).isFile()` and remove/replace the non-regular entry.","If a directory was created by mistake, remove it (`rm -r`) and create the expected regular file.","Verify the configured filename/path passed to loadAnalyzeConfigStrict points at the intended regular file.","Re-run the command; the file is re-validated on every read."],"exampleFix":"// before: path is a directory/FIFO\nrm -rf .gitnexusrc\n// after: write a regular file\nprintf 'mode: strict\\n' > .gitnexusrc","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nconst st = fs.lstatSync(controlFilePath);\nif (!st.isFile()) {\n  throw new Error(`${controlFilePath} is not a regular file; remove it and create a plain file`);\n}","typeGuard":"function isRegularFile(p: string): boolean {\n  try { return fs.lstatSync(p).isFile(); } catch { return false; }\n}","tryCatchPattern":"try {\n  const content = await readRepoControlFile(root, filename);\n} catch (err) {\n  if ((err as Error).message.includes('must be a regular file')) {\n    fs.rmSync(path.join(root, filename), { recursive: true, force: true });\n    // recreate or skip\n  } else throw err;\n}","preventionTips":["Never create directories, FIFOs, or device nodes at control-file paths.","Add a preflight `lstat` check in scripts that generate the config.","Keep control files in version control so they are always plain regular files."],"tags":["filesystem","security","validation","config"],"backgroundTag":"path-is-not-a-regular-file","analyzedSha":"52924ef12c2290ceee4612526a828ec4cdf2047f","analyzedAt":"2026-09-01T13:15:02.810Z","contentChangedAt":"2026-09-01T13:15:02.810Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}