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
- 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.
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
- 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.
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
- invalid zip file name
- path escapes project dir
- encrypted zip entries are not supported
- invalid brand id: ${input.brandId}
- invalid design system id: ${designSystemId}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/0f2cb1055d382d60.
Report an issue: GitHub.