mastra-ai/mastra · error

Recording outputPath must be inside ${baseDir}

Error message

Recording outputPath must be inside ${baseDir}

What it means

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).

Source

Thrown at packages/core/src/browser/recording/tools.ts:116

function generateRecordingId(): string {
  return `rec_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
}

function recordingsDir(outputDir: string): string {
  return resolve(outputDir);
}

function defaultOutputPath(id: string, outputDir: string): string {
  return join(recordingsDir(outputDir), `${id}.avi`);
}

function resolveOutputPath(id: string, outputDir: string, requestedPath?: string): string {
  const baseDir = recordingsDir(outputDir);
  const outputPath = requestedPath ? resolve(requestedPath) : defaultOutputPath(id, outputDir);
  const rel = relative(baseDir, outputPath);
  if (rel === '' || isAbsolute(rel) || rel === '..' || rel.startsWith(`..${sep}`)) {
    throw new Error(`Recording outputPath must be inside ${baseDir}`);
  }
  return outputPath;
}

function clearState(): void {
  if (active?.autoStopTimer) {
    clearTimeout(active.autoStopTimer);
  }
  if (active?.watchdogTimer) {
    clearInterval(active.watchdogTimer);
  }
  active = null;
}

/** Internal: stop the screencast, ignoring errors. */
async function safeStop(stream: ScreencastStream): Promise<void> {
  try {
    await stream.stop();

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Pass an outputPath relative to the recordings directory (or omit it to use the default path)
  2. Use path.join(recordingsDir, 'my-video.avi') so the resolved path stays inside baseDir
  3. If you need output elsewhere, change outputDir when starting the recording rather than escaping via outputPath

Example fix

// before
browser_record({ action: 'start', outputPath: '/tmp/video.avi' });
// after
browser_record({ action: 'start', outputPath: 'session-42/video.avi' }); // resolved inside recordingsDir
Defensive patterns

Strategy: validation

Validate before calling

import { resolve, relative, isAbsolute } from 'node:path';
const baseDir = recordingsDir(outputDir);
function isInsideBase(p) {
  const rel = relative(baseDir, resolve(p));
  return rel !== '' && !isAbsolute(rel) && rel !== '..' && !rel.startsWith('..' + require('node:path').sep);
}
if (!isInsideBase(requestedPath)) throw new Error('outputPath must stay inside the recordings directory');

Type guard

function isSafeOutputPath(p) {
  const { resolve, relative, isAbsolute, sep } = require('node:path');
  const rel = relative(baseDir, resolve(p));
  return rel !== '' && !isAbsolute(rel) && rel !== '..' && !rel.startsWith(`..${sep}`);
}

Try / catch

try {
  await browser_record({ action: 'start', outputPath: userPath });
} catch (e) {
  if (e.message.startsWith('Recording outputPath must be inside')) {
    // fall back to the default output path
  } else throw e;
}

Prevention

When it happens

Trigger: Calling browser_record with an outputPath that is absolute elsewhere, points above the recordings dir via ../, or equals the base directory itself.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/8f466c076c62af66. Report an issue: GitHub.