{"record":{"id":"667e84def1be9fd2","repo":"mastra-ai/mastra","slug":"label-must-be-relative","errorCode":null,"errorMessage":"${label} must be relative","messagePattern":"(.+?) must be relative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/routes/fs.ts","lineNumber":217,"sourceCode":"/**\n * Resolve a path's real location (following symlinks) and confirm it stays\n * within `root`. Returns the real path when confined, or `null` when it escapes\n * the root or does not exist. Used so a symlink inside the root that points\n * outside it cannot be browsed or selected.\n */\nasync function realPathWithinRoot(candidate: string, root: string): Promise<string | null> {\n  try {\n    const real = await realpath(candidate);\n    return isWithinRoot(real, root) ? real : null;\n  } catch {\n    return null;\n  }\n}\n\nfunction assertRelativePath(path: string, label: string): string {\n  const trimmed = path.trim();\n  if (!trimmed) throw new Error(`Missing required query param: ${label}`);\n  if (isAbsolute(trimmed)) throw new Error(`${label} must be relative`);\n  if (trimmed.split(/[\\\\/]+/).includes('..')) throw new Error(`${label} escapes workspace`);\n  const normalized = resolve('/', trimmed).slice(1);\n  if (!normalized || normalized === '..' || normalized.startsWith(`..${sep}`))\n    throw new Error(`${label} escapes workspace`);\n  return normalized;\n}\n\nfunction assertApprovedRenderedRoot(renderedRoot: string): string {\n  const safeRoot = assertRelativePath(renderedRoot, 'root');\n  if (!APPROVED_RENDERED_ROOTS.has(safeRoot)) throw new Error('Root is not approved for rendered workspace access');\n  return safeRoot;\n}\n\nasync function confinedWorkspacePath(\n  root: string,\n  workspacePath: string,\n): Promise<{ resolvedRoot: string; workspace: string }> {\n  const resolvedRoot = await realOrResolved(resolveFsRoot(root));","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/routes/fs.ts#L199-L235","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst p = path.join(WORKSPACE_ROOT, 'src/app.ts');\nfetch(`/api/fs/read?path=${encodeURIComponent(p)}`); // absolute => throws\n// after\nconst p = path.relative(WORKSPACE_ROOT, path.join(WORKSPACE_ROOT, 'src/app.ts'));\nfetch(`/api/fs/read?path=${encodeURIComponent(p)}`); // 'src/app.ts'","handlingStrategy":"validation","validationCode":"function toWorkspaceRelative(workspaceRoot: string, p: string): string {\n  const rel = path.relative(workspaceRoot, p);\n  if (!rel || rel.startsWith('..') || path.isAbsolute(p)) {\n    throw new Error(`path must be workspace-relative, got: ${p}`);\n  }\n  return rel.split(path.sep).join('/');\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await api.fsRead({ path });\n} catch (err) {\n  if (err instanceof Error && err.message.endsWith('must be relative')) {\n    return api.fsRead({ path: toWorkspaceRelative(workspaceRoot, path) });\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["http","validation","path-traversal","filesystem"],"backgroundTag":"invalid-path-input","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}