{"record":{"id":"8d02f44addcd5ad9","repo":"Yeachan-Heo/oh-my-codex","slug":"refusing-to-use-unsafe-created-backup-ancestor-c","errorCode":null,"errorMessage":"Refusing to use unsafe created backup ancestor ${currentPath}.","messagePattern":"Refusing to use unsafe created backup ancestor (.+?)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cli/setup.ts","lineNumber":1989,"sourceCode":"\t\t\trelativeParent === \"..\" ||\n\t\t\trelativeParent.startsWith(`..${sep}`)\n\t\t) {\n\t\t\tthrow new Error(`Refusing to back up ${artifact.path} outside controlled backup root.`);\n\t\t}\n\t\tlet currentPath = backupContext.baseRoot;\n\t\tfor (const component of relativeParent.split(sep).filter(Boolean)) {\n\t\t\tcurrentPath = join(currentPath, component);\n\t\t\ttry {\n\t\t\t\tconst currentStat = await lstat(currentPath);\n\t\t\t\tif (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {\n\t\t\t\t\tthrow new Error(`Refusing to use unsafe backup ancestor ${currentPath}.`);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tif (!isMissingPathError(error)) throw error;\n\t\t\t\tawait mkdir(currentPath);\n\t\t\t\tconst createdStat = await lstat(currentPath);\n\t\t\t\tif (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {\n\t\t\t\t\tthrow new Error(`Refusing to use unsafe created backup ancestor ${currentPath}.`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst handle = await open(backupPath, \"wx\", 0o600);\n\t\ttry {\n\t\t\tawait handle.writeFile(bytes);\n\t\t\trecordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t\tconst backupStat = await lstat(backupPath);\n\t\tif (backupStat.isSymbolicLink() || !backupStat.isFile() || backupStat.nlink !== 1) {\n\t\t\tthrow new Error(`Refusing unsafe native hook transaction backup ${backupPath}.`);\n\t\t}\n\t\tconst writtenBytes = await readFile(backupPath);\n\t\tif (!writtenBytes.equals(bytes)) {\n\t\t\tthrow new Error(`Native hook transaction backup verification failed for ${backupPath}.`);\n\t\t}","sourceCodeStart":1971,"sourceCodeEnd":2007,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/setup.ts#L1971-L2007","documentation":"When an ancestor directory of the backup path does not exist, setup mkdirs it and then lstats the freshly created directory. If the created entry is a symlink or not a directory (possible via races or pre-existing entries appearing between checks), the write is aborted.","triggerScenarios":"mkdir(currentPath) succeeds but a concurrent process swaps the new directory for a symlink before lstat, or the entry already exists as a non-directory on platforms where mkdir is not exclusive.","commonSituations":"Race conditions with parallel setup runs or symlink-planting scenarios; rarely hit outside adversarial or heavily concurrent environments.","solutions":["Ensure no other process mutates the backup root during setup","Remove the offending entry at currentPath and retry","Serialize setup runs with a lock","Run setup with exclusive access to the backup tree (single-user, single-process)"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import { lstat } from \"node:fs/promises\";\nconst st = await lstat(currentPath);\nif (st.isSymbolicLink() || !st.isDirectory()) throw new Error(`unsafe created ancestor: ${currentPath}`);","typeGuard":null,"tryCatchPattern":"try { await createBackup(artifact); } catch (e) { if (e instanceof Error && e.message.includes(\"unsafe created backup ancestor\")) { await rm(currentPath); await retry(createBackup, artifact); } else throw e; }","preventionTips":["Serialize setup runs to avoid creation races","Remove pre-existing junk entries before running","Use a dedicated, exclusively-owned backup root"],"tags":["symlink","race-condition","security","backup"],"backgroundTag":"symlink-escape-blocked","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}