{"record":{"id":"8240a1d2cabe9cf6","repo":"affaan-m/ECC","slug":"unsafe-nasiko-archive-truncated-tar-header","errorCode":null,"errorMessage":"Unsafe Nasiko archive: truncated tar header.","messagePattern":"Unsafe Nasiko archive: truncated tar header\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/nasiko-release.js","lineNumber":99,"sourceCode":"  const field = block.subarray(offset, offset + length).toString('ascii');\n  const match = /^ *([0-7]+)[ \\0]*$/.exec(field);\n  if (!match) throw new Error('Unsafe Nasiko archive: invalid tar size field.');\n  const size = Number.parseInt(match[1], 8);\n  if (!Number.isSafeInteger(size) || size < 0) {\n    throw new Error('Unsafe Nasiko archive: invalid tar size field.');\n  }\n  return size;\n}\n\nfunction extractQualifiedTarGzip(archiveBytes, expectedName) {\n  let tar;\n  try { tar = zlib.gunzipSync(archiveBytes, { maxOutputLength: MAX_BINARY_BYTES + 2048 }); }\n  catch (_error) { throw new Error('Nasiko archive is invalid or exceeds the decompressed size limit.'); }\n  let offset = 0;\n  let binary = null;\n  let terminated = false;\n  while (offset < tar.length) {\n    if (offset + 512 > tar.length) throw new Error('Unsafe Nasiko archive: truncated tar header.');\n    const header = tar.subarray(offset, offset + 512);\n    if (header.every(byte => byte === 0)) {\n      const terminatorEnd = offset + 1024;\n      if (\n        terminatorEnd > tar.length\n        || !tar.subarray(offset + 512, terminatorEnd).every(byte => byte === 0)\n        || !tar.subarray(terminatorEnd).every(byte => byte === 0)\n      ) {\n        throw new Error('Unsafe Nasiko archive: incomplete terminator or nonzero trailing data.');\n      }\n      terminated = true;\n      break;\n    }\n    const name = readTarString(header, 0, 100);\n    const prefix = readTarString(header, 345, 155);\n    const type = String.fromCharCode(header[156] || 48);\n    const size = readTarOctal(header, 124, 12);\n    const start = offset + 512;","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/nasiko-release.js#L81-L117","documentation":"While walking the gunzipped tar stream in 512-byte blocks, extractQualifiedTarGzip throws this error when fewer than 512 bytes remain before a header could be read — the tar stream ends mid-header. This is the library's truncation guard: a well-formed tar always ends with two zero blocks, never with a partial header.","triggerScenarios":"The decompressed tar length is not a multiple of 512 such that the final partial block begins a header (offset + 512 > tar.length) — e.g. the gzip stream was truncated before transfer completed, or the file was concatenated/cut.","commonSituations":"Interrupted downloads that gzip-decompress 'successfully' up to the cut point, storage corruption on a cached artifact, or a publishing pipeline that wrote a partial tar before gzipping.","solutions":["Re-download the artifact and retry — a tar ending mid-header almost always means a truncated transfer.","Compare the artifact's sha256 against the manifest digest; if it matches yet the tar is truncated, the publisher shipped a bad artifact.","Check the artifact file size on disk versus the Content-Length / declared layer.size from the manifest.","Clear local caches of the artifact to rule out a partially written cache entry."],"exampleFix":"// before\nextractQualifiedTarGzip(fs.readFileSync('artifact.gz'), 'bin'); // truncated tar header\n// after\nconst manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));\nconst bytes = fs.readFileSync('artifact.gz');\nif (bytes.length !== manifest.layers[0].size) {\n  throw new Error(`truncated download: ${bytes.length} of ${manifest.layers[0].size} bytes — refetch`);\n}\nassertDigest(bytes, manifest.layers[0].digest, 'artifact');\nextractQualifiedTarGzip(bytes, 'bin');","handlingStrategy":"validation","validationCode":"if (bytes.length !== declaredLayerSize) throw new Error(`incomplete artifact: ${bytes.length}/${declaredLayerSize} bytes`);\nassertDigest(bytes, declaredDigest, 'artifact');","typeGuard":"null","tryCatchPattern":"try { installNasiko(opts); } catch (e) { if (e.message.includes('truncated tar header')) { await refetchArtifact(); return installNasiko(opts); } throw e; }","preventionTips":["Compare downloaded byte count with the manifest's declared size immediately after fetch.","Verify sha256 before install to catch truncation with a clearer error.","Avoid reading artifacts from flaky caches; re-download on any length mismatch."],"tags":["tar","truncation","archive"],"backgroundTag":"unexpected-response-shape","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}