microsoft/playwright · error · Error
HAR zip entry '${entry}' escapes output directory
Error message
HAR zip entry '${entry}' escapes output directory What it means
Thrown by harUnzip() in localUtils when a zip entry name, after resolution via resolveWithinRoot(), falls outside the designated resources directory. This is a zip-slip / path-traversal security guard preventing malicious zip entries with names like '../../etc/passwd' from writing files outside the intended output directory.
Source
Thrown at packages/playwright-core/src/server/localUtils.ts:205
}
export async function harUnzip(progress: Progress, params: channels.LocalUtilsHarUnzipParams): Promise<void> {
const resourcesDir = params.resourcesDir ?? path.dirname(params.zipFile);
const zipFile = new ZipFile(params.zipFile);
let resourcesDirCreated = false;
try {
for (const entry of await progress.race(zipFile.entries())) {
const buffer = await progress.race(zipFile.read(entry));
if (entry === 'har.har') {
await progress.race(fs.promises.writeFile(params.harFile, buffer));
} else {
if (!resourcesDirCreated) {
await progress.race(fs.promises.mkdir(resourcesDir, { recursive: true }));
resourcesDirCreated = true;
}
const outPath = resolveWithinRoot(resourcesDir, entry);
if (!outPath)
throw new Error(`HAR zip entry '${entry}' escapes output directory`);
await progress.race(fs.promises.writeFile(outPath, buffer));
}
}
await progress.race(fs.promises.unlink(params.zipFile));
} finally {
zipFile.close();
}
}
export async function tracingStarted(progress: Progress, stackSessions: Map<string, StackSession>, params: channels.LocalUtilsTracingStartedParams): Promise<channels.LocalUtilsTracingStartedResult> {
let tmpDir = undefined;
if (!params.tracesDir)
tmpDir = await progress.race(fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-tracing-')));
const traceStacksFile = path.join(params.tracesDir || tmpDir!, params.traceName + '.stacks');
// Ensure the directory exists before addStackToTracingNoReply races ahead of
// the tracing recorder's own (separately queued) mkdir.
await progress.race(fs.promises.mkdir(path.dirname(traceStacksFile), { recursive: true }));
stackSessions.set(traceStacksFile, { callStacks: [], file: traceStacksFile, writer: Promise.resolve(), tmpDir, live: params.live });View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Re-create the HAR zip using Playwright's built-in HAR recording to ensure clean entry names.
- Inspect the zip file's entry list (unzip -l) and fix any entries containing '..' or absolute paths.
- Use a non-zipped HAR format instead, or ensure all zip entries are simple relative filenames.
Defensive patterns
Strategy: validation
Validate before calling
// Validate zip entries before extraction
const zip = new (require('yauzl-with-promise'))(zipPath); // or similar
for (const entry of await zip.entries()) {
if (entry.includes('..') || path.isAbsolute(entry))
throw new Error(`Unsafe zip entry: ${entry}`);
} Prevention
- Use Playwright's built-in HAR recording to produce safe zip files.
- Inspect zip entry names with unzip -l before processing.
- Avoid processing HAR zips from untrusted sources.
When it happens
Trigger: Calling harUnzip (triggered internally when Playwright processes a zipped HAR file) where the zip archive contains entries with path traversal sequences in their names. The resolveWithinRoot function rejects any entry that would resolve outside the resourcesDir.
Common situations: Zipped HAR file was produced by a faulty tool or manually constructed with unsafe entry names. HAR zip downloaded from an untrusted source. Zip file created on Windows with absolute paths or backslash-based traversal. Race condition or corruption in zip creation producing malformed entry names.
Related errors
- HAR entry _file escapes base directory: ${file}
- Trace entry '${entry}' escapes output directory
- Download filename '${downloadFilename}' escapes download dir
- Attachment name '${fileName}' escapes output directory
- Route from har is not supported in thin clients
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/507216dab1464bc7.
Report an issue: GitHub.