{"record":{"id":"522a1ad4b046034a","repo":"microsoft/playwright","slug":"trace-entry-entry-escapes-output-directory","errorCode":null,"errorMessage":"Trace entry '${entry}' escapes output directory","messagePattern":"Trace entry '(.+?)' escapes output directory","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playwright-core/src/tools/trace/traceParser.ts","lineNumber":91,"sourceCode":"    const resolved = resolveWithinRoot(this._dir, entryName);\n    if (!resolved)\n      return;\n    try {\n      const buffer = await fs.promises.readFile(resolved);\n      return new Blob([new Uint8Array(buffer)]);\n    } catch {\n    }\n  }\n}\n\nexport async function extractTrace(traceFile: string, outDir: string): Promise<void> {\n  const zipFile = new ZipFile(traceFile);\n  try {\n    const entries = await zipFile.entries();\n    for (const entry of entries) {\n      const outPath = resolveWithinRoot(outDir, entry);\n      if (!outPath)\n        throw new Error(`Trace entry '${entry}' escapes output directory`);\n      await fs.promises.mkdir(path.dirname(outPath), { recursive: true });\n      const buffer = await zipFile.read(entry);\n      await fs.promises.writeFile(outPath, buffer);\n    }\n  } finally {\n    zipFile.close();\n  }\n}\n","sourceCodeStart":73,"sourceCodeEnd":100,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/playwright-core/src/tools/trace/traceParser.ts#L73-L100","documentation":"Thrown by extractTrace() when a zip entry inside a .trace.zip file would, when joined with the output directory, resolve to a path outside that directory (resolveWithinRoot returns null). This is a path-traversal / 'zip-slip' guard: a maliciously or corruptly crafted trace archive could otherwise write files anywhere on disk during extraction.","triggerScenarios":"Opening/extracting a trace archive whose internal entry names contain '../' sequences or absolute paths, e.g. an entry named '../../../etc/passwd'. Can also occur with archivers that emit absolute entry names.","commonSituations":"A hand-edited or third-party-supplied trace zip; a corrupted download; an archive produced by a tool that stores absolute paths. In normal Playwright-generated traces this should never happen.","solutions":["Re-export the trace from the original Playwright run; do not use a hand-modified archive.","If you received the trace from an untrusted source, treat it as untrusted and do not extract it.","Inspect the archive entry names (unzip -l) for '../' or leading slashes and repackage it without traversal sequences if you control the source."],"exampleFix":"// inspect offending entries\nunzip -l trace.zip | grep '\\.\\./'\n// regenerate from source\nnpx playwright test --trace=on","handlingStrategy":"validation","validationCode":"import path from 'path';\nfunction entriesAreSafe(entries: string[], outDir: string): boolean {\n  const root = path.resolve(outDir);\n  return entries.every(e => {\n    if (path.isAbsolute(e)) return false;\n    const resolved = path.resolve(root, e);\n    return resolved === root || resolved.startsWith(root + path.sep);\n  });\n}","typeGuard":"function isSafeEntryName(entry: string): boolean {\n  return !path.isAbsolute(entry) && !entry.includes('..');\n}","tryCatchPattern":"try {\n  await extractTrace(traceFile, outDir);\n} catch (e) {\n  if (/escapes output directory/.test((e as Error).message)) {\n    console.warn('Refusing to extract unsafe trace archive:', traceFile);\n  }\n  throw e;\n}","preventionTips":["Only extract trace archives produced by Playwright itself.","Treat third-party or downloaded trace zips as untrusted input.","Run extraction in a sandbox/throwaway directory so traversal cannot reach sensitive paths."],"tags":["trace","zip","security","path-traversal","filesystem"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}