{"record":{"id":"8f466c076c62af66","repo":"mastra-ai/mastra","slug":"recording-outputpath-must-be-inside-basedir","errorCode":null,"errorMessage":"Recording outputPath must be inside ${baseDir}","messagePattern":"Recording outputPath must be inside (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/browser/recording/tools.ts","lineNumber":116,"sourceCode":"\nfunction generateRecordingId(): string {\n  return `rec_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction recordingsDir(outputDir: string): string {\n  return resolve(outputDir);\n}\n\nfunction defaultOutputPath(id: string, outputDir: string): string {\n  return join(recordingsDir(outputDir), `${id}.avi`);\n}\n\nfunction resolveOutputPath(id: string, outputDir: string, requestedPath?: string): string {\n  const baseDir = recordingsDir(outputDir);\n  const outputPath = requestedPath ? resolve(requestedPath) : defaultOutputPath(id, outputDir);\n  const rel = relative(baseDir, outputPath);\n  if (rel === '' || isAbsolute(rel) || rel === '..' || rel.startsWith(`..${sep}`)) {\n    throw new Error(`Recording outputPath must be inside ${baseDir}`);\n  }\n  return outputPath;\n}\n\nfunction clearState(): void {\n  if (active?.autoStopTimer) {\n    clearTimeout(active.autoStopTimer);\n  }\n  if (active?.watchdogTimer) {\n    clearInterval(active.watchdogTimer);\n  }\n  active = null;\n}\n\n/** Internal: stop the screencast, ignoring errors. */\nasync function safeStop(stream: ScreencastStream): Promise<void> {\n  try {\n    await stream.stop();","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/browser/recording/tools.ts#L98-L134","documentation":"resolveOutputPath constrains the recording's output file to live inside the recordings base directory. It computes the relative path from baseDir and throws if the requested path is outside it, is the directory itself, or is a parent (path traversal guard).","triggerScenarios":"Calling browser_record with an outputPath that is absolute elsewhere, points above the recordings dir via ../, or equals the base directory itself.","commonSituations":"Users passing /tmp/video.avi or C:\\temp\\out.avi while recordings are rooted elsewhere; '../escape.avi' style traversal (possibly from untrusted input); forgetting the tool sandboxes outputs by design.","solutions":["Pass an outputPath relative to the recordings directory (or omit it to use the default path)","Use path.join(recordingsDir, 'my-video.avi') so the resolved path stays inside baseDir","If you need output elsewhere, change outputDir when starting the recording rather than escaping via outputPath"],"exampleFix":"// before\nbrowser_record({ action: 'start', outputPath: '/tmp/video.avi' });\n// after\nbrowser_record({ action: 'start', outputPath: 'session-42/video.avi' }); // resolved inside recordingsDir","handlingStrategy":"validation","validationCode":"import { resolve, relative, isAbsolute } from 'node:path';\nconst baseDir = recordingsDir(outputDir);\nfunction isInsideBase(p) {\n  const rel = relative(baseDir, resolve(p));\n  return rel !== '' && !isAbsolute(rel) && rel !== '..' && !rel.startsWith('..' + require('node:path').sep);\n}\nif (!isInsideBase(requestedPath)) throw new Error('outputPath must stay inside the recordings directory');","typeGuard":"function isSafeOutputPath(p) {\n  const { resolve, relative, isAbsolute, sep } = require('node:path');\n  const rel = relative(baseDir, resolve(p));\n  return rel !== '' && !isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`);\n}","tryCatchPattern":"try {\n  await browser_record({ action: 'start', outputPath: userPath });\n} catch (e) {\n  if (e.message.startsWith('Recording outputPath must be inside')) {\n    // fall back to the default output path\n  } else throw e;\n}","preventionTips":["Pass relative paths inside the recordings directory","Sanitize user-supplied paths before passing them to the tool","Never build outputPath from untrusted input without confinement checks"],"tags":["path-traversal","security","validation","recording"],"backgroundTag":"path-outside-allowed-directory","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}