{"record":{"id":"0f2cb1055d382d60","repo":"nexu-io/open-design","slug":"absolute-zip-paths-are-not-allowed","errorCode":null,"errorMessage":"absolute zip paths are not allowed","messagePattern":"absolute zip paths are not allowed","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/daemon/src/design/claude-design-import.ts","lineNumber":259,"sourceCode":"  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\nfunction safeJoin(root: string, relPath: string): string {\n  const target = path.resolve(root, relPath);","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/design/claude-design-import.ts#L241-L277","documentation":"sanitizeZipPath rejects entry names that start with '/' (POSIX absolute) or match /^[A-Za-z]:/ (Windows drive letter). Absolute paths would let an entry write outside the project directory. Maps to HTTP 400.","triggerScenarios":"A zip entry like '/etc/foo' or 'C:\\Users\\foo' — either malicious or produced by a tool that stored absolute paths (e.g. `zip -y` preserving absolute paths).","commonSituations":"Archiving with absolute paths on Linux/macOS; cross-platform packaging that baked in Windows drive letters; an archive from an untrusted source attempting path injection.","solutions":["Re-create the zip with relative paths: `cd <dir> && zip -r export.zip .` (avoid `zip -y` with absolute paths).","Use a tool option that strips leading slashes / drive letters before archiving.","Inspect entry names with `unzip -l` and confirm none are absolute."],"exampleFix":"// before (stores absolute paths):\n//   zip -ry export.zip /home/me/design\n// after (relative paths):\n//   cd /home/me/design && zip -r export.zip .","handlingStrategy":"validation","validationCode":"// Reject archives with absolute entry paths before importing.\nimport { execFileSync } from 'node:child_process';\nfunction zipHasAbsolutePaths(file: string): boolean {\n  try {\n    const out = execFileSync('unzip', ['-l', file], { encoding: 'utf8' });\n    return /^[^/\\s]+\\s+\\d+\\s+[\\d-]+\\s+[\\d:]+\\s+(\\/|[A-Za-z]:)/m.test(out);\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await importClaudeDesignZip(zipPath, projectDir);\n} catch (err) {\n  if (String(err).includes('absolute zip paths')) {\n    return res.status(400).json({ error: 're-zip with relative entry paths' });\n  }\n  throw err;\n}","preventionTips":["Create zips with `cd <dir> && zip -r out.zip .` rather than `zip -ry` with absolute paths.","Inspect `unzip -l` for entries beginning with / or a drive letter.","Strip leading slashes in the archiving tool before exporting."],"tags":["zip","security","path-traversal","absolute-path","import"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}