{"record":{"id":"d9213f793f886ca5","repo":"vercel-labs/skills","slug":"archive-links-are-not-supported-d9213f","errorCode":null,"errorMessage":"Archive links are not supported","messagePattern":"Archive links are not supported","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/providers/wellknown.ts","lineNumber":738,"sourceCode":"    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];\n      const prefix = this.readTarString(header, 345, 155);\n      const path = prefix ? `${prefix}/${name}` : name;\n      const size = Number.parseInt(sizeText || '0', 8);\n\n      if (!Number.isFinite(size) || size < 0) throw new Error('Invalid tar entry size');\n      offset += 512;\n\n      // Reject symlinks and hard links. Skip directories and metadata entries.\n      if (typeFlag === 0x32 || typeFlag === 0x31) {\n        throw new Error('Archive links are not supported');\n      }\n\n      const isFile = typeFlag === 0 || typeFlag === 0x30;\n      if (isFile) {\n        const content = tar.subarray(offset, offset + size);\n        this.addArchiveFile(files, path, new Uint8Array(content), runningTotal);\n      }\n\n      offset += Math.ceil(size / 512) * 512;\n    }\n\n    if (!files.has('SKILL.md')) throw new Error('Archive missing root SKILL.md');\n    return files;\n  }\n\n  private readTarString(buffer: Uint8Array, offset: number, length: number): string {\n    const slice = buffer.subarray(offset, offset + length);\n    const nul = slice.indexOf(0);","sourceCodeStart":720,"sourceCodeEnd":756,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/providers/wellknown.ts#L720-L756","documentation":"extractTarGz rejects tar entries whose type flag is a symlink (0x32, '2') or hard link (0x31, '1'). Link entries could point outside the archive, so extraction refuses them outright.","triggerScenarios":"A registry artifact containing symlink/hardlink entries — common when archiving a directory that itself contains symlinks (e.g. node_modules/.bin, or a SKILL.md symlinked to docs) without dereferencing.","commonSituations":"macOS/Linux skill repos with symlinks committed; packaging with tar default settings that preserve links instead of dereferencing them.","solutions":["Rebuild the archive dereferencing links: tar -czhf skill.tar.gz . (the -h flag follows symlinks) or use bsdtar --format zip","Replace symlinks with real files or tiny stub files in the source repo","Check for accidental symlinks: find . -type l","Re-publish the fixed artifact to the registry"],"exampleFix":"# before\ntar -czf skill.tar.gz .\n# after\ntar -czhf skill.tar.gz .   # -h dereferences symlinks into real files","handlingStrategy":"validation","validationCode":"import { execSync } from 'node:child_process';\nconst links = execSync(`tar -tvf ${file} | grep -E '^[hl]' || true`).toString().trim();\nif (links) throw new Error(`Archive contains link entries:\\n${links}`);","typeGuard":"function isArchiveLinkError(e: unknown): e is Error {\n  return e instanceof Error && /links are not supported/i.test(e.message);\n}","tryCatchPattern":"try { await provider.fetchArtifact(url); }\ncatch (e) {\n  if (isArchiveLinkError(e)) {\n    notifyPublisher('Repack with tar -czhf (dereference symlinks)');\n    return null;\n  }\n  throw e;\n}","preventionTips":["Package with tar -h or bsdtar to dereference symlinks","Audit repos for committed symlinks: find . -type l","Never repackage archives preserving link entries for this provider"],"tags":["archive","tar","symlink","security","wellknown-provider"],"backgroundTag":"archive-symlink-rejected","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}