{"record":{"id":"e6ae1db4bc7c7e5e","repo":"santifer/career-ops","slug":"refusing-to-hash-non-regular-file-childrel","errorCode":null,"errorMessage":"refusing to hash non-regular file: ${childRel}","messagePattern":"refusing to hash non-regular file: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/_lock.mjs","lineNumber":52,"sourceCode":" * @param {string} dir absolute plugin directory\n * @returns {{ files: Record<string,string>, integrity: string }}\n */\nexport function hashPluginTree(dir) {\n  const files = {};\n  const walk = (abs, rel) => {\n    let entries;\n    try { entries = readdirSync(abs, { withFileTypes: true }); }\n    catch (err) { throw new Error(`cannot read ${rel || '.'}: ${err.message}`); }\n    for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n      if (e.name === 'node_modules' || e.name === '.git') continue;\n      const childAbs = path.join(abs, e.name);\n      const childRel = rel ? `${rel}/${e.name}` : e.name;\n      // lstat (not stat) so a symlink is detected, never followed.\n      const st = lstatSync(childAbs);\n      if (st.isSymbolicLink()) throw new Error(`refusing to hash symlink: ${childRel}`);\n      if (st.isDirectory()) walk(childAbs, childRel);\n      else if (st.isFile()) files[childRel] = sha256(readFileSync(childAbs));\n      else throw new Error(`refusing to hash non-regular file: ${childRel}`);\n    }\n  };\n  walk(dir, '');\n  // Aggregate integrity = sha256 over the deterministic sorted \"rel:hash\" join.\n  const aggregate = Object.keys(files).sort().map(k => `${k}:${files[k]}`).join('\\n');\n  return { files, integrity: sha256(Buffer.from(aggregate)) };\n}\n\n/** Read plugins.lock (fail-open to an empty lock — like the rest of the engine). */\nexport function readLock(root) {\n  const file = lockPath(root);\n  if (!existsSync(file)) return { lockfileVersion: LOCK_VERSION, plugins: {} };\n  try {\n    const parsed = JSON.parse(readFileSync(file, 'utf8'));\n    if (!parsed || typeof parsed !== 'object' || typeof parsed.plugins !== 'object') return { lockfileVersion: LOCK_VERSION, plugins: {} };\n    return parsed;\n  } catch {\n    return { lockfileVersion: LOCK_VERSION, plugins: {} };","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/plugins/_lock.mjs#L34-L70","documentation":"Thrown by `hashPluginTree` (plugins/_lock.mjs:52) when `lstatSync` reports a file that is neither a regular file nor a directory nor a symlink — i.e. a special file type such as a FIFO, socket, character/block device. The integrity walker can only meaningfully hash regular files, so an unknown file type aborts rather than silently skipping it (a skip would leave an un-hashed entry exploitable by a rug-pull).","triggerScenarios":"A plugin directory contains a named pipe, Unix domain socket, or device file. The walk hits it, `isFile()` and `isDirectory()` both return false, and the guard throws naming the relative path.","commonSituations":"A leftover FIFO/socket from a crashed dev tool or editor; a plugin dir accidentally placed in /tmp or /var where a daemon created a socket; someone ran `mkfifo` inside a plugin dir for testing; an accidental device-node copy on Linux.","solutions":["Locate the special file: `find plugins/<name> -type p -o -type s -o -type b -o -type c`.","Remove the offending file if it is not part of the plugin.","If the plugin legitimately needs a runtime socket, ensure it is created at runtime in a writable dir (not bundled in the plugin tree that gets hashed)."],"exampleFix":"# before\nmkfifo plugins/myplugin/job-queue   # accidentally left behind\n# after\nfind plugins -type p -delete   # remove all FIFOs from the plugin tree","handlingStrategy":"validation","validationCode":"import { lstatSync, readdirSync } from 'node:fs';\nimport path from 'node:path';\n// Detect non-regular files before hashing.\nfunction findSpecialFiles(root) {\n  const bad = [];\n  const walk = (dir) => {\n    for (const e of readdirSync(dir, { withFileTypes: true })) {\n      if (e.name === 'node_modules' || e.name === '.git') continue;\n      const p = path.join(dir, e.name);\n      const st = lstatSync(p);\n      if (st.isDirectory()) walk(p);\n      else if (!st.isFile()) bad.push(p);\n    }\n  };\n  walk(root);\n  return bad;\n}\nconst special = findSpecialFiles(pluginDir);\nif (special.length) throw new Error(`Remove special files: ${special.join(', ')}`);","typeGuard":null,"tryCatchPattern":"try {\n  hashPluginTree(pluginDir);\n} catch (err) {\n  if (/non-regular file/.test(err.message)) {\n    console.error(`Clean the plugin dir: ${err.message}`);\n  } else throw err;\n}","preventionTips":["Don't create FIFOs/sockets inside plugin dirs.","Run `find plugins -type p -o -type s -o -type b -o -type c` in CI."],"tags":["plugin","lock","integrity","filesystem","special-files"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}