{"record":{"id":"23a34389ad3fb108","repo":"Yeachan-Heo/oh-my-codex","slug":"run-directory-escapes-the-authorized-runs-root","errorCode":null,"errorMessage":"run directory escapes the authorized runs root","messagePattern":"run directory escapes the authorized runs root","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/cli/index.ts","lineNumber":8147,"sourceCode":"\n  const candidates: Array<{ sessionDir: string; sessionId: string; record: MadmaxDetachedActiveRecord }> = [];\n\n  const files = await readdir(activeDir).catch(() => [] as string[]);\n  for (const file of files) {\n    if (!file.endsWith(\".json\")) continue;\n    const record = readMadmaxDetachedActiveRecord(join(activeDir, file));\n    if (!record || file !== `${record.context_key}.json`) continue;\n    if (!record.session_id || normalizeSessionId(record.session_id) !== record.session_id) continue;\n    if (!hasMatchingMadmaxDetachedRuntimeBinding(record)) continue;\n    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.\");","sourceCodeStart":8129,"sourceCodeEnd":8165,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/index.ts#L8129-L8165","documentation":"While collecting detached run authorities for cancellation, realpath(record.run_dir) resolves outside the authorized runs root, so the record is considered untrusted and refused.","triggerScenarios":"record.run_dir is a symlink whose real path is outside canonicalRunsRoot, or OMX_ROOT/runs root was moved/changed since the record was written (relative run_dir resolving elsewhere).","commonSituations":"The project or runs root was relocated, a symlinked workspace, or a hand-edited active-record JSON pointing run_dir at an arbitrary path.","solutions":["Correct record.run_dir in the detached active record to point inside the current runs root","Remove the stale active record and re-register/re-launch the session","Ensure OMX_ROOT is set consistently between launch and cancel"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const real = realpathSync(resolve(record.run_dir));\nif (!real.startsWith(realpathSync(runsRoot) + sep)) throw new Error('run_dir outside runs root — fix record');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set OMX_ROOT identically at launch and cancel time","Never symlink run dirs outside the runs root","Clean active records when relocating projects"],"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"}