affaan-m/ECC · critical
Unsafe Nasiko archive: expected exactly one bounded regular
Error message
Unsafe Nasiko archive: expected exactly one bounded regular binary file.
What it means
extractQualifiedTarGzip enforces a strict allowlist while walking the tar: each entry must be the expected regular binary file (no path prefix, type '0' or NUL, size 1..64 MiB, at most once), or one of two tolerated metadata entries - a macOS AppleDouble '._name' file (<=1 MiB) or a bounded PaxHeader entry (type 'x', <=64 KiB, containing no path/linkpath overrides). Any other entry - extra files, directories, symlinks, hardlinks, devices, or Pax records that would relocate the binary - aborts extraction.
Source
Thrown at scripts/lib/nasiko-release.js:103
let binary = null;
while (offset + 512 <= tar.length) {
const header = tar.subarray(offset, offset + 512);
if (header.every(byte => byte === 0)) break;
const name = readTarString(header, 0, 100);
const prefix = readTarString(header, 345, 155);
const type = String.fromCharCode(header[156] || 48);
const rawSize = readTarString(header, 124, 12).trim();
const size = Number.parseInt(rawSize || '0', 8);
const start = offset + 512;
const end = start + size;
if (!Number.isSafeInteger(size) || size < 0 || end > tar.length) throw new Error('Nasiko archive is truncated.');
const payload = tar.subarray(start, end);
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 = start + Math.ceil(size / 512) * 512;
}
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;View on GitHub (pinned to 06c5e118c4)
Solutions
- Repack the release archive with exactly one regular file named nasiko (or nasiko.exe) and nothing else
- Strip extra entries from the tar (for example `tar --delete` or rebuild with `tar -cf archive nasiko`)
- Remove path/linkpath records from PaxHeaders in the packaging pipeline
- Treat unexpected entries in a digest-pinned archive as a security incident and report it upstream
Defensive patterns
Strategy: try-catch
Try / catch
try {
await installNasiko({ version: 'v0.1.0' });
} catch (error) {
if (/Unsafe Nasiko archive/.test(String(error.message))) {
// Fail closed. Never loosen the entry allowlist. Inspect the archive with
// `tar -tvf` to find the offending entry and fix the packaging (exactly one
// regular binary, no symlinks/extra files/path-rewriting Pax records).
}
throw error;
} Prevention
- Build release archives with `tar -cf archive nasiko` and nothing else in the tree
- Never allow path/linkpath Pax records in packaged artifacts
- Treat any unexpected entry under a pinned digest as a security incident, not a bug to patch around
When it happens
Trigger: An entry in the qualified archive is not the expected binary and not whitelisted metadata: name !== expectedName (or a ustar prefix is set), type is '5' (directory), '1'/'2' (hard/symlink), or a second regular file appears; or a PaxHeader entry contains 'path=' / 'linkpath=' records that could rename or redirect the extracted file.
Common situations: Repackaging scripts that add a LICENSE or README into the archive; macOS builds leaking extra AppleDouble files beyond the bounded ones; upstream changing packaging to include directories; adversarial archives attempting path traversal or binary substitution via Pax path records.
Related errors
- Path traversal rejected: ${relPath}
- Path traversal rejected: ${relPath}
- Refusing to ${action}: destination parent is not a trusted d
- Refusing to ${action} outside the install root: '${targetPat
- Nasiko archive is truncated.
AI-assisted analysis of affaan-m/ECC@06c5e118c4 (2026-08-18).
Data as JSON: /api/errors/17fb2a74c19b1483.
Report an issue: GitHub.