chatboxai/chatbox · critical

Unsafe ZIP entry path: ${path}

Error message

Unsafe ZIP entry path: ${path}

What it means

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.

Source

Thrown at src/renderer/packages/backup/zip.ts:41

  maxTotalUncompressedBytes?: number
  maxCompressionRatio?: number
}

export interface ZipReadOptions {
  limits?: ZipReadLimits
  entryLimits?: (path: string) => Partial<Pick<ZipReadLimits, 'maxEntryUncompressedBytes' | 'maxCompressionRatio'>>
  signal?: AbortSignal
}

function throwIfAborted(signal?: AbortSignal) {
  if (signal?.aborted) {
    throw signal.reason instanceof Error ? signal.reason : new DOMException('Operation canceled', 'AbortError')
  }
}

export function assertSafeArchivePath(path: string): void {
  if (!path || path.includes('\0') || path.includes('\\') || path.startsWith('/') || /^[a-zA-Z]:/.test(path)) {
    throw new Error(`Unsafe ZIP entry path: ${path}`)
  }
  const segments = path.split('/')
  if (segments.some((segment) => segment === '' || segment === '.' || segment === '..')) {
    throw new Error(`Unsafe ZIP entry path: ${path}`)
  }
}

function* splitBytes(bytes: Uint8Array): Generator<Uint8Array> {
  for (let offset = 0; offset < bytes.length; offset += ZIP_INPUT_CHUNK_SIZE) {
    yield bytes.subarray(offset, Math.min(offset + ZIP_INPUT_CHUNK_SIZE, bytes.length))
  }
}

function toChunks(data: ZipArchiveEntry['data']): Iterable<Uint8Array> | AsyncIterable<Uint8Array> {
  return data instanceof Uint8Array ? splitBytes(data) : data
}

export async function* createZipStream(

View on GitHub (pinned to 81571269ad)

Solutions

  1. Reject the archive; do not attempt to extract paths that fail this guard.
  2. When producing archives, normalize entry paths to relative forward-slash form before adding.
  3. Sanitize entry names: strip leading slashes, convert backslashes to '/', reject empty names.

Example fix

// before
zip.add(new ZipDeflate(entry.path, { level: 6 }))

// after
const safe = entry.path.replace(/\\/g, '/').replace(/^\/+/, '')
if (!safe || /\0/.test(safe) || /^[a-zA-Z]:/.test(safe)) {
  throw new Error(`Refusing unsafe path: ${entry.path}`)
}
zip.add(new ZipDeflate(safe, { level: 6 }))
Defensive patterns

Strategy: validation

Validate before calling

function isLexicallySafePath(path: string): boolean {
  return !!path && !path.includes('\0') && !path.includes('\\') && !path.startsWith('/') && !/^[a-zA-Z]:/.test(path)
}

Type guard

const isSafeArchivePath = (path: string): boolean =>
  !!path && !path.includes('\0') && !path.includes('\\') && !path.startsWith('/') && !/^[a-zA-Z]:/.test(path) &&
  !path.split('/').some((seg) => seg === '' || seg === '.' || seg === '..')

Try / catch

try {
  assertSafeArchivePath(entry.name)
} catch (error) {
  if (/Unsafe ZIP entry path/.test((error as Error).message)) skipEntry(entry)
  throw error
}

Prevention

When it happens

Trigger: A ZIP entry name is '', contains '\\0' or '\\', starts with '/', or matches /^[a-zA-Z]:/.

Common situations: Malicious or malformed archive with absolute entry paths; archive created on Windows with backslash separators; an entry crafted for path traversal.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/4da49c3c598b1e4b. Report an issue: GitHub.