Files
playbook/antigravity-awesome-skills/verification/aas-v1/verifier/lib/tarball.mjs
T
2026-07-18 00:02:59 +00:00

118 lines
5.1 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import zlib from "node:zlib";
import { sha256, sha512 } from "./canonical.mjs";
function textField(block, offset, length) {
return block.subarray(offset, offset + length).toString("utf8").replace(/\0.*$/s, "");
}
function octalField(block, offset, length) {
const value = textField(block, offset, length).trim();
if (!/^[0-7]*$/.test(value)) throw new Error(`Invalid tar octal field: ${JSON.stringify(value)}`);
return Number.parseInt(value || "0", 8);
}
export function parseTarGzip(bytes) {
const expanded = zlib.gunzipSync(bytes, { maxOutputLength: 512 * 1024 * 1024 });
if (expanded.length % 512 !== 0) throw new Error("Tarball is not block aligned");
const entries = [];
let offset = 0;
let zeroBlocks = 0;
while (offset + 512 <= expanded.length) {
const header = expanded.subarray(offset, offset + 512);
offset += 512;
if (header.every((byte) => byte === 0)) {
zeroBlocks += 1;
if (zeroBlocks === 2) break;
continue;
}
zeroBlocks = 0;
const checksumHeader = Buffer.from(header);
checksumHeader.fill(0x20, 148, 156);
const actualChecksum = [...checksumHeader].reduce((total, byte) => total + byte, 0);
if (actualChecksum !== octalField(header, 148, 8)) throw new Error("Tar header checksum mismatch");
const name = textField(header, 0, 100);
const prefix = textField(header, 345, 155);
const entryPath = prefix ? `${prefix}/${name}` : name;
const size = octalField(header, 124, 12);
const type = textField(header, 156, 1) || "0";
if (offset + size > expanded.length) throw new Error(`${entryPath}: tar entry exceeds archive`);
const content = expanded.subarray(offset, offset + size);
entries.push({
path: entryPath,
type,
size,
mode: octalField(header, 100, 8),
linkName: textField(header, 157, 100),
sha256: type === "0" || type === "\0" ? sha256(content) : null,
content,
});
offset += size + ((512 - (size % 512)) % 512);
}
if (zeroBlocks !== 2) throw new Error("Tarball lacks two terminal zero blocks");
return entries;
}
function portablePath(value) {
if (!value || value.includes("\\") || value.startsWith("/") || /^[A-Za-z]:/.test(value)) return false;
const segments = value.split("/");
return segments.every((segment) => segment && segment !== "." && segment !== "..");
}
const ALLOWED_PACKAGE_PATHS = [
/^package\/(?:LICENSE(?:\.[^/]+)?|README(?:\.[^/]+)?|package\.json)$/i,
/^package\/tools\/(?:bin|lib)\/[A-Za-z0-9._/-]+$/,
/^package\/(?:data|schemas)\/[A-Za-z0-9._/-]+$/,
/^package\/skills\/.+$/,
/^package\/skills_index\.json$/,
/^package\/node_modules\/(?:ajv|fast-deep-equal|fast-uri|json-schema-traverse|require-from-string|sanitize-filename|truncate-utf8-bytes|utf8-byte-length|yaml)\/[A-Za-z0-9@._+/-]+$/,
];
const FORBIDDEN_NAMES = /(?:^|\/)(?:\.git|verification|coverage|\.env(?:\.(?!example$|sample$|template$)[^/]*)?|[^/]*\.(?:pem|key|p12|pfx|log))(?:\/|$)/i;
export function inspectPackageTarball(tarballPath) {
const bytes = fs.readFileSync(tarballPath);
const entries = parseTarGzip(bytes);
const failures = [];
const normalized = new Set();
for (const entry of entries) {
if (!portablePath(entry.path)) failures.push({ code: "PACKAGE_UNSAFE_PATH", path: entry.path });
if (!["0", "\0", "5"].includes(entry.type)) failures.push({ code: "PACKAGE_NON_REGULAR_ENTRY", path: entry.path, type: entry.type });
const collisionKey = entry.path.normalize("NFC").toLowerCase();
if (normalized.has(collisionKey)) failures.push({ code: "PACKAGE_PATH_COLLISION", path: entry.path });
normalized.add(collisionKey);
if (entry.type !== "5" && !ALLOWED_PACKAGE_PATHS.some((pattern) => pattern.test(entry.path))) {
failures.push({ code: "PACKAGE_PATH_NOT_ALLOWLISTED", path: entry.path });
}
if (FORBIDDEN_NAMES.test(entry.path)) failures.push({ code: "PACKAGE_SENSITIVE_OR_CHECKOUT_PATH", path: entry.path });
if ((entry.mode & 0o7000) !== 0 || (entry.mode & 0o002) !== 0) failures.push({ code: "PACKAGE_UNSAFE_MODE", path: entry.path, mode: entry.mode });
}
const packageEntry = entries.find((entry) => entry.path === "package/package.json" && entry.type === "0");
if (!packageEntry) failures.push({ code: "PACKAGE_JSON_MISSING" });
let manifest = null;
if (packageEntry) {
manifest = JSON.parse(packageEntry.content.toString("utf8"));
const bins = manifest.bin || {};
for (const [name, expected] of Object.entries({
aas: "tools/bin/aas.js",
"aas-mcp": "tools/bin/aas-mcp.js",
"agentic-awesome-skills": "tools/bin/install.js",
})) {
if (bins[name] !== expected) failures.push({ code: "PACKAGE_BIN_CONTRACT", name, expected, actual: bins[name] });
}
for (const target of Object.values(bins)) {
if (!entries.some((entry) => entry.path === `package/${target}` && entry.type === "0")) failures.push({ code: "PACKAGE_BIN_TARGET_MISSING", target });
}
}
return {
tarballPath: path.resolve(tarballPath),
bytes: bytes.length,
sha256: sha256(bytes),
sha512: sha512(bytes),
entries: entries.map(({ content, ...entry }) => entry),
manifest,
failures,
};
}