{"record":{"id":"4da49c3c598b1e4b","repo":"chatboxai/chatbox","slug":"unsafe-zip-entry-path-path","errorCode":null,"errorMessage":"Unsafe ZIP entry path: ${path}","messagePattern":"Unsafe ZIP entry path: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/renderer/packages/backup/zip.ts","lineNumber":41,"sourceCode":"  maxTotalUncompressedBytes?: number\n  maxCompressionRatio?: number\n}\n\nexport interface ZipReadOptions {\n  limits?: ZipReadLimits\n  entryLimits?: (path: string) => Partial<Pick<ZipReadLimits, 'maxEntryUncompressedBytes' | 'maxCompressionRatio'>>\n  signal?: AbortSignal\n}\n\nfunction throwIfAborted(signal?: AbortSignal) {\n  if (signal?.aborted) {\n    throw signal.reason instanceof Error ? signal.reason : new DOMException('Operation canceled', 'AbortError')\n  }\n}\n\nexport function assertSafeArchivePath(path: string): void {\n  if (!path || path.includes('\\0') || path.includes('\\\\') || path.startsWith('/') || /^[a-zA-Z]:/.test(path)) {\n    throw new Error(`Unsafe ZIP entry path: ${path}`)\n  }\n  const segments = path.split('/')\n  if (segments.some((segment) => segment === '' || segment === '.' || segment === '..')) {\n    throw new Error(`Unsafe ZIP entry path: ${path}`)\n  }\n}\n\nfunction* splitBytes(bytes: Uint8Array): Generator<Uint8Array> {\n  for (let offset = 0; offset < bytes.length; offset += ZIP_INPUT_CHUNK_SIZE) {\n    yield bytes.subarray(offset, Math.min(offset + ZIP_INPUT_CHUNK_SIZE, bytes.length))\n  }\n}\n\nfunction toChunks(data: ZipArchiveEntry['data']): Iterable<Uint8Array> | AsyncIterable<Uint8Array> {\n  return data instanceof Uint8Array ? splitBytes(data) : data\n}\n\nexport async function* createZipStream(","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/renderer/packages/backup/zip.ts#L23-L59","documentation":"Thrown by assertSafeArchivePath during the first (lexical) path-safety check. It rejects paths that are empty, contain a NUL byte or backslash, are absolute (start with '/'), or look like a Windows drive root (e.g. 'C:'). This is a zip-slip / path-traversal guard applied to every entry on both read and write.","triggerScenarios":"A ZIP entry name is '', contains '\\\\0' or '\\\\', starts with '/', or matches /^[a-zA-Z]:/.","commonSituations":"Malicious or malformed archive with absolute entry paths; archive created on Windows with backslash separators; an entry crafted for path traversal.","solutions":["Reject the archive; do not attempt to extract paths that fail this guard.","When producing archives, normalize entry paths to relative forward-slash form before adding.","Sanitize entry names: strip leading slashes, convert backslashes to '/', reject empty names."],"exampleFix":"// before\nzip.add(new ZipDeflate(entry.path, { level: 6 }))\n\n// after\nconst safe = entry.path.replace(/\\\\/g, '/').replace(/^\\/+/, '')\nif (!safe || /\\0/.test(safe) || /^[a-zA-Z]:/.test(safe)) {\n  throw new Error(`Refusing unsafe path: ${entry.path}`)\n}\nzip.add(new ZipDeflate(safe, { level: 6 }))","handlingStrategy":"validation","validationCode":"function isLexicallySafePath(path: string): boolean {\n  return !!path && !path.includes('\\0') && !path.includes('\\\\') && !path.startsWith('/') && !/^[a-zA-Z]:/.test(path)\n}","typeGuard":"const isSafeArchivePath = (path: string): boolean =>\n  !!path && !path.includes('\\0') && !path.includes('\\\\') && !path.startsWith('/') && !/^[a-zA-Z]:/.test(path) &&\n  !path.split('/').some((seg) => seg === '' || seg === '.' || seg === '..')","tryCatchPattern":"try {\n  assertSafeArchivePath(entry.name)\n} catch (error) {\n  if (/Unsafe ZIP entry path/.test((error as Error).message)) skipEntry(entry)\n  throw error\n}","preventionTips":["Normalize entry paths to relative forward-slash form before adding to an archive.","Reject archives whose entries use absolute or drive-rooted paths.","Never extract to a filesystem path derived directly from an untrusted entry name."],"tags":["zip","security","path-traversal","validation"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}