vercel-labs/skills · error · ArchiveValidationError

Archive extracts to more than ${limits.extractMaxBytes} byte

Error message

Archive extracts to more than ${limits.extractMaxBytes} bytes. Set SKILLS_EXTRACT_MAX_BYTES to override.

What it means

incrementEntry accumulates each entry's size into state.bytes and throws ArchiveValidationError when the total exceeds limits.extractMaxBytes. It prevents an archive from expanding to an unbounded number of bytes on disk (the classic decompression-bomb scenario). Override the cap with SKILLS_EXTRACT_MAX_BYTES.

Source

Thrown at src/download-source.ts:74

function validateArchivePath(path: string): string | null {
  const normalized = path.replace(/\\/g, '/').replace(/^\.\//, '');
  if (!normalized || normalized.endsWith('/')) return normalized;
  if (normalized.startsWith('/') || /^[a-zA-Z]:\//.test(normalized)) return null;
  if (normalized.split('/').includes('..')) return null;
  return normalized;
}

function incrementEntry(state: ExtractState, size: number, limits: DownloadLimits): void {
  state.entries += 1;
  if (state.entries > limits.extractMaxFiles) {
    throw new ArchiveValidationError(
      `Archive contains too many files (${state.entries}). Maximum is ${limits.extractMaxFiles}. Set SKILLS_EXTRACT_MAX_FILES to override.`
    );
  }

  state.bytes += size;
  if (state.bytes > limits.extractMaxBytes) {
    throw new ArchiveValidationError(
      `Archive extracts to more than ${limits.extractMaxBytes} bytes. Set SKILLS_EXTRACT_MAX_BYTES to override.`
    );
  }
}

async function downloadToFile(
  url: string,
  targetFile: string,
  limits: DownloadLimits
): Promise<void> {
  const response = await fetch(url, {
    signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
    redirect: 'follow',
  });

  if (!response.ok) {
    throw new Error(`Download failed with HTTP ${response.status}`);
  }

View on GitHub (pinned to 435076e789)

Solutions

  1. Check the archive's uncompressed size first: unzip -l file.zip
  2. If legitimate, raise the budget: SKILLS_EXTRACT_MAX_BYTES=1073741824 skills add <source>
  3. Slim the archive (remove large assets, .git, node_modules) and re-package
  4. For large content, install from a git repo or local directory instead of a zip

Example fix

# before
skills add ./big-skills.zip  # extracts to more than max bytes

# after
SKILLS_EXTRACT_MAX_BYTES=536870912 skills add ./big-skills.zip
Defensive patterns

Strategy: validation

Validate before calling

// Sum declared uncompressed sizes before extracting
import { execFileSync } from 'node:child_process';
function zipUncompressedBytes(path: string): number {
  const out = execFileSync('unzip', ['-l', path], { encoding: 'utf8' });
  const m = out.match(/\s([0-9,]+)\s+\d+ files?/);
  return m ? Number(m[1].replace(/,/g, '')) : -1;
}

Type guard

null

Try / catch

try {
  await extractArchive(file, limits);
} catch (err) {
  if (err instanceof ArchiveValidationError && err.message.includes('extractMaxBytes')) {
    // abort: archive expands beyond budget — reject rather than raise
  } else throw err;
}

Prevention

When it happens

Trigger: Extracting an archive whose declared uncompressed total exceeds the configured byte budget — highly compressed payloads (zip bombs) or legitimately large bundles such as binaries, images, or model files inside a skill archive.

Common situations: Skill archives that bundle large assets, CI environments with tight limits, or a deliberately malicious archive downloaded from an untrusted URL.

Related errors


AI-assisted analysis of vercel-labs/skills@435076e789 (2026-08-28). Data as JSON: /api/errors/c8011c1cdb375235. Report an issue: GitHub.