mastra-ai/mastra · error
${label} must be relative
Error message
${label} must be relative What it means
`assertRelativePath` only accepts workspace-relative paths and throws `${label} must be relative` when the supplied query param parses as an absolute path (detected via `isAbsolute`, e.g. starts with '/' on POSIX or 'C:\' on Windows). This prevents clients from reading or writing outside the workspace root by supplying fully qualified filesystem paths.
Source
Thrown at mastracode/factory/src/routes/fs.ts:217
/**
* 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));View on GitHub (pinned to 75dd419e61)
Solutions
- Send the path relative to the workspace root (e.g. `src/index.ts`, not `/work/src/index.ts`).
- On the client, strip the workspace root prefix before sending: `path.relative(workspaceRoot, absPath)`.
- Never pass results of `path.resolve()`/`path.join(absoluteRoot, ...)` directly as the query param.
- Normalize Windows-style paths to forward-slash relative form before the request.
Example fix
// before
const p = path.join(WORKSPACE_ROOT, 'src/app.ts');
fetch(`/api/fs/read?path=${encodeURIComponent(p)}`); // absolute => throws
// after
const p = path.relative(WORKSPACE_ROOT, path.join(WORKSPACE_ROOT, 'src/app.ts'));
fetch(`/api/fs/read?path=${encodeURIComponent(p)}`); // 'src/app.ts' Defensive patterns
Strategy: validation
Validate before calling
function toWorkspaceRelative(workspaceRoot: string, p: string): string {
const rel = path.relative(workspaceRoot, p);
if (!rel || rel.startsWith('..') || path.isAbsolute(p)) {
throw new Error(`path must be workspace-relative, got: ${p}`);
}
return rel.split(path.sep).join('/');
} Try / catch
try {
return await api.fsRead({ path });
} catch (err) {
if (err instanceof Error && err.message.endsWith('must be relative')) {
return api.fsRead({ path: toWorkspaceRelative(workspaceRoot, path) });
}
throw err;
} Prevention
- Always convert client-side paths with path.relative(root, abs) before sending.
- Never send outputs of path.resolve() or path.join(absoluteRoot, ...) as query params.
- Normalize Windows drive-letter paths to slash-separated relative form in cross-platform clients.
When it happens
Trigger: Calling an fs route with a param like `?path=/etc/passwd`, `?path=C:\Users\...`, or any value produced by `path.resolve()`/`absolute()` on the client side instead of a path relative to the workspace.
Common situations: Client joining an absolute base directory with the filename (`path.join(workspaceRoot, file)`) and sending the result; paths copied from absolute file watchers; cross-platform bugs where Windows drive-letter paths appear after syncing from a Windows machine.
Related errors
- Missing required query param: ${label}
- ${label} escapes workspace
- Path escapes workspace
- [FilesystemStorage] path must be a non-empty relative path.
- Invalid resourceId: ${resourceId}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/667e84def1be9fd2.
Report an issue: GitHub.