Initial vmc CLI

This commit is contained in:
Vault Sovereign
2025-12-26 19:35:03 +00:00
commit a075fcf95f
37 changed files with 3967 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
import fs from "node:fs";
import path from "node:path";
import { hashBlake3Hex, hashSha256Hex } from "../lib/hash.js";
import { readHead } from "../lib/ledger.js";
import { keyIdFromPublicHex, verifyMessage } from "../lib/keys.js";
type ReceiptEnvelope = {
receipt_version: "1";
created_at: string;
cwd: string;
user: string;
hostname: string;
argv: string[];
reason: string;
lock_file: string | null;
lock_started_at: string | null;
force: boolean;
plan_file: string | null;
plan_sha256: string | null;
plan_blake3: string | null;
target: { id: number; name: string; ip?: string | null };
request: unknown;
response: unknown;
prev_blake3: string | null;
hash_alg: "blake3+sha256";
blake3: string;
sha256: string;
sig_alg?: "ed25519";
signer_pub?: string;
signer_kid?: string;
signed_at?: string;
signature?: string;
};
type ReceiptBody = Omit<ReceiptEnvelope, "hash_alg" | "blake3" | "sha256">;
function mustExist(p: string, label: string) {
if (!fs.existsSync(p)) {
const err = new Error(`${label} not found: ${p}`);
(err as { exitCode?: number }).exitCode = 2;
throw err;
}
}
function stripHashes(env: ReceiptEnvelope): ReceiptBody {
const {
hash_alg: _hash_alg,
blake3: _blake3,
sha256: _sha256,
sig_alg: _sig_alg,
signer_pub: _signer_pub,
signer_kid: _signer_kid,
signed_at: _signed_at,
signature: _signature,
...body
} = env;
return body;
}
export async function verifyReceipt(
receiptPath: string,
opts: { head?: boolean; plan?: boolean; sig?: boolean } = {}
) {
const abs = path.resolve(receiptPath);
mustExist(abs, "Receipt");
const raw = fs.readFileSync(abs, "utf8");
let env: ReceiptEnvelope;
try {
env = JSON.parse(raw) as ReceiptEnvelope;
} catch {
const err = new Error("Receipt is not valid JSON");
(err as { exitCode?: number }).exitCode = 2;
throw err;
}
if (env.hash_alg !== "blake3+sha256" || !env.blake3 || !env.sha256) {
const err = new Error(
"Receipt missing required hash fields (hash_alg/blake3/sha256)"
);
(err as { exitCode?: number }).exitCode = 2;
throw err;
}
const body = stripHashes(env);
const blake3 = hashBlake3Hex(body);
const sha256 = hashSha256Hex(body);
const failures: string[] = [];
if (blake3 !== env.blake3) {
failures.push(`BLAKE3 mismatch (expected ${env.blake3}, got ${blake3})`);
}
if (sha256 !== env.sha256) {
failures.push(`SHA256 mismatch (expected ${env.sha256}, got ${sha256})`);
}
if (opts.plan && env.plan_file) {
const planAbs = path.isAbsolute(env.plan_file)
? env.plan_file
: path.join(process.cwd(), env.plan_file);
try {
mustExist(planAbs, "Plan file referenced by receipt");
const planRaw = fs.readFileSync(planAbs, "utf8");
const planObj = JSON.parse(planRaw) as unknown;
const planSha = hashSha256Hex(planObj);
const planB3 = hashBlake3Hex(planObj);
if (!env.plan_sha256 || !env.plan_blake3) {
failures.push("Receipt is missing plan hash fields");
} else {
if (env.plan_sha256 !== planSha) {
failures.push(
`Plan SHA256 mismatch (expected ${env.plan_sha256}, got ${planSha})`
);
}
if (env.plan_blake3 !== planB3) {
failures.push(
`Plan BLAKE3 mismatch (expected ${env.plan_blake3}, got ${planB3})`
);
}
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
failures.push(msg);
}
}
if (opts.head) {
const head = readHead();
if (!head) {
failures.push("HEAD.json missing");
} else {
const rel = path.relative(process.cwd(), abs) || abs;
if (head.file === rel && head.blake3 !== env.blake3) {
failures.push(
`HEAD blake3 mismatch (HEAD=${head.blake3}, receipt=${env.blake3})`
);
}
}
}
if (opts.sig) {
if (!env.signature || !env.signer_pub || env.sig_alg !== "ed25519") {
const err = new Error("Receipt signature fields missing or invalid");
(err as { exitCode?: number }).exitCode = 2;
throw err;
}
const msg = new TextEncoder().encode(env.blake3);
const ok = await verifyMessage(msg, env.signature, env.signer_pub);
if (!ok) failures.push("Signature verification failed");
if (env.signer_kid) {
const kid = keyIdFromPublicHex(env.signer_pub);
if (env.signer_kid !== kid) {
failures.push(
`Signer key id mismatch (expected ${env.signer_kid}, got ${kid})`
);
}
}
}
if (failures.length) {
const err = new Error("Receipt verification failed:\n- " + failures.join("\n- "));
(err as { exitCode?: number }).exitCode = 5;
throw err;
}
console.log("OK receipt verified");
console.log(`file: ${abs}`);
console.log(`blake3: ${env.blake3}`);
console.log(`sha256: ${env.sha256}`);
if (env.prev_blake3) console.log(`prev_blake3: ${env.prev_blake3}`);
if (env.plan_file) console.log(`plan: ${env.plan_file}`);
}