{"record":{"id":"1c14a2020a1ab87d","repo":"mastra-ai/mastra","slug":"refusing-to-use-local-sandbox-path-outside-configu","errorCode":null,"errorMessage":"Refusing to use local sandbox path outside configured root: ${resolved}","messagePattern":"Refusing to use local sandbox path outside configured root: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/sandbox/workdir.ts","lineNumber":52,"sourceCode":"  if (sandbox.provider === 'local' && typeof wd === 'string' && wd.length > 0) {\n    const [, name] = repoFullName.split('/', 2);\n    return resolveContainedLocalWorkdir(wd, sanitizeSegment(name || 'repo'));\n  }\n  return undefined;\n}\n\n/** `<home>/<repo>` — where a remote VM's default-cwd clone lands. */\nexport function remoteWorkdirFromHome(home: string, repoFullName: string): string {\n  const [, name] = repoFullName.split('/', 2);\n  return `${home.replace(/\\/+$/, '')}/${sanitizeSegment(name || 'repo')}`;\n}\n\n/** Resolve a workdir under `root`, refusing any path that escapes the configured root. */\nexport function resolveContainedLocalWorkdir(root: string, ...segments: string[]): string {\n  const resolvedRoot = path.resolve(root);\n  const resolved = path.resolve(resolvedRoot, ...segments);\n  if (resolved !== resolvedRoot && resolved.startsWith(`${resolvedRoot}${path.sep}`)) return resolved;\n  throw new Error(`Refusing to use local sandbox path outside configured root: ${resolved}`);\n}\n","sourceCodeStart":34,"sourceCodeEnd":54,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/sandbox/workdir.ts#L34-L54","documentation":"resolveContainedLocalWorkdir() builds a local sandbox workdir by resolving segments under a configured root and rejects any result that escapes that root (a path-traversal guard). The throw means the requested segments resolved outside the configured sandbox root, so the library refuses to expose that path. resolved !== resolvedRoot and the startsWith check together allow only the root itself or strict subdirectories.","triggerScenarios":"Calling resolveContainedLocalWorkdir (directly or via deriveLocalWorkdir) with segments such as '../..' or a repoFullName containing traversal so that path.resolve lands outside the configured root; on Windows, segments that change drive or case-mismatched roots.","commonSituations":"Repo names with '..' or unusual characters passed as path segments; a configured root that differs in casing/separator from what segments assume (Windows drive letters, UNC paths); symlinked root directory making resolved paths differ; users configuring a workspace outside the sandbox root.","solutions":["Sanitize the input segments (strip/normalize '..', validate repo name characters) before calling resolveContainedLocalWorkdir.","Set the sandbox root configuration to the actual parent of the workspace you expect so the resolved path stays inside it.","Normalize with path.resolve(path.normalize(...)) and check containment yourself to debug which segment escapes.","If you truly need a path outside the root, move/copy the workspace inside the configured root rather than bypassing the guard."],"exampleFix":"// before\nderiveLocalWorkdir(root, '../../etc/passwd'); // escapes root -> throws\n\n// after\nconst safeName = repoFullName.replace(/[^a-zA-Z0-9._-]/g, '-');\nderiveLocalWorkdir(root, safeName); // stays under root","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nfunction isInsideRoot(root: string, ...segments: string[]): boolean {\n  const resolvedRoot = path.resolve(root);\n  const resolved = path.resolve(resolvedRoot, ...segments);\n  return resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path.sep);\n}\n// call resolveContainedLocalWorkdir only if isInsideRoot(root, ...segments)","typeGuard":"function isContainedPath(root: string, candidate: string): boolean {\n  const rel = path.relative(path.resolve(root), path.resolve(candidate));\n  return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n}","tryCatchPattern":"let workdir: string;\ntry {\n  workdir = resolveContainedLocalWorkdir(root, repoFullName);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Refusing to use local sandbox path')) {\n    throw new Error(`Repo path '${repoFullName}' escapes sandbox root '${root}'; sanitize segments or fix root config`);\n  }\n  throw err;\n}","preventionTips":["Sanitize repo/workspace names (strip '..', control chars) before path composition.","Keep sandbox root configuration normalized with path.resolve and consistent separators.","Add tests for traversal inputs ('..', absolute segments) against the workdir resolver.","Verify symlink targets under the root also stay contained."],"tags":["path-traversal","security","sandbox","configuration"],"backgroundTag":"path-escapes-root","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}