mastra-ai/mastra · error
${label} escapes workspace
Error message
${label} escapes workspace What it means
assertRelativePath validates that a query path parameter (labelled e.g. 'path' or 'root') is a safe relative path inside the workspace before it is used for filesystem access. The factory throws "<label> escapes workspace" when the path contains a '..' segment or normalizes outside the root, because such a path could traverse out of the confined workspace directory. It is a deliberate path-traversal guard, not a bug.
Source
Thrown at mastracode/factory/src/routes/fs.ts:218
* Resolve a path's real location (following symlinks) and confirm it stays
* within `root`. Returns the real path when confined, or `null` when it escapes
* the root or does not exist. Used so a symlink inside the root that points
* outside it cannot be browsed or selected.
*/
async function realPathWithinRoot(candidate: string, root: string): Promise<string | null> {
try {
const real = await realpath(candidate);
return isWithinRoot(real, root) ? real : null;
} catch {
return null;
}
}
function assertRelativePath(path: string, label: string): string {
const trimmed = path.trim();
if (!trimmed) throw new Error(`Missing required query param: ${label}`);
if (isAbsolute(trimmed)) throw new Error(`${label} must be relative`);
if (trimmed.split(/[\\/]+/).includes('..')) throw new Error(`${label} escapes workspace`);
const normalized = resolve('/', trimmed).slice(1);
if (!normalized || normalized === '..' || normalized.startsWith(`..${sep}`))
throw new Error(`${label} escapes workspace`);
return normalized;
}
function assertApprovedRenderedRoot(renderedRoot: string): string {
const safeRoot = assertRelativePath(renderedRoot, 'root');
if (!APPROVED_RENDERED_ROOTS.has(safeRoot)) throw new Error('Root is not approved for rendered workspace access');
return safeRoot;
}
async function confinedWorkspacePath(
root: string,
workspacePath: string,
): Promise<{ resolvedRoot: string; workspace: string }> {
const resolvedRoot = await realOrResolved(resolveFsRoot(root));
const candidate = isAbsolute(workspacePath) ? resolve(workspacePath) : resolve(resolvedRoot, workspacePath);View on GitHub (pinned to 75dd419e61)
Solutions
- Remove '..' segments from the path before sending; navigate within the workspace using paths relative to its root
- Use path.resolve(workspaceRoot, requested) in the caller and verify the result starts with workspaceRoot before passing it as the query param
- Pass an absolute workspacePath via the dedicated workspace param (which is confined via confinedWorkspacePath) instead of escaping a relative one
- If upward navigation is needed, request the workspace root itself rather than '..'
Example fix
// before
const res = await fetch(`/api/fs?path=${encodeURIComponent('../other-project/file.txt')}`);
// after
const rel = path.relative(workspaceRoot, targetFile);
if (rel.startsWith('..')) throw new Error('target is outside workspace');
const res = await fetch(`/api/fs?path=${encodeURIComponent(rel)}`); Defensive patterns
Strategy: validation
Validate before calling
import { isAbsolute, resolve, sep } from 'node:path';
function isSafeRelative(p: string): boolean {
const t = p.trim();
if (!t || isAbsolute(t)) return false;
if (t.split(/[\\/]+/).includes('..')) return false;
const norm = resolve('/', t).slice(1);
return !!norm && norm !== '..' && !norm.startsWith(`..${sep}`);
}
if (!isSafeRelative(userPath)) throw new Error('path escapes workspace'); Type guard
function isSafeRelativePath(p: unknown): p is string {
return typeof p === 'string' && isSafeRelative(p);
} Try / catch
try {
const data = await fetchFsRoute({ path: userPath });
} catch (err) {
if (err instanceof Error && err.message.includes('escapes workspace')) {
// clamp to workspace root or re-derive with path.relative(root, target)
} else throw err;
} Prevention
- Always build paths with path.relative(workspaceRoot, target) instead of string manipulation
- Reject '..' segments client-side before any API call
- Use resolve() and an isWithinRoot prefix check as a habit
- Never concatenate user input into path query params unchecked
When it happens
Trigger: Calling any fs route (via safeRoot, safeRelativePath, safePath, or safePreviousPath) with a query param containing '..' segments (e.g. ?path=../secrets), a path that normalizes to empty or '..' (e.g. ?path=..), or a path like 'a/../../b' that resolves above the root after resolve('/', trimmed).
Common situations: Client code joining paths with string concatenation instead of resolve(); passing an absolute path stripped incorrectly; encoding '..%2F' that a framework decodes before validation; tests or scripts reusing paths computed for a different workspace depth; symlink-free traversal attempts in UI 'go up one level' handlers.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Path escapes workspace
- Invalid resourceId: ${resourceId}
- Invalid route path: "${path}". Path cannot contain '..', '?'
- Worker ${label} must stay within the deployed artifact root.
- ${label} must be relative
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/cb180dcd537a35b9.
Report an issue: GitHub.