paperclipai/paperclip · error · Error

Unsupported zip archive: decompressed contents exceed the

Error message

Unsupported zip archive: decompressed contents exceed the ${maxTotalDecompressedBytes}-byte limit.

What it means

Decompression-bomb guard inside readZipArchive: an entry's decompressed output pushes the running total past the configured maxTotalDecompressedBytes cap. Fires when a zip (malicious or accidental) expands beyond the import budget, protecting memory before the whole archive is materialized.

Solutions

  1. Reduce total decompressed size under the limit, or extract in smaller archives.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at packages/shared/src/portability-zip.ts:265 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/f67eb345f7831281. Report an issue: GitHub.

Appendix: source

Thrown at packages/shared/src/portability-zip.ts:265

      throw new Error("Unsupported zip archive: data descriptors are not supported.");
    }

    const nameOffset = offset + 30;
    const bodyOffset = nameOffset + fileNameLength + extraFieldLength;
    const bodyEnd = bodyOffset + compressedSize;
    if (bodyEnd > bytes.length) {
      throw new Error("Invalid zip archive: truncated file contents.");
    }

    localHeaderCount += 1;
    const rawArchivePath = textDecoder.decode(bytes.slice(nameOffset, nameOffset + fileNameLength));
    const archivePath = normalizeArchivePath(rawArchivePath);
    const isDirectoryEntry = /\/$/.test(rawArchivePath.replace(/\\/g, "/"));
    if (archivePath && !isDirectoryEntry) {
      const entryBytes = inflateZipEntry(compressionMethod, bytes.slice(bodyOffset, bodyEnd), maxEntryDecompressedBytes);
      totalDecompressedBytes += entryBytes.length;
      if (totalDecompressedBytes > maxTotalDecompressedBytes) {
        throw new Error(
          `Unsupported zip archive: decompressed contents exceed the ${maxTotalDecompressedBytes}-byte limit.`,
        );
      }
      entries.push({
        path: archivePath,
        body: bytesToPortableFileEntry(archivePath, entryBytes),
      });
    }

    offset = bodyEnd;
  }

  // A complete archive always ends with a central directory after its local
  // entries. If the scan ran off the end of the buffer without reaching one, the
  // upload was truncated at a record boundary — fail closed rather than import a
  // leading fragment. Then fully validate the central directory the EOCD points
  // at so a truncated tail with a forged EOCD (whose count happens to match the
  // surviving entries) cannot smuggle in a partial import.

View on GitHub (pinned to 120ae5428f)