{"record":{"id":"74b76e8d5e8eda11","repo":"yamadashy/repomix","slug":"path-input-resolves-outside-workspace-root","errorCode":null,"errorMessage":"Path \"${input}\" resolves outside workspace root.","messagePattern":"Path \"(.+?)\" resolves outside workspace root\\.","errorType":"validation","errorClass":"PathScopeError","httpStatus":null,"severity":"error","filePath":"src/mcp/pathScope.ts","lineNumber":73,"sourceCode":"): Promise<string> => {\n  const rootResolved = path.resolve(root);\n\n  if (input === '' || input === '.') {\n    return rootResolved;\n  }\n\n  // Reject anything that escapes the workspace root — absolute/drive/UNC, a \"~\"\n  // home ref, or any \"..\" segment. A redundant leading \"./\" or \".\\\" is allowed.\n  if (isEscapingPath(input)) {\n    throw new PathScopeError(\n      `Path \"${input}\" must be relative to workspace root — no \"/\", \"~/\", \"../\", drive, or \"..\" segment. Use e.g. \"src/index.ts\" or \".\" for whole workspace.`,\n    );\n  }\n\n  const candidate = path.resolve(rootResolved, input);\n\n  if (!isInside(rootResolved, candidate)) {\n    throw new PathScopeError(`Path \"${input}\" resolves outside workspace root.`);\n  }\n\n  // Resolve symlinks to catch a link inside root that points back out. If a path\n  // can't be resolved via realpath (e.g. the target does not exist yet, or the\n  // root itself can't be stat'd), fall back to the lexical path — it is already\n  // confined lexically above.\n  let realRoot: string;\n  try {\n    realRoot = (await deps.realpath(rootResolved)) as string;\n  } catch {\n    realRoot = rootResolved;\n  }\n  let realCandidate: string;\n  try {\n    realCandidate = (await deps.realpath(candidate)) as string;\n  } catch {\n    return candidate;\n  }","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/yamadashy/repomix/blob/f465ad909315a22120636baf03fa5e28701a50cb/src/mcp/pathScope.ts#L55-L91","documentation":"After lexical checks pass, resolveWithinRoot resolves the candidate with path.resolve and verifies it remains inside the workspace root via isInside. If a relative-but-tricky input (symlinks in the middle, weird separators, overlapping names like 'root-evil') resolves outside root, this PathScopeError is thrown.","triggerScenarios":"Input passes isEscapingPath but path.resolve(root, input) lands outside root — e.g. after normalization of unusual segments — or an intermediate symlink points out of the root.","commonSituations":"Agents submitting crafted relative paths; directories whose names share a prefix with root; intermediate symlinked dirs inside the workspace pointing elsewhere on disk.","solutions":["Use a plain relative path under the workspace root","Remove or relocate symlinks inside the workspace that point outside it","Verify with path.resolve yourself which absolute path results and adjust the input","If this blocks a legitimate layout, restructure so the target lives inside the root"],"exampleFix":"// before (intermediate symlink out of root)\nresolveWithinRoot(root, 'vendor/link/../secret.txt')\n// after\nresolveWithinRoot(root, 'src/actual-file.ts')","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nconst candidate = path.resolve(root, input);\nif (candidate !== root && !candidate.startsWith(root + path.sep)) {\n  throw new Error(`path escapes workspace root: ${input}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const abs = await resolveWithinRoot(root, input);\n} catch (err) {\n  if (err instanceof PathScopeError && err.message.includes('resolves outside')) {\n    console.error(`${input} normalizes outside the root; inspect intermediate symlinks.`);\n  } else throw err;\n}","preventionTips":["Avoid symlinked intermediate directories inside the workspace pointing outside","Lexically pre-check resolved paths against root before calls","Watch out for sibling directories whose names share root's prefix","Run `find . -type l` periodically to audit workspace symlinks"],"tags":["mcp","path-validation","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"f465ad909315a22120636baf03fa5e28701a50cb","analyzedAt":"2026-08-29T01:27:42.024Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}