affaan-m/ECC · error · Error
Unsafe Nasiko archive: missing complete tar terminator.
Error message
Unsafe Nasiko archive: missing complete tar terminator.
What it means
A valid tar ends with at least two consecutive 512-byte blocks of zeros. extractQualifiedTarGzip tracks whether such a terminator block was seen during its scan; if the archive runs out of bytes without encountering one, it throws this error. It is the structural-integrity check ensuring the archive was not cut short mid-entry-stream.
Solutions
- Re-download the archive and verify its byte length/checksum before extracting.
- Use a full-featured tar writer (GNU tar, bsdtar) to repack if you control the archive.
- Retry the download with resume/verification enabled.
- Compare with a known-good copy of the same release artifact.
Example fix
// before const buf = fs.readFileSync(cachePath); // cache write was interrupted extractQualifiedTarGzip(buf); // after const buf = fs.readFileSync(cachePath); if (sha256(buf) !== expectedDigest) fs.rmSync(cachePath); // evict corrupt cache extractQualifiedTarGzip(sha256(buf) === expectedDigest ? buf : await downloadFresh(url));
Defensive patterns
Strategy: validation
Validate before calling
if (archiveBuffer.length !== manifestSize) throw new Error('Downloaded archive size mismatch; refusing extraction.'); Try / catch
try { return extractQualifiedTarGzip(tar); } catch (err) { if (err.message.includes('missing complete tar terminator')) { evictCache(entry); return downloadFresh(url); } throw err; } Prevention
- Verify total byte count and checksum of downloads before caching
- Use packers that emit the standard 1024-byte end-of-archive marker
- Enable retry with resume on flaky networks
- Write archives to a temp file and atomically rename only after full verification
When it happens
Trigger: Calling extractQualifiedTarGzip with a decompressed tar that ends immediately after a file entry (or mid-entry) without the required 1024 zero bytes, so the parsing loop exits with terminated === false.
Common situations: Truncated download (network drop, interrupted curl/wget); tarball produced by a packer that omits the end-of-archive marker; corrupted cache after a disk-full write; CDN serving a partial body.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Nasiko archive is truncated.
- Unsafe Nasiko archive: incomplete terminator or nonzero…
- Unsafe Nasiko archive: truncated tar header.
- Unsafe Nasiko archive: expected exactly one bounded regular…
- Unsafe Nasiko archive: invalid tar size field.
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/0c04ea01a05587fa.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/nasiko-release.js:133
const type = String.fromCharCode(header[156] || 48);
const size = readTarOctal(header, 124, 12);
const start = offset + 512;
const end = start + size;
const paddedEnd = start + Math.ceil(size / 512) * 512;
if (!Number.isSafeInteger(end) || paddedEnd > tar.length) throw new Error('Nasiko archive is truncated.');
const payload = tar.subarray(start, end);
if (!tar.subarray(end, paddedEnd).every(byte => byte === 0)) {
throw new Error('Unsafe Nasiko archive: nonzero tar padding.');
}
const isBinary = !prefix && name === expectedName && (type === '0' || type === '\0');
const isAppleDouble = !prefix && name === `._${expectedName}` && type === '0' && size <= 1024 * 1024;
const isPaxMetadata = !prefix && name === `PaxHeader/${expectedName}` && type === 'x' && size <= 64 * 1024
&& !/(?:^|\n)(?:path|linkpath)=/i.test(payload.toString('utf8'));
if (isBinary && !binary && size > 0 && size <= MAX_BINARY_BYTES) binary = Buffer.from(payload);
else if (!isAppleDouble && !isPaxMetadata) throw new Error('Unsafe Nasiko archive: expected exactly one bounded regular binary file.');
offset = paddedEnd;
}
if (!terminated) throw new Error('Unsafe Nasiko archive: missing complete tar terminator.');
if (!binary) throw new Error('Unsafe Nasiko archive: expected exactly one bounded regular binary file.');
return binary;
}
function fetchBytes(url, options = {}) {
const parsed = new URL(url);
if (parsed.origin !== REGISTRY_ORIGIN || parsed.protocol !== 'https:') return Promise.reject(new Error('Nasiko download origin is not allowed.'));
const maxBytes = options.maxBytes || MAX_ARCHIVE_BYTES;
return new Promise((resolve, reject) => {
const request = https.get(parsed, { headers: options.accept ? { Accept: options.accept } : {} }, response => {
if (response.statusCode >= 300 && response.statusCode < 400) { response.resume(); reject(new Error('Nasiko registry redirects are not allowed.')); return; }
if (response.statusCode !== 200) { response.resume(); reject(new Error(`Nasiko registry returned HTTP ${response.statusCode}.`)); return; }
const chunks = [];
let total = 0;
response.on('data', chunk => {
total += chunk.length;
if (total > maxBytes) request.destroy(new Error('Nasiko registry response exceeded the size limit.'));
else chunks.push(chunk);View on GitHub (pinned to 8321021c54)