{"record":{"id":"d2f60ae6e8e92139","repo":"Yeachan-Heo/oh-my-codex","slug":"session-directory-escapes-the-authorized-state-dir","errorCode":null,"errorMessage":"session directory escapes the authorized state directory","messagePattern":"session directory escapes the authorized state directory","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/cli/index.ts","lineNumber":8157,"sourceCode":"    if (\n      canonicalizePathForRunDirMatch(record.source_cwd) !== canonicalCwd\n      && (!record.worktree_cwd || canonicalizePathForRunDirMatch(record.worktree_cwd) !== canonicalCwd)\n    ) continue;\n\n    try {\n      const canonicalRunDir = realpathSync(resolve(record.run_dir));\n      if (!isCanonicalPathWithin(canonicalRunsRoot, canonicalRunDir)) {\n        throw new Error(\"run directory escapes the authorized runs root\");\n      }\n      const stateDir = realpathSync(join(canonicalRunDir, \".omx\", \"state\"));\n      if (!isCanonicalPathWithin(canonicalRunDir, stateDir)) {\n        throw new Error(\"state directory escapes the authorized run directory\");\n      }\n      const session = JSON.parse(await readFile(join(stateDir, \"session.json\"), \"utf-8\")) as Record<string, unknown>;\n      if (session.session_id !== record.session_id) throw new Error(\"run session pointer changed\");\n      const sessionDir = realpathSync(join(stateDir, \"sessions\", record.session_id));\n      if (!isCanonicalPathWithin(stateDir, sessionDir)) {\n        throw new Error(\"session directory escapes the authorized state directory\");\n      }\n      candidates.push({ sessionDir, sessionId: record.session_id, record });\n    } catch (err) {\n      throw new Error(`Refusing cancellation because detached run authority is invalid: ${record.run_dir}.`, { cause: err });\n    }\n  }\n\n  if (candidates.length > 1) throw new Error(\"Refusing cancellation because multiple detached run authorities match.\");\n  if (candidates.length === 0) return null;\n  const [{ sessionDir, sessionId, record }] = candidates;\n  const refs: ModeStateFileRef[] = [];\n  const stateFiles = await readdir(sessionDir).catch(() => [] as string[]);\n  for (const file of stateFiles) {\n    if (!isModeStateFilename(file)) continue;\n    const path = join(sessionDir, file);\n    try {\n      const fileStat = lstatSync(path);\n      if (!fileStat.isFile() || fileStat.isSymbolicLink()) {","sourceCodeStart":8139,"sourceCodeEnd":8175,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/index.ts#L8139-L8175","documentation":"The per-session directory realpath(stateDir/sessions/<session_id>) resolves outside the authorized state dir (symlinked session id component), so it is rejected before any state file is touched.","triggerScenarios":"stateDir/sessions/<session_id> is a symlink pointing elsewhere (e.g. to shared state across projects), escaping stateDir after canonicalization.","commonSituations":"Manually symlinking session dirs to deduplicate state, or tampered/corrupted session directories.","solutions":["Remove the symlink under .omx/state/sessions so the session dir is a real child of state dir","Recreate the session state by re-launching if data was only linked, not moved","Never symlink inside .omx/state/sessions"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const sessionDir = realpathSync(join(stateDir, 'sessions', id));\nif (!sessionDir.startsWith(realpathSync(stateDir) + sep)) throw new Error('session dir must live inside state dir');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never symlink entries under sessions/","Move state instead of linking it","Validate session dir layout after restoring backups"],"tags":["path-traversal","security","symlink"],"backgroundTag":"path-escapes-allowed-root","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}