{"record":{"id":"b0550c2739d9f21b","repo":"Yeachan-Heo/oh-my-codex","slug":"artifact-path-must-not-traverse-directories","errorCode":null,"errorMessage":"artifact path must not traverse directories","messagePattern":"artifact path must not traverse directories","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/mcp/hermes-bridge.ts","lineNumber":505,"sourceCode":"      stdio: \"ignore\",\n      env: { ...bridgeEnv, OMX_HERMES_MCP_BRIDGE: \"1\" },\n    }) as ChildProcess;\n    child.unref();\n    if (!child.pid) return failure(\"command_failed\", \"OMX session launcher did not report a pid\");\n    return jsonResult({ pid: child.pid, command, args: launchArgs, workingDirectory: cwd });\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    if (message.includes(\"allow_mutation\")) return failure(\"mutation_not_allowed\", message);\n    return failure(\"invalid_input\", message);\n  }\n}\n\nfunction normalizeArtifactRelativePath(pathValue: unknown): string {\n  const raw = normalizeString(pathValue, \"path\", { required: true })!;\n  if (isAbsolute(raw)) throw new Error(\"artifact path must be relative\");\n  const normalized = raw.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n  if (normalized.includes(\"../\") || normalized === \"..\" || normalized.includes(\"\\0\")) {\n    throw new Error(\"artifact path must not traverse directories\");\n  }\n  if (!SAFE_ARTIFACT_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {\n    throw new Error(`artifact path must be under ${SAFE_ARTIFACT_PREFIXES.join(\", \")}`);\n  }\n  return normalized;\n}\n\nfunction isInsideDirectory(parent: string, candidate: string): boolean {\n  const rel = relative(parent, candidate);\n  return rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n\nasync function resolveSafeArtifactPath(cwd: string, rel: string): Promise<string> {\n  const cwdRealPath = await realpath(cwd);\n  const full = resolve(cwd, rel);\n  const relativeToCwd = relative(resolve(cwd), full);\n  if (relativeToCwd.startsWith(\"..\") || isAbsolute(relativeToCwd)) {\n    throw new Error(\"artifact resolved outside working directory\");","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/mcp/hermes-bridge.ts#L487-L523","documentation":"Thrown when a relative artifact path contains ../ traversal (or is exactly \"..\" or contains NUL bytes) after backslash-to-slash normalization. This is a path-traversal guard: the bridge must never read files outside the working directory via crafted relative paths.","triggerScenarios":"path=\"../../etc/passwd\", path=\"logs/../../../../secrets\", path=\"..\", or a path containing \\0. Also triggers for Windows-style backslash traversal like \"..\\\\..\\\\etc\" because backslashes are normalized to slashes first.","commonSituations":"Attempting to fetch files outside the artifact directory; malicious or buggy client input; tests probing traversal behavior.","solutions":["Remove any ../ segments and pass a path directly under an allowed prefix","If you need a file outside the working directory, copy it into the working directory first","Validate/escape user-supplied filenames before forwarding them as artifact paths"],"exampleFix":"// before\nawait tool({ path: \"logs/../../config.env\" });\n// after\nawait tool({ path: \"logs/run.log\" });","handlingStrategy":"validation","validationCode":"if (/(^|\\/)\\.\\.(\\/|$)/.test(p) || p.includes('\\0')) throw new TypeError('unsafe artifact path');","typeGuard":"function isSafeRelativePath(p: string): boolean { const n = p.replace(/\\\\/g, '/'); return !(n.split('/').includes('..') || n.includes('\\0')); }","tryCatchPattern":null,"preventionTips":["Build paths with path.posix.join and pre-normalized segments","Never concatenate raw user input into artifact paths"],"tags":["path-traversal","security","mcp","artifact"],"backgroundTag":"path-traversal-blocked","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}