{"record":{"id":"78a9de293ba2c0ad","repo":"affaan-m/ECC","slug":"refusing-to-read-non-file-path-filepath","errorCode":null,"errorMessage":"Refusing to read non-file path: ${filePath}","messagePattern":"Refusing to read non-file path: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install-lifecycle.js","lineNumber":520,"sourceCode":"    );\n    fs.ftruncateSync(fileDescriptor, 0);\n    fs.writeFileSync(fileDescriptor, content);\n    if (mode !== undefined) {\n      fs.fchmodSync(fileDescriptor, mode);\n    }\n  } finally {\n    fs.closeSync(fileDescriptor);\n  }\n}\n\nfunction readFileWithMetadataNoFollow(filePath, encoding) {\n  const flags = fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0);\n  const fileDescriptor = fs.openSync(filePath, flags);\n\n  try {\n    const stat = fs.fstatSync(fileDescriptor);\n    if (!stat.isFile()) {\n      throw new Error(`Refusing to read non-file path: ${filePath}`);\n    }\n    return {\n      content: fs.readFileSync(fileDescriptor, encoding),\n      mode: stat.mode,\n    };\n  } finally {\n    fs.closeSync(fileDescriptor);\n  }\n}\n\nfunction readFileNoFollow(filePath, encoding) {\n  return readFileWithMetadataNoFollow(filePath, encoding).content;\n}\n\nfunction readJsonNoFollow(filePath) {\n  return JSON.parse(readFileNoFollow(filePath, 'utf8'));\n}\n","sourceCodeStart":502,"sourceCodeEnd":538,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install-lifecycle.js#L502-L538","documentation":"readFileWithMetadataNoFollow opens files with O_NOFOLLOW and then double-checks with fstat that the opened descriptor is a regular file before reading. If the path resolved to something else (symlink, directory, FIFO, device), the library refuses to read it rather than following or misinterpreting it. This prevents TOCTOU symlink attacks when reading files named by untrusted install state.","triggerScenarios":"Calling the read helper with a path that is not a regular file at open time: a directory path, a symlink (O_NOFOLLOW makes open fail or the check fails), a FIFO/socket/device node, or a path swapped between the existsSync check and the read.","commonSituations":"install-state recording a destinationPath that is actually a directory; users symlinking managed files elsewhere; a race where another process replaces the file with a symlink or special file while install runs.","solutions":["Point the operation at a regular file path, not a directory, symlink, or special file.","If the target is a symlink to a real file, use the real file's path (or replace the symlink with a copy) since O_NOFOLLOW refuses to follow links.","Re-run the operation if it was a transient race; investigate what replaced the file if it recurs.","Verify the path with `ls -la` / `file` and ensure it is a plain regular file before retrying."],"exampleFix":"// before: state says destinationPath: ~/.claude/settings.json, but that is a directory\n// after\nrm -rf ~/.claude/settings.json   # remove the directory\necho '{}' > ~/.claude/settings.json  # real file, then retry install","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction assertRegularFileNoSymlink(p) {\n  const st = fs.lstatSync(p); // lstat: does not follow symlinks\n  if (!st.isFile()) throw new Error(`Not a regular file: ${p}`);\n}","typeGuard":"function isPlainRegularFile(p) {\n  try { const st = fs.lstatSync(p); return st.isFile() && !st.isSymbolicLink(); } catch { return false; }\n}","tryCatchPattern":"try {\n  await install(operations);\n} catch (err) {\n  if (/Refusing to read non-file path/.test(err.message)) {\n    console.error(`Check ${err.message.split(': ')[1]} — replace symlink/dir with a real file.`);\n  } else throw err;\n}","preventionTips":["Ensure paths in install-state point at regular files, never directories or symlinks.","Resolve symlinks to their real targets before recording paths in state.","Avoid concurrent processes that replace managed files with links during installs."],"tags":["security","symlink","file-read"],"backgroundTag":"incompatible-source-type","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}