{"record":{"id":"ec3f5824099b7dcf","repo":"nexu-io/open-design","slug":"invalid-zip-file-name","errorCode":null,"errorMessage":"invalid zip file name","messagePattern":"invalid zip file name","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/daemon/src/design/claude-design-import.ts","lineNumber":257,"sourceCode":"  const bodyStart = offset + 30 + nameLen + extraLen;\n  const bodyEnd = bodyStart + entry.compressedSize;\n  if (bodyEnd > zip.length) throw new Error(`zip entry exceeds archive: ${entry.name}`);\n  const compressed = zip.slice(bodyStart, bodyEnd);\n  if (entry.method === 0) return Buffer.from(compressed);\n  // A genuinely empty deflate payload would still occupy at least the BFINAL\n  // marker; an entirely missing payload cannot be inflated, so treat it as\n  // empty rather than handing a zero-length buffer to zlib.\n  if (compressed.length === 0) return Buffer.alloc(0);\n  // When the central directory advertises 0 (streaming zips with data\n  // descriptors), fall back to the per-file ceiling so legitimate non-empty\n  // payloads decode instead of being silently truncated. The post-decode\n  // checks in the caller enforce MAX_FILE_BYTES and total-bytes limits.\n  const cap = entry.uncompressedSize > 0 ? entry.uncompressedSize : MAX_FILE_BYTES;\n  return inflateRawSync(compressed, { maxOutputLength: cap });\n}\n\nfunction sanitizeZipPath(name: string): string {\n  if (name.includes('\\0')) throw new Error('invalid zip file name');\n  if (/^[A-Za-z]:/.test(name) || name.startsWith('/')) {\n    throw new Error('absolute zip paths are not allowed');\n  }\n  return validateProjectPath(name);\n}\n\nfunction chooseEntryFile(paths: string[]): string | null {\n  const html = paths.filter((p) => /\\.html?$/i.test(p));\n  if (html.length === 0) return null;\n  const lower = new Map(html.map((p) => [p.toLowerCase(), p]));\n  return (\n    lower.get('index.html') ??\n    html.find((p) => !p.includes('/')) ??\n    html[0] ??\n    null\n  );\n}\n","sourceCodeStart":239,"sourceCodeEnd":275,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/design/claude-design-import.ts#L239-L275","documentation":"sanitizeZipPath rejects any entry name containing a null byte (\\0). Null bytes can terminate strings early in C-based path handlers and are a classic path-injection vector. This guard fires before validateProjectPath. Maps to HTTP 400.","triggerScenarios":"A zip entry whose name field embeds \\0 — typically a maliciously crafted archive (e.g. 'safe.txt\\0../../etc/passwd') or one produced by a buggy encoder.","commonSituations":"Security testing / fuzzing; an archive received from an untrusted source; rare encoder bugs that leak null bytes into name fields.","solutions":["Do not import untrusted archives.","Inspect the zip with `unzip -l` for suspicious/garbled entry names.","Re-create the archive from trusted source files with a standard tool."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Reject archives whose entry names contain null bytes.\nimport { execFileSync } from 'node:child_process';\nfunction zipHasNullByteNames(file: string): boolean {\n  try {\n    const out = execFileSync('unzip', ['-l', file], { encoding: 'utf8' });\n    return out.includes('\\0');\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await importClaudeDesignZip(zipPath, projectDir);\n} catch (err) {\n  if (String(err).includes('invalid zip file name')) {\n    return res.status(400).json({ error: 'zip contains an invalid entry name' });\n  }\n  throw err;\n}","preventionTips":["Never import archives from untrusted sources.","Inspect `unzip -l` output for garbled or truncated names.","Re-create archives from trusted source files only."],"tags":["zip","security","path-traversal","null-byte","import"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}