{"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":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/install-lifecycle.js","lineNumber":414,"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":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/scripts/lib/install-lifecycle.js#L396-L432","documentation":"readFileWithMetadataNoFollow opens the path with O_RDONLY | O_NOFOLLOW (so the final component is not followed) and then fstat's the descriptor. If the descriptor does not point at a regular file (S_ISREG false — e.g. it is a directory, pipe, socket, device, or symlink target), reading is refused. This prevents the installer from reading attacker-controlled non-file entries as if they were file contents.","triggerScenarios":"Any internal call to readFileNoFollow / readFileWithMetadataNoFollow / readJsonNoFollow / copyContainedFile when the resolved path is a directory, a symlink (blocked by O_NOFOLLOW at open), a FIFO, or a special file. Common during repair when an install-state points at a destination that has since become a directory.","commonSituations":"A destination recorded in the install-state file was replaced by a directory or a symlink; repair then tries to read it as a file. A previous failed install left a directory where a JSON file was expected (e.g. ~/.claude/install-state.json is a directory).","solutions":["Identify the path from the error message and run `ls -la <path>` to confirm its type.","If it is a directory or symlink, remove or rename it: `rm -rf <path>` or `mv <path> <path>.bak`.","Re-run the install/repair operation so a real file is written.","Audit how the non-file entry was created (failed prior install, manual mkdir, sync tool) to prevent recurrence."],"exampleFix":"// before: ~/.claude/install-state.json is a directory\n// after:\nrm -rf ~/.claude/install-state.json\n./install.sh --target claude","handlingStrategy":"validation","validationCode":"const fs = require('fs');\nfunction assertRegularFile(filePath) {\n  const st = fs.lstatSync(filePath);\n  if (st.isSymbolicLink()) throw new Error('path is a symlink');\n  if (!st.isFile()) throw new Error('path is not a regular file');\n}\n// before reading via the library helpers\nassertRegularFile(stateFilePath);","typeGuard":"function isReadableRegularFile(filePath) {\n  try {\n    return fs.lstatSync(filePath).isFile() && !fs.lstatSync(filePath).isSymbolicLink();\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  content = readJsonNoFollow(path);\n} catch (err) {\n  if (err.message.startsWith('Refusing to read non-file path')) {\n    // remove or rename the offending entry, then regenerate state\n  } else throw err;\n}","preventionTips":["Treat install-state and recorded destinations as read-only after install.","Do not replace installed files with directories or symlinks manually.","Run a sanity `ls -la` on the install root before invoking repair/uninstall.","Keep backups of install-state so a corrupted entry can be restored."],"tags":["security","filesystem","symlink","install","validation"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}