{"record":{"id":"eff2100a2548ae17","repo":"mastra-ai/mastra","slug":"path-is-outside-the-workspace","errorCode":null,"errorMessage":"Path is outside the workspace","messagePattern":"Path is outside the workspace","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/routes/fs.ts","lineNumber":252,"sourceCode":"): Promise<{ resolvedRoot: string; workspace: string }> {\n  const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n  const candidate = isAbsolute(workspacePath) ? resolve(workspacePath) : resolve(resolvedRoot, workspacePath);\n  const workspace = await realPathWithinRoot(candidate, resolvedRoot);\n  if (!workspace) throw new Error('Path is outside the browsable root');\n  return { resolvedRoot, workspace };\n}\n\nasync function confinedWorkspaceRelativePath(\n  root: string,\n  workspacePath: string,\n  relativePath: string,\n): Promise<{ workspace: string; path: string; relativePath: string }> {\n  const safeRelativePath = assertRelativePath(relativePath, 'path');\n  const { workspace } = await confinedWorkspacePath(root, workspacePath);\n  const candidate = resolve(workspace, safeRelativePath);\n  if (!isWithinRoot(candidate, workspace)) throw new Error('Path escapes workspace');\n  const confinedPath = await realPathWithinRoot(candidate, workspace);\n  if (!confinedPath) throw new Error('Path is outside the workspace');\n  return { workspace, path: confinedPath, relativePath: safeRelativePath };\n}\n\n/**\n * List the directories inside `requestedPath`, confined to `root`. An absent or\n * out-of-root path is clamped to the root, so the worst a malicious client can\n * do is browse within the allowed root.\n */\nexport async function listDirectory(root: string, requestedPath?: string): Promise<DirectoryListing> {\n  // Resolve the root through symlinks so all confinement checks compare real\n  // paths; a symlink that escapes the root is then reliably detectable.\n  const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n\n  let target = resolvedRoot;\n  if (requestedPath && requestedPath.trim()) {\n    const candidate = isAbsolute(requestedPath) ? resolve(requestedPath) : resolve(resolvedRoot, requestedPath);\n    // Follow symlinks and re-confirm the real target stays within the root.\n    target = (await realPathWithinRoot(candidate, resolvedRoot)) ?? resolvedRoot;","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/routes/fs.ts#L234-L270","documentation":"confinedWorkspaceRelativePath resolves a client-supplied relative path inside a workspace after validating it (no absolute path, no '..' segments), then follows symlinks via realPathWithinRoot to confirm the real target still lands inside the workspace. This error is thrown when the candidate path does not exist (realpath fails) OR exists but its symlink-resolved real location is outside the workspace. The library throws it to prevent symlink-based escape from the workspace sandbox and to signal that the requested path cannot be served.","triggerScenarios":"Calling any fs route that resolves a relative path (e.g. readWorkspaceFile, listWorkspaceRenderedPath helpers) where: (1) the relative path points to a file that does not exist in the workspace, or (2) the path is (or passes through) a symlink whose real target resolves outside the workspace directory.","commonSituations":"A stale UI bookmark pointing at a deleted file; a symlinked node_modules or assets directory in the workspace pointing outside the browsable root; a typo in the path query param; race where a file is deleted between listing and read.","solutions":["Verify the relative path exists inside the workspace directory (ls the workspace to confirm).","Check for symlinks in the path (ls -l / readlink) and replace links that point outside the workspace with real files or copies inside it.","Recreate the missing file if it was deleted, or refresh the client's file listing to get a valid path.","If the intent was to browse, use the listing endpoints which clamp out-of-root paths to the workspace root instead of throwing."],"exampleFix":"// before: path is a symlink escaping the workspace\nGET /fs/file?path=link/outside.txt   // link -> /etc/passwd → 'Path is outside the workspace'\n// after: copy the target inside the workspace and reference the copy\ncp /etc/passwd ./data/ref.txt\nGET /fs/file?path=data/ref.txt","handlingStrategy":"validation","validationCode":"import { stat, realpath } from 'node:fs/promises';\nimport { resolve, isAbsolute, sep } from 'node:path';\nasync function isSafeWorkspacePath(workspace: string, rel: string): Promise<boolean> {\n  const trimmed = rel.trim();\n  if (!trimmed || isAbsolute(trimmed) || trimmed.split(/[\\\\/]+/).includes('..')) return false;\n  const candidate = resolve(workspace, trimmed);\n  if (!candidate.startsWith(workspace + sep)) return false;\n  try {\n    const real = await realpath(candidate);\n    return real.startsWith((await realpath(workspace)) + sep);\n  } catch {\n    return false; // does not exist → would throw\n  }\n}\n// call: if (!(await isSafeWorkspacePath(ws, p))) skip the request;","typeGuard":"function isRelativeInsideWorkspace(rel: string): boolean {\n  const t = rel.trim();\n  return t.length > 0 && !isAbsolute(t) && !t.split(/[\\\\/]+/).includes('..');\n}","tryCatchPattern":"try {\n  const file = await readWorkspaceFile(root, ws, path);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Path is outside the workspace') {\n    // treat as not-found / unsafe link: refresh listing, skip entry\n  } else throw e;\n}","preventionTips":["Never pass absolute paths or '..' segments; always use relative paths returned by listing endpoints.","Avoid symlinks in workspaces that point outside the workspace; audit with `find . -type l ! -exec realpath --relative-to . {} \\;`.","Refresh listings before reads to avoid stale paths to deleted files.","Treat this error as 'not found or unsafe' and skip the entry rather than retrying."],"tags":["filesystem","path-traversal","symlink","security"],"backgroundTag":"path-escapes-workspace","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}