Yeachan-Heo/oh-my-codex · error · Error
[native-assets] cache path escapes configured root
Error message
[native-assets] cache path escapes configured root
What it means
A caller-supplied cache path resolved outside the configured native cache root. The library canonicalizes the cache root (realpath) and requires every managed path to be a descendant; if the path is outside even after resolving symlinks/canonicalization, publication/inspection is refused to prevent writes outside the cache.
Source
Thrown at src/cli/native-assets.ts:364
function lockRecord(token: string, binaryPath: string): string {
return `${JSON.stringify({
version: 1,
token,
pid: process.pid,
hostname: hostname(),
started_at: new Date().toISOString(),
binary_path: binaryPath,
})}\n`;
}
function canonicalDescendantPath(path: string, configuredRoot: string, canonicalRoot: string): string {
const resolvedPath = resolve(path);
const canonicalRelative = relative(canonicalRoot, resolvedPath);
if (canonicalRelative && canonicalRelative !== '..' && !canonicalRelative.startsWith(`..${sep}`)) return resolvedPath;
const rel = relative(resolve(configuredRoot), resolvedPath);
if (!rel || rel === '..' || rel.startsWith(`..${sep}`)) throw new Error('[native-assets] cache path escapes configured root');
return join(canonicalRoot, rel);
}
async function canonicalCacheRoot(root: string, create: boolean): Promise<string | undefined> {
try {
if (create) await mkdir(root, { recursive: true, mode: 0o700 });
const entry = await lstat(root);
if (!entry.isDirectory() && !entry.isSymbolicLink()) throw new Error('unsafe root');
return await realpath(root); // The configured root itself is intentionally allowed to be a symlink.
} catch (error) {
if (!create && absent(error)) return undefined;
throw error;
}
}
async function validateDescendant(path: string, canonicalRoot: string, createParents: boolean): Promise<void> {
const candidate = resolve(path);
const rel = relative(canonicalRoot, candidate);View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Pass destinations derived from the cache root the library itself resolves, not absolute hardcoded paths.
- Remove symlinks inside the cache directory so realpath stays within the root.
- Align the cache-root env override with the paths you pass in.
Defensive patterns
Strategy: validation
Validate before calling
import { resolve, relative } from 'node:path';
function isInside(root: string, p: string): boolean {
const rel = relative(resolve(root), resolve(p));
return rel !== '' && rel !== '..' && !rel.startsWith(`..${require('node:path').sep}`);
} Try / catch
try { await inspectManagedNativeBinary(dest); } catch (e) { if (/cache path escapes configured root/.test(String(e))) { /* derive dest from the library's cache root */ } throw e; } Prevention
- Never hardcode absolute cache paths
- Derive destinations from the cache root the library resolves
- Keep symlinks out of the cache directory
When it happens
Trigger: inspectManagedNativeBinary or publishManagedNativeBinary with a destination that, once resolved, is not under the canonicalized cache root — e.g. a symlink inside the cache pointing elsewhere, or a custom cache-root env var (NATIVE_CACHE_ROOT-style override) that doesn't contain the destination path.
Common situations: Users overriding the cache root env var but passing hardcoded paths; symlinks inside the cache directory (dotfiles managers like Stow, tmpfs symlinks); CI caches that replace directories with links.
Related errors
- [native-assets] cache descendant is unsafe: ${current}
- [native-assets] unsafe publication lock
- [native-assets] unable to create cache root
- [native-assets] cache publication verification failed: ${fin
- [native-assets] cache publication verification failed for ${
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/64cc611b1f3407e8.
Report an issue: GitHub.