{"record":{"id":"91506a572ec75963","repo":"vercel-labs/skills","slug":"archive-contains-too-many-files","errorCode":null,"errorMessage":"Archive contains too many files","messagePattern":"Archive contains too many files","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/providers/wellknown.ts","lineNumber":710,"sourceCode":"\n    return parts.join('/');\n  }\n\n  private addArchiveFile(\n    files: Map<string, WellKnownFileContent>,\n    path: string,\n    content: Uint8Array,\n    runningTotal: { bytes: number }\n  ) {\n    const normalizedPath = this.normalizeArchivePath(path);\n    if (!normalizedPath) throw new Error(`Unsafe archive path: ${path}`);\n\n    runningTotal.bytes += content.byteLength;\n    if (runningTotal.bytes > MAX_ARCHIVE_UNPACKED_BYTES) {\n      throw new Error('Archive exceeds maximum unpacked size');\n    }\n    if (files.size >= MAX_ARCHIVE_FILES) {\n      throw new Error('Archive contains too many files');\n    }\n\n    files.set(normalizedPath, content);\n  }\n\n  private extractTarGz(bytes: Uint8Array): Map<string, WellKnownFileContent> {\n    const tar = gunzipSync(Buffer.from(bytes));\n    const files = new Map<string, WellKnownFileContent>();\n    const runningTotal = { bytes: 0 };\n    let offset = 0;\n\n    while (offset + 512 <= tar.length) {\n      const header = tar.subarray(offset, offset + 512);\n      if (header.every((byte) => byte === 0)) break;\n\n      const name = this.readTarString(header, 0, 100);\n      const sizeText = this.readTarString(header, 124, 12).trim();\n      const typeFlag = header[156];","sourceCodeStart":692,"sourceCodeEnd":728,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/providers/wellknown.ts#L692-L728","documentation":"A second extraction cap: the provider allows at most MAX_ARCHIVE_FILES entries. When files.size reaches the cap and another file entry arrives, 'Archive contains too many files' is thrown, preventing file-count bombs.","triggerScenarios":"A registry archive whose entry count exceeds MAX_ARCHIVE_FILES — e.g. a skill bundling node_modules, .git objects, or generated file trees with tens of thousands of entries.","commonSituations":"Packaging mistakes that forget to exclude .git/node_modules; malicious archives padded with empty files; build outputs accidentally included.","solutions":["Repack excluding junk: tar -czf skill.tar.gz --exclude=.git --exclude=node_modules .","Audit entry count locally: tar -tf artifact.tar.gz | wc -l","Keep the artifact to SKILL.md plus a small set of reference files","Verify you're fetching the intended artifact, not a whole-repo snapshot"],"exampleFix":"# before\ntar -czf skill.tar.gz .            # includes .git and node_modules\n# after\ntar -czf skill.tar.gz --exclude=.git --exclude=node_modules .","handlingStrategy":"try-catch","validationCode":"const entryCount = await countArchiveEntries(artifactUrl); // e.g. stream tar headers\nif (entryCount > 5000) throw new Error(`Artifact has ${entryCount} entries; too many`);","typeGuard":"function isTooManyFiles(e: unknown): e is Error {\n  return e instanceof Error && /too many files/i.test(e.message);\n}","tryCatchPattern":"try { await provider.fetchArtifact(url); }\ncatch (e) {\n  if (isTooManyFiles(e)) { logger.warn(`Skipping bloated artifact ${url}`); return null; }\n  throw e;\n}","preventionTips":["Exclude .git, node_modules, dist from packaged skills","Run tar -tf | wc -l as a packaging CI check","Keep artifacts limited to SKILL.md plus essential refs"],"tags":["security","limits","archive","file-count","wellknown-provider"],"backgroundTag":"archive-entry-count-limit","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}