Yeachan-Heo/oh-my-codex · error · Error
Refusing to back up ${artifact.path} outside controlled back
Error message
Refusing to back up ${artifact.path} outside controlled backup root. What it means
Before writing a transaction backup, setup computes the backup path relative to the controlled backup root and rejects any path that escapes it (absolute relative path, '..', or '..' prefix). This is a path-traversal guard ensuring backups never land outside the managed backup root.
Source
Thrown at src/cli/setup.ts:1974
}
async function ensureSnapshotBackup(
artifact: NativeHookTransactionArtifact,
backupContext: SetupBackupContext,
options: Pick<SetupOptions, "dryRun" | "verbose">,
tracker: RegularFileDurabilityTracker,
): Promise<boolean> {
const bytes = artifact.before.bytes;
if (bytes === null) return false;
const backupPath = nativeHookTransactionBackupPath(artifact.path, backupContext);
if (!options.dryRun) {
const relativeParent = relative(backupContext.baseRoot, dirname(backupPath));
if (
isAbsolute(relativeParent) ||
relativeParent === ".." ||
relativeParent.startsWith(`..${sep}`)
) {
throw new Error(`Refusing to back up ${artifact.path} outside controlled backup root.`);
}
let currentPath = backupContext.baseRoot;
for (const component of relativeParent.split(sep).filter(Boolean)) {
currentPath = join(currentPath, component);
try {
const currentStat = await lstat(currentPath);
if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
throw new Error(`Refusing to use unsafe backup ancestor ${currentPath}.`);
}
} catch (error) {
if (!isMissingPathError(error)) throw error;
await mkdir(currentPath);
const createdStat = await lstat(currentPath);
if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
throw new Error(`Refusing to use unsafe created backup ancestor ${currentPath}.`);
}
}
}View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Sanitize artifact.path: strip .., absolute prefixes, and symlinked segments before constructing the backup path
- Verify backupContext.baseRoot is the intended controlled root
- Log artifact.path and the computed backupPath to spot the escaping segment
- Treat this as a security signal: audit where artifact.path originates
Example fix
// before const backupPath = join(backupContext.baseRoot, artifact.path); // after const safeRel = artifact.path.split(/[\\/]/).filter((p) => p && p !== "." && p !== "..").join(sep); const backupPath = join(backupContext.baseRoot, safeRel);
Defensive patterns
Strategy: validation
Validate before calling
const isSafeRelative = (p: string) =>
!p.includes("..") && !isAbsolute(p) && p.split(/[\\/]/).every((s) => s && s !== ".");
if (!isSafeRelative(artifact.path)) throw new Error(`unsafe artifact path: ${artifact.path}`); Type guard
const isSafeArtifactPath = (p: string): p is `${string}.${string}` =>
typeof p === "string" && !isAbsolute(p) && !p.split(/[\\/]/).includes(".."); Try / catch
try { await createBackup(artifact); } catch (e) { if (e instanceof Error && e.message.startsWith("Refusing to back up")) { /* sanitize artifact.path and retry */ } else throw e; } Prevention
- Never build artifact paths from raw user input
- Normalize and reject '..'/absolute segments before joining
- Audit path sources as a security hygiene step
When it happens
Trigger: An artifact path containing traversal segments (../) or an absolute path that, when joined under backupContext.baseRoot, resolves outside it.
Common situations: Malicious or malformed artifact paths, tampered backup context, or bugs constructing backupPath from user-supplied hook file names.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Refusing cancellation outside authorized state root: ${ref.p
- Refusing to use unsafe backup ancestor ${currentPath}.
- run directory escapes the authorized runs root
- state directory escapes the authorized run directory
- session directory escapes the authorized state directory
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/5bbbc82a419e6e5e.
Report an issue: GitHub.