{"record":{"id":"7f0be3f4d11b5023","repo":"mastra-ai/mastra","slug":"path-escapes-workspace-root-inputpath","errorCode":null,"errorMessage":"Path escapes workspace root: ${inputPath}","messagePattern":"Path escapes workspace root: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/agents/sandbox-filesystem.ts","lineNumber":165,"sourceCode":"   * Accepts both workspace-relative paths (`src/foo.ts`, `/src/foo.ts`) and\n   * absolute sandbox paths that already live under the workdir — the agent's\n   * prompt advertises the workdir as its working directory, so tools are\n   * routinely called with fully-qualified paths like `<workdir>/src/foo.ts`.\n   */\n  private resolveAgainst(basePath: string, inputPath: string): string {\n    const base = posixPath.normalize(basePath);\n    const normalizedInput = posixPath.normalize(inputPath);\n    const rel =\n      normalizedInput === base\n        ? ''\n        : normalizedInput.startsWith(`${base}/`)\n          ? normalizedInput.slice(base.length + 1)\n          : inputPath.startsWith('/')\n            ? inputPath.slice(1)\n            : inputPath;\n    const resolved = posixPath.normalize(posixPath.join(base, rel));\n    if (resolved !== base && !resolved.startsWith(`${base}/`)) {\n      throw new Error(`Path escapes workspace root: ${inputPath}`);\n    }\n    return resolved;\n  }\n\n  resolveAbsolutePath(inputPath: string): string | undefined {\n    // Sync interface: a lazy workdir that has not resolved yet has no\n    // absolute form to offer.\n    if (!this.resolvedBase) return undefined;\n    return this.resolveAgainst(this.resolvedBase, inputPath);\n  }\n\n  // ── Command helper ─────────────────────────────────────────────────────\n\n  private async exec(script: string): Promise<SandboxCommandResult> {\n    return this.sandbox.executeCommand('sh', ['-c', script], { timeout: COMMAND_TIMEOUT_MS });\n  }\n\n  /**","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/agents/sandbox-filesystem.ts#L147-L183","documentation":"This sandboxed filesystem resolves every user-supplied path against the workspace root and refuses anything that lexically normalizes outside it. The library throws this to enforce the sandbox boundary: even a path like `a/../../etc/passwd` or an absolute path pointing at a sibling directory is rejected before any shell command runs. It is a deliberate security guard, not a bug.","triggerScenarios":"Calling resolveAsync, resolveAbsolutePath, or any file operation built on resolveAgainst with a path containing `..` segments that escape the root (e.g. `../secrets.txt`, `a/../../x`), or an absolute path that does not fall under the configured workspace root base.","commonSituations":"Passing an absolute host path (e.g. `/home/user/file`) into a sandbox whose root is a different directory; joining a workspace-external temp dir; resolving symlink-like or user-provided paths that contain `..`; configuring the sandbox workdir lower in the tree than the paths the agent tries to touch.","solutions":["Remove `..` segments or rewrite the path so it resolves inside the workspace root","If the target genuinely lives outside the root, move it into the workspace or reconfigure the sandbox workdir to encompass it","Normalize/validate caller-supplied paths (e.g. path.resolve then checking containment) before passing them to the sandbox API"],"exampleFix":"// before\nawait fs.resolveAsync('../outside/secret.txt'); // throws\n// after\nawait fs.resolveAsync('outside/secret.txt'); // stays within workspace root","handlingStrategy":"validation","validationCode":"import path from 'node:path';\nexport function isInsideRoot(root: string, input: string): boolean {\n  const resolvedInput = path.resolve(root, input);\n  const resolvedRoot = path.resolve(root);\n  return resolvedInput === resolvedRoot || resolvedInput.startsWith(resolvedRoot + path.sep);\n}\nif (!isInsideRoot(WORKSPACE_ROOT, userInput)) throw new Error('Path must stay inside the workspace root');","typeGuard":"function isSafeWorkspacePath(p: string): boolean {\n  const norm = path.posix.normalize(p.replace(/^\\//, ''));\n  return !norm.startsWith('..') && norm !== '..' && !path.posix.isAbsolute(norm);\n}","tryCatchPattern":"try {\n  const resolved = await fs.resolveAsync(inputPath);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith('Path escapes workspace root')) {\n    // reject/ask for corrected path; never retry with the same input\n  }\n  throw err;\n}","preventionTips":["Normalize and containment-check user/LLM-provided paths before handing them to the sandbox API","Never concatenate raw absolute host paths into sandbox path arguments","Configure the sandbox workdir high enough to cover all paths the workload needs","Strip or reject `..` segments in any path template or interpolated input"],"tags":["sandbox","filesystem","path-traversal","security"],"backgroundTag":"path-escapes-workspace-root","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}