{"record":{"id":"ab549c444da0718c","repo":"affaan-m/ECC","slug":"refusing-to-reuse-unsafe-legacy-codex-ownership-pa","errorCode":null,"errorMessage":"Refusing to reuse unsafe legacy Codex ownership path: ${filePath}","messagePattern":"Refusing to reuse unsafe legacy Codex ownership path: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/codex-legacy-sync.js","lineNumber":208,"sourceCode":"\nfunction snapshotLegacyPath(filePath) {\n  const snapshot = readRegularFileNoFollow(filePath);\n  const previousType = snapshot ? 'file' : 'missing';\n  return {\n    path: filePath,\n    installedSha256: null,\n    previousType,\n    previousContentBase64: snapshot ? snapshot.content.toString('base64') : null,\n    previousMode: snapshot ? snapshot.mode : null,\n  };\n}\n\nfunction assertInstalledStateUnmodified(state) {\n  for (const entry of state.paths) {\n    const filePath = path.resolve(entry.path);\n    const trustedRoot = getTrustedRoot(state, filePath);\n    if (!trustedRoot || hasUnsafeManagedAncestor(filePath, trustedRoot)) {\n      throw new Error(`Refusing to reuse unsafe legacy Codex ownership path: ${filePath}`);\n    }\n    const snapshot = readRegularFileNoFollow(filePath);\n    if (!entry.installedSha256) {\n      if (snapshot) throw new Error(`Refusing to replace modified legacy Codex artifact: ${filePath}`);\n      continue;\n    }\n    const digest = snapshot\n      ? crypto.createHash('sha256').update(snapshot.content).digest('hex')\n      : null;\n    if (digest !== entry.installedSha256) {\n      throw new Error(`Refusing to replace modified legacy Codex artifact: ${filePath}`);\n    }\n  }\n}\n\nfunction beginLegacySyncState(options) {\n  const codexHome = path.resolve(options.codexHome);\n  const statePath = getStatePath(codexHome);","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/affaan-m/ECC/blob/06c5e118c4d3e6c3b7f9445f973a2194c82de193/scripts/lib/codex-legacy-sync.js#L190-L226","documentation":"During reinstall, assertInstalledStateUnmodified() replays every recorded path. For each entry it resolves a trusted root (codexHome, recorded trustedRoots, or the installedHooksPath) and refuses the entry when the file no longer sits under any trusted root, or when a directory between the root and the file is a symlink (hasUnsafeManagedAncestor walks each segment). This is a deliberate guard: recorded ownership must never be reused through a path that escaped the managed tree.","triggerScenarios":"Re-running the legacy Codex sync when a state entry references an absolute path outside the current trusted roots (CODEX_HOME moved/renamed after install, state file copied from another machine), or when an ancestor directory inside the trusted root was replaced by a symlink.","commonSituations":"Renaming or moving ~/.codex after the first install; dotfile managers replacing config subdirectories with symlinks; state created under a different user or container path.","solutions":["Open the state file and list each entry.path; identify which no longer live under the current codexHome / trustedRoots / hooks path","Restore the original layout (same absolute codexHome, no symlinked ancestors) and re-run","If the entries are stale, back up and delete the state file so the next sync rebuilds ownership from scratch","Replace any symlinked ancestor directory with a real directory before re-running"],"exampleFix":"// before: ~/.codex/hooks is a symlink -> Refusing to reuse unsafe legacy Codex ownership path\nfs.rmSync(path.join(codexHome, 'hooks')); // removes the symlink only\nfs.mkdirSync(path.join(codexHome, 'hooks'), { recursive: true });\n// after: re-run the legacy Codex sync","handlingStrategy":"validation","validationCode":"const path = require('path'), fs = require('fs');\nfunction entriesAreWithinRoots(entries, roots) {\n  const resolved = roots.map(r => path.resolve(r));\n  return entries.every(e => {\n    const p = path.resolve(e.path);\n    return resolved.some(r => p === r || p.startsWith(r + path.sep));\n  });\n}\nfunction hasSymlinkedAncestor(filePath, root) {\n  const segs = path.relative(root, filePath).split(path.sep).slice(0, -1);\n  let cur = root;\n  for (const seg of segs) {\n    cur = path.join(cur, seg);\n    if (fs.lstatSync(cur).isSymbolicLink()) return true;\n  }\n  return false;\n}","typeGuard":null,"tryCatchPattern":"try { beginLegacySyncState({ codexHome }); }\ncatch (err) {\n  if (/unsafe legacy Codex ownership path/.test(err.message)) {\n    // print the offending entry.path and the trusted roots so the operator fixes the layout\n  } else throw err;\n}","preventionTips":["Keep CODEX_HOME at a stable absolute path","Avoid symlinking directories inside the Codex home","Do not copy sync-state files between machines"],"tags":["codex","path-validation","symlink","security"],"backgroundTag":"path-traversal-guard","analyzedSha":"06c5e118c4d3e6c3b7f9445f973a2194c82de193","analyzedAt":"2026-08-18T11:27:13.915Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}