microsoft/playwright · error · Error
Attachment name '${fileName}' escapes output directory
Error message
Attachment name '${fileName}' escapes output directory What it means
Thrown by saveOutputFile() when the requested attachment file name, resolved against cliOutputDir (.playwright-cli), would escape that directory (resolveWithinRoot returns null). This is a path-traversal guard preventing a trace-derived file name containing '../' or an absolute path from writing outside the designated output directory. Only applies when no explicit output path was given; an explicit output bypasses the check.
Source
Thrown at packages/playwright-core/src/tools/trace/traceUtils.ts:119
const totalMs = Math.floor(relative);
const minutes = Math.floor(totalMs / 60000);
const seconds = Math.floor((totalMs % 60000) / 1000);
const millis = totalMs % 1000;
return `${minutes}:${seconds.toString().padStart(2, '0')}.${millis.toString().padStart(3, '0')}`;
}
export function actionTitle(action: ActionEntry): string {
return renderTitleForCall({ ...action, type: action.class }) || `${action.class}.${action.method}`;
}
export async function saveOutputFile(fileName: string, content: string | Buffer, explicitOutput?: string): Promise<string> {
let outFile: string;
if (explicitOutput) {
outFile = explicitOutput;
} else {
const resolved = resolveWithinRoot(cliOutputDir, fileName);
if (!resolved)
throw new Error(`Attachment name '${fileName}' escapes output directory`);
await fs.promises.mkdir(path.dirname(resolved), { recursive: true });
outFile = resolved;
}
await fs.promises.writeFile(outFile, content);
return outFile;
}
function buildOrdinalMap(model: TraceModel): { ordinalToCallId: Map<number, string>, callIdToOrdinal: Map<string, number> } {
const actions = model.actions.filter(a => a.group !== 'configuration');
const { rootItem } = buildActionTree(actions);
const ordinalToCallId = new Map<number, string>();
const callIdToOrdinal = new Map<string, number>();
let ordinal = 1;
const visit = (item: ReturnType<typeof buildActionTree>['rootItem']) => {
ordinalToCallId.set(ordinal, item.action.callId);
callIdToOrdinal.set(item.action.callId, ordinal);
ordinal++;View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Pass an explicit output path via the explicitOutput parameter to write to a known location.
- Sanitize the attachment file name: strip path separators and '..' segments before calling saveOutputFile.
- Investigate where the unsafe file name originated — it usually indicates a bug or untrusted input upstream.
Example fix
// before
saveOutputFile('../out/log.txt', content);
// after
saveOutputFile('log.txt', content); Defensive patterns
Strategy: validation
Validate before calling
import path from 'path';
function isSafeAttachmentName(name: string): boolean {
if (path.isAbsolute(name)) return false;
const resolved = path.resolve('.playwright-cli', name);
const root = path.resolve('.playwright-cli');
return resolved === root || resolved.startsWith(root + path.sep);
}
if (!isSafeAttachmentName(fileName)) throw new Error(`Unsafe attachment name: ${fileName}`); Type guard
function isSafeAttachmentName(name: string): boolean {
return !path.isAbsolute(name) && !name.includes('..') && !name.includes(path.sep);
} Prevention
- Pass plain file names (no path separators, no '..') as attachment names.
- Provide an explicit output path when you need to write outside .playwright-cli.
- Sanitize any file name derived from untrusted or external input.
When it happens
Trigger: A tool/snapshot producing an attachment whose file name contains traversal sequences (e.g. '../evil.txt') or an absolute path, and the caller did not pass an explicit output path.
Common situations: Malformed or adversarial input feeding an attachment name; a bug producing a file name with leading slashes; normal Playwright output names should never trigger this.
Related errors
- Trace entry '${entry}' escapes output directory
- Trace file not found: ${filePath}
- Cannot find .trace file
- Download filename '${downloadFilename}' escapes download dir
- HAR entry _file escapes base directory: ${file}
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/eae989ffcb6f6cc8.
Report an issue: GitHub.