{"record":{"id":"3df7fcf993c44e22","repo":"affaan-m/ECC","slug":"unsafe-nasiko-archive-incomplete-terminator-or-nonzero","errorCode":null,"errorMessage":"Unsafe Nasiko archive: incomplete terminator or nonzero trailing data.","messagePattern":"Unsafe Nasiko archive: incomplete terminator or nonzero trailing data\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/nasiko-release.js","lineNumber":108,"sourceCode":"\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;\n    const end = start + size;\n    const paddedEnd = start + Math.ceil(size / 512) * 512;\n    if (!Number.isSafeInteger(end) || paddedEnd > tar.length) throw new Error('Nasiko archive is truncated.');\n    const payload = tar.subarray(start, end);\n    if (!tar.subarray(end, paddedEnd).every(byte => byte === 0)) {\n      throw new Error('Unsafe Nasiko archive: nonzero tar padding.');\n    }\n    const isBinary = !prefix && name === expectedName && (type === '0' || type === '\\0');\n    const isAppleDouble = !prefix && name === `._${expectedName}` && type === '0' && size <= 1024 * 1024;","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/nasiko-release.js#L90-L126","documentation":"extractQualifiedTarGzip validates that a gunzipped Nasiko tar archive ends with the required two-block (1024-byte) zero terminator and that no nonzero bytes follow the first terminator block. This guards against truncated or tampered archives. If the terminator is incomplete or trailing data is nonzero, the archive is rejected as unsafe.","triggerScenarios":"Calling extractQualifiedTarGzip (or any higher-level download/verify flow that uses it) with an archive whose gzip payload is cut off before 1024 trailing zero bytes, or that has appended bytes after the first 512-byte zero block of the terminator.","commonSituations":"Partially downloaded tarballs from a flaky network or interrupted fetch; corrupted cache files; archives rebuilt or repacked by tools that append junk; a truncated upload in a mirror.","solutions":["Re-download the archive from the registry and verify again.","Check that the download completed fully (compare Content-Length / checksum) before extraction.","Ensure the gunzip step produced the complete tar; retry with a fresh connection.","Report the artifact to the registry if the published tarball is corrupted."],"exampleFix":"// before\nconst tar = gunzipSync(downloadedBuffer); // possibly truncated\ncleanup(tar);\n// after\nconst expected = await fetchContentLength(url);\nif (downloadedBuffer.length !== expected) throw new Error('Incomplete download; refetch before extracting.');\nconst tar = gunzipSync(downloadedBuffer);\ncleanup(tar);","handlingStrategy":"try-catch","validationCode":"if (!downloadedBuffer || downloadedBuffer.length < expectedSize) throw new Error('Incomplete archive before extraction.');","typeGuard":"const isCompleteTar = (buf) => Buffer.isBuffer(buf) && buf.length >= 1024 && buf.subarray(buf.length - 1024).every(b => b === 0);","tryCatchPattern":"try { const bin = await nasiko.downloadAndExtract(url, name); } catch (err) { if (err.message.includes('Unsafe Nasiko archive')) { await refetchAndVerifyChecksum(url); } else { throw err; } }","preventionTips":["Always verify the published checksum/size of the download before extraction","Use resumable, verified downloads (Range + Content-Length) for large artifacts","Never append data to tarballs after packing","Cache-evict any archive whose byte length does not match the manifest"],"tags":["archive","tar","validation","integrity"],"backgroundTag":"checksum-mismatch","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"}