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

  1. Re-create the HAR zip using Playwright's built-in HAR recording to ensure clean entry names.
  2. Inspect the zip file's entry list (unzip -l) and fix any entries containing '..' or absolute paths.
  3. 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

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


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/507216dab1464bc7. Report an issue: GitHub.