{"record":{"id":"c8011c1cdb375235","repo":"vercel-labs/skills","slug":"archive-extracts-to-more-than-limits-extractmaxb","errorCode":null,"errorMessage":"Archive extracts to more than ${limits.extractMaxBytes} bytes. Set SKILLS_EXTRACT_MAX_BYTES to override.","messagePattern":"Archive extracts to more than (.+?) bytes\\. Set SKILLS_EXTRACT_MAX_BYTES to override\\.","errorType":"validation","errorClass":"ArchiveValidationError","httpStatus":null,"severity":"error","filePath":"src/download-source.ts","lineNumber":74,"sourceCode":"function validateArchivePath(path: string): string | null {\n  const normalized = path.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n  if (!normalized || normalized.endsWith('/')) return normalized;\n  if (normalized.startsWith('/') || /^[a-zA-Z]:\\//.test(normalized)) return null;\n  if (normalized.split('/').includes('..')) return null;\n  return normalized;\n}\n\nfunction incrementEntry(state: ExtractState, size: number, limits: DownloadLimits): void {\n  state.entries += 1;\n  if (state.entries > limits.extractMaxFiles) {\n    throw new ArchiveValidationError(\n      `Archive contains too many files (${state.entries}). Maximum is ${limits.extractMaxFiles}. Set SKILLS_EXTRACT_MAX_FILES to override.`\n    );\n  }\n\n  state.bytes += size;\n  if (state.bytes > limits.extractMaxBytes) {\n    throw new ArchiveValidationError(\n      `Archive extracts to more than ${limits.extractMaxBytes} bytes. Set SKILLS_EXTRACT_MAX_BYTES to override.`\n    );\n  }\n}\n\nasync function downloadToFile(\n  url: string,\n  targetFile: string,\n  limits: DownloadLimits\n): Promise<void> {\n  const response = await fetch(url, {\n    signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n    redirect: 'follow',\n  });\n\n  if (!response.ok) {\n    throw new Error(`Download failed with HTTP ${response.status}`);\n  }","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/download-source.ts#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"Skill archives that bundle large assets, CI environments with tight limits, or a deliberately malicious archive downloaded from an untrusted URL.","solutions":["Check the archive's uncompressed size first: unzip -l file.zip","If legitimate, raise the budget: SKILLS_EXTRACT_MAX_BYTES=1073741824 skills add <source>","Slim the archive (remove large assets, .git, node_modules) and re-package","For large content, install from a git repo or local directory instead of a zip"],"exampleFix":"# before\nskills add ./big-skills.zip  # extracts to more than max bytes\n\n# after\nSKILLS_EXTRACT_MAX_BYTES=536870912 skills add ./big-skills.zip","handlingStrategy":"validation","validationCode":"// Sum declared uncompressed sizes before extracting\nimport { execFileSync } from 'node:child_process';\nfunction zipUncompressedBytes(path: string): number {\n  const out = execFileSync('unzip', ['-l', path], { encoding: 'utf8' });\n  const m = out.match(/\\s([0-9,]+)\\s+\\d+ files?/);\n  return m ? Number(m[1].replace(/,/g, '')) : -1;\n}","typeGuard":"null","tryCatchPattern":"try {\n  await extractArchive(file, limits);\n} catch (err) {\n  if (err instanceof ArchiveValidationError && err.message.includes('extractMaxBytes')) {\n    // abort: archive expands beyond budget — reject rather than raise\n  } else throw err;\n}","preventionTips":["Keep large binaries out of skill archives","Set SKILLS_EXTRACT_MAX_BYTES explicitly in automation so failures are deterministic"],"tags":["zip","limits","zip-bomb","disk-usage","env-var"],"backgroundTag":"resource-limit-exceeded","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}