nexu-io/open-design · error · Error

absolute zip paths are not allowed

Error message

absolute zip paths are not allowed

What it means

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.

Source

Thrown at apps/daemon/src/design/claude-design-import.ts:259

  if (bodyEnd > zip.length) throw new Error(`zip entry exceeds archive: ${entry.name}`);
  const compressed = zip.slice(bodyStart, bodyEnd);
  if (entry.method === 0) return Buffer.from(compressed);
  // A genuinely empty deflate payload would still occupy at least the BFINAL
  // marker; an entirely missing payload cannot be inflated, so treat it as
  // empty rather than handing a zero-length buffer to zlib.
  if (compressed.length === 0) return Buffer.alloc(0);
  // When the central directory advertises 0 (streaming zips with data
  // descriptors), fall back to the per-file ceiling so legitimate non-empty
  // payloads decode instead of being silently truncated. The post-decode
  // checks in the caller enforce MAX_FILE_BYTES and total-bytes limits.
  const cap = entry.uncompressedSize > 0 ? entry.uncompressedSize : MAX_FILE_BYTES;
  return inflateRawSync(compressed, { maxOutputLength: cap });
}

function sanitizeZipPath(name: string): string {
  if (name.includes('\0')) throw new Error('invalid zip file name');
  if (/^[A-Za-z]:/.test(name) || name.startsWith('/')) {
    throw new Error('absolute zip paths are not allowed');
  }
  return validateProjectPath(name);
}

function chooseEntryFile(paths: string[]): string | null {
  const html = paths.filter((p) => /\.html?$/i.test(p));
  if (html.length === 0) return null;
  const lower = new Map(html.map((p) => [p.toLowerCase(), p]));
  return (
    lower.get('index.html') ??
    html.find((p) => !p.includes('/')) ??
    html[0] ??
    null
  );
}

function safeJoin(root: string, relPath: string): string {
  const target = path.resolve(root, relPath);

View on GitHub (pinned to 5be4028344)

Solutions

  1. Re-create the zip with relative paths: `cd <dir> && zip -r export.zip .` (avoid `zip -y` with absolute paths).
  2. Use a tool option that strips leading slashes / drive letters before archiving.
  3. Inspect entry names with `unzip -l` and confirm none are absolute.

Example fix

// before (stores absolute paths):
//   zip -ry export.zip /home/me/design
// after (relative paths):
//   cd /home/me/design && zip -r export.zip .
Defensive patterns

Strategy: validation

Validate before calling

// Reject archives with absolute entry paths before importing.
import { execFileSync } from 'node:child_process';
function zipHasAbsolutePaths(file: string): boolean {
  try {
    const out = execFileSync('unzip', ['-l', file], { encoding: 'utf8' });
    return /^[^/\s]+\s+\d+\s+[\d-]+\s+[\d:]+\s+(\/|[A-Za-z]:)/m.test(out);
  } catch {
    return false;
  }
}

Try / catch

try {
  await importClaudeDesignZip(zipPath, projectDir);
} catch (err) {
  if (String(err).includes('absolute zip paths')) {
    return res.status(400).json({ error: 're-zip with relative entry paths' });
  }
  throw err;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/0f2cb1055d382d60. Report an issue: GitHub.