Yeachan-Heo/oh-my-codex · error · Error
artifact path must be under ${SAFE_ARTIFACT_PREFIXES.join(",
Error message
artifact path must be under ${SAFE_ARTIFACT_PREFIXES.join(", ")} What it means
Thrown when a (valid, relative, non-traversing) artifact path does not start with any of the SAFE_ARTIFACT_PREFIXES whitelisted directory prefixes. Only artifacts stored under these known roots can be resolved, keeping file access predictable.
Source
Thrown at src/mcp/hermes-bridge.ts:508
child.unref();
if (!child.pid) return failure("command_failed", "OMX session launcher did not report a pid");
return jsonResult({ pid: child.pid, command, args: launchArgs, workingDirectory: cwd });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
if (message.includes("allow_mutation")) return failure("mutation_not_allowed", message);
return failure("invalid_input", message);
}
}
function normalizeArtifactRelativePath(pathValue: unknown): string {
const raw = normalizeString(pathValue, "path", { required: true })!;
if (isAbsolute(raw)) throw new Error("artifact path must be relative");
const normalized = raw.replace(/\\/g, "/").replace(/^\.\//, "");
if (normalized.includes("../") || normalized === ".." || normalized.includes("\0")) {
throw new Error("artifact path must not traverse directories");
}
if (!SAFE_ARTIFACT_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
throw new Error(`artifact path must be under ${SAFE_ARTIFACT_PREFIXES.join(", ")}`);
}
return normalized;
}
function isInsideDirectory(parent: string, candidate: string): boolean {
const rel = relative(parent, candidate);
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
}
async function resolveSafeArtifactPath(cwd: string, rel: string): Promise<string> {
const cwdRealPath = await realpath(cwd);
const full = resolve(cwd, rel);
const relativeToCwd = relative(resolve(cwd), full);
if (relativeToCwd.startsWith("..") || isAbsolute(relativeToCwd)) {
throw new Error("artifact resolved outside working directory");
}
let artifactRealPath: string;View on GitHub (pinned to 3ad79a8a6f)
Solutions
- Move/link the artifact under one of the allowed prefixes (e.g. artifacts/)
- Check SAFE_ARTIFACT_PREFIXES in src/mcp/hermes-bridge.ts and align your output directories
- If a new prefix is legitimately needed, extend SAFE_ARTIFACT_PREFIXES and rebuild
Example fix
// before
await tool({ path: "out/report.md" });
// after
await tool({ path: "artifacts/report.md" }); Defensive patterns
Strategy: validation
Validate before calling
const SAFE = ['artifacts/', 'logs/']; // mirror SAFE_ARTIFACT_PREFIXES
if (!SAFE.some(pre => p.startsWith(pre))) throw new RangeError(`path must start with ${SAFE.join(', ')}`); Type guard
function isAllowedArtifactPath(p: string, prefixes: string[]): p is string { return prefixes.some(x => p.startsWith(x)); } Prevention
- Write build outputs directly under the allowed prefixes
- Check the prefix list in src/mcp/hermes-bridge.ts when upgrading
When it happens
Trigger: path="docs/readme.md" when allowed prefixes are e.g. ["artifacts/", "logs/"]; path="./dist/bundle.js" after the leading ./ is stripped fails the prefix check.
Common situations: Assuming any file in the working directory is fetchable; version changes that alter the allowed prefix list; artifacts written to a new directory not yet whitelisted.
Related errors
- artifact path must be relative
- artifact path must not traverse directories
- artifact resolved outside working directory
- artifact_missing
- mode must not contain path separators
AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27).
Data as JSON: /api/errors/62feb0e60d38dfbc.
Report an issue: GitHub.