{"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/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/codex-legacy-sync.js#L190-L226","documentation":"assertInstalledStateUnmodified re-verifies every path recorded in the sync state before the installer reuses them. For each entry it resolves a trusted root and rejects the whole operation if the path has no trusted root or if any managed ancestor directory is unsafe (hasUnsafeManagedAncestor, e.g. a symlinked ancestor). This prevents reusing ownership of paths that could be redirected through symlinks to write outside the trusted root — a path-traversal/symlink-swap defense.","triggerScenarios":"beginLegacySyncState -> assertInstalledStateUnmodified over a state whose entry.path resolves outside any trusted root (getTrustedRoot returns null), or whose resolved path has an unsafe (e.g. symlinked) managed ancestor directory relative to that root.","commonSituations":"A directory in the path (e.g. ~/.codex or a subdirectory) was replaced with a symlink; the state file was moved/copied between machines so recorded absolute paths no longer sit under a trusted root; project relocated and paths recorded with a different prefix; attacker tampering with managed directories.","solutions":["Find the symlink in the recorded path (`ls -la` on each ancestor) and replace it with a real directory.","Delete the stale state file and rerun install so paths are re-registered against the current, trusted layout.","Ensure recorded entry.path values live under an accepted trusted root (correct codexHome); regenerate state if the install location moved.","If you believe the path is genuinely safe, verify each ancestor with a symlink audit and only then recreate the state — do not bypass the check."],"exampleFix":"// before: ~/.codex/prompts is a symlink to /tmp/prompts\nmv /tmp/prompts ~/.codex/prompts.real\nrm ~/.codex/prompts && mv ~/.codex/prompts.real ~/.codex/prompts\n// after: real directory, no symlinked ancestors; rerun install","handlingStrategy":"validation","validationCode":"import fs from 'node:fs';\nimport path from 'node:path';\nfunction hasSymlinkAncestor(filePath) {\n  let dir = path.dirname(path.resolve(filePath));\n  while (true) {\n    if (fs.lstatSync(dir).isSymbolicLink()) return true;\n    const parent = path.dirname(dir);\n    if (parent === dir) return false;\n    dir = parent;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  beginLegacySyncState(options);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Refusing to reuse unsafe legacy Codex ownership path')) {\n    const p = err.message.split('path: ')[1];\n    console.error(`Fix symlinked/tampered ancestors of ${p}, then rerun install`);\n    process.exitCode = 1;\n  } else {\n    throw err;\n  }\n}","preventionTips":["Keep codexHome and its subdirectories as real directories, never symlinks.","Do not relocate the install directory after recording state; reinstall instead of moving files.","Audit recorded state paths with `ls -la` for symlinked ancestors before updates.","Never copy a state file between machines or users; each environment should generate its own."],"tags":["security","symlink","path-traversal","codex","installer"],"backgroundTag":"path-traversal-blocked","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"}