mastra-ai/mastra · error
Root is not approved for rendered workspace access
Error message
Root is not approved for rendered workspace access
What it means
assertApprovedRenderedRoot validates that a requested workspace root (after relative-path sanitization) is one of the pre-approved rendered roots in APPROVED_RENDERED_ROOTS. The factory throws "Root is not approved for rendered workspace access" when the caller asks the fs routes to serve a root that the deployment has not explicitly allow-listed, preventing arbitrary directories from being exposed for rendered output browsing.
Source
Thrown at mastracode/factory/src/routes/fs.ts:227
} 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);
const workspace = await realPathWithinRoot(candidate, resolvedRoot);
if (!workspace) throw new Error('Path is outside the browsable root');
return { resolvedRoot, workspace };
}
async function confinedWorkspaceRelativePath(
root: string,
workspacePath: string,
relativePath: string,View on GitHub (pinned to 75dd419e61)
Solutions
- Use one of the approved root values exactly as listed in APPROVED_RENDERED_ROOTS in mastracode/factory/src/routes/fs.ts
- If a new root is legitimately needed, add its sanitized relative form to APPROVED_RENDERED_ROOTS and redeploy
- Log/inspect the sanitized root (assertRelativePath output) to spot mismatched case, slashes, or nesting
- Verify the route is the right one: rendered-workspace routes only accept rendered roots; generic browsing routes take the configured workspace root instead
Example fix
// before
const res = await fetch('/api/fs/rendered?root=sessions/tmp-123/output');
// after (add to APPROVED_RENDERED_ROOTS first, or use an approved root)
const APPROVED = ['rendered', 'sessions/rendered'];
const root = APPROVED.includes('rendered') ? 'rendered' : APPROVED[0];
const res = await fetch(`/api/fs/rendered?root=${root}`); Defensive patterns
Strategy: validation
Validate before calling
const APPROVED_RENDERED_ROOTS = new Set(['rendered']); // mirror of the server-side set
function useApprovedRoot(root: string): string {
const safe = root.trim().replace(/^\/+/, '');
if (!APPROVED_RENDERED_ROOTS.has(safe)) throw new Error(`root not approved: ${safe}`);
return safe;
} Type guard
function isApprovedRoot(root: unknown): root is string {
return typeof root === 'string' && APPROVED_RENDERED_ROOTS.has(root.trim().replace(/^\/+/, ''));
} Try / catch
try {
return await renderedRoute({ root });
} catch (err) {
if (err instanceof Error && err.message.includes('not approved for rendered workspace')) {
root = 'rendered'; // fall back to the default approved root
return await renderedRoute({ root });
}
throw err;
} Prevention
- Keep a shared constant of approved roots in sync between client and server
- When adding new workspace roots, update APPROVED_RENDERED_ROOTS in the same PR
- Match the sanitized form exactly (no leading/trailing slashes, correct case)
- Prefer the default approved root unless a new one was explicitly approved
When it happens
Trigger: Calling routes that use safeRoot, readWorkspaceFile, or readSessionWorkspaceFile with ?root=<something> whose sanitized value is not present in APPROVED_RENDERED_ROOTS — e.g. a typo'd root name, a session-specific directory not on the list, or a root added to the filesystem but not to the allow-list constant.
Common situations: New workspace/session directories created by the pipeline but APPROVED_RENDERED_ROOTS not updated; case or trailing-slash mismatches after sanitization; deploying code with a hardcoded root name that differs per environment; tests referencing fixture roots not in the allow-list.
Related errors
- Cookie password must be at least 32 characters for SSO. Set
- [MastraAuthGoogle] GOOGLE_COOKIE_PASSWORD is required for Go
- Cookie password must be at least 32 characters. Set OKTA_COO
- MastraFactory: integration '${integration.id}' signs OAuth s
- ${label} escapes workspace
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/110b04c764bb18a0.
Report an issue: GitHub.