import { timingSafeEqual } from "node:crypto"; export type ProviderGuardianKind = "vulnerability" | "provenance"; export const MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES = 4_092; export const MAX_PROVIDER_GUARDIAN_LEASE_MS = 40 * 60 * 1_000; export const MAX_PROVIDER_SEALED_BYTES = 8_388_608; const V2_GUARD_KEYS = ["type", "version", "kind", "nonce", "deadlineEpochMs"] as const; const READY_KEYS = [ "type", "version", "nonce", "rawDev", "rawIno", "sealedTempLeaf", "sealedDev", "sealedIno", ] as const; const PUBLISH_KEYS = [ "type", "version", "nonce", "sealedDev", "sealedIno", "size", "sha256", ] as const; const PUBLISHED_KEYS = ["type", "version", "nonce", "sealedDev", "sealedIno"] as const; const COMMIT_KEYS = ["type", "version", "nonce"] as const; export type ProviderGuardianGuard = Readonly<{ kind: ProviderGuardianKind; nonce: Buffer; deadlineEpochMs: number; }>; export type ProviderGuardianReady = Readonly<{ nonce: Buffer; rawDev: number; rawIno: number; sealedTempLeaf: string; sealedDev: number; sealedIno: number; }>; export type ProviderGuardianPublish = Readonly<{ nonce: Buffer; sealedDev: number; sealedIno: number; size: number; sha256: string; }>; export type ProviderGuardianPublished = Readonly<{ nonce: Buffer; sealedDev: number; sealedIno: number; }>; export function providerGuardianSealedTempLeaf( kind: ProviderGuardianKind, nonce: Buffer, ): string { const rawLeaf = baseLeaf(kind); assertV2Nonce(nonce); return `.${rawLeaf}.guardian-${nonce.subarray(0, 16).toString("hex")}.tmp`; } export function providerGuardianRawStagingLeaf( kind: ProviderGuardianKind, nonce: Buffer, ): string { const rawLeaf = baseLeaf(kind); assertV2Nonce(nonce); return `.${rawLeaf}.guardian-${nonce.subarray(0, 16).toString("hex")}.raw.tmp`; } export function encodeProviderGuardianGuard(input: ProviderGuardianGuard): Buffer { assertV2Guard(input); return prefixFrame(encodeV2GuardPayload(input)); } export function decodeProviderGuardianGuard( payload: Buffer, options: Readonly<{ nowEpochMs: number; maxLeaseMs: number }>, ): ProviderGuardianGuard { const value = parseRecord(payload, V2_GUARD_KEYS, "guard"); const guard: ProviderGuardianGuard = { kind: value.kind as ProviderGuardianKind, nonce: parseV2Nonce(value.nonce), deadlineEpochMs: value.deadlineEpochMs as number, }; if (value.type !== "guard" || value.version !== 2) { throw new TypeError("provider guardian guard version is invalid"); } assertV2Guard(guard); if ( !Number.isSafeInteger(options.nowEpochMs) || !Number.isSafeInteger(options.maxLeaseMs) || options.maxLeaseMs <= 0 || guard.deadlineEpochMs <= options.nowEpochMs || guard.deadlineEpochMs > options.nowEpochMs + options.maxLeaseMs ) { throw new TypeError("provider guardian guard deadline is invalid"); } assertCanonical(payload, encodeV2GuardPayload(guard), "guard"); return guard; } export function encodeProviderGuardianReady(input: ProviderGuardianReady): Buffer { assertReady(input); return prefixFrame(encodeReadyPayload(input)); } export function decodeProviderGuardianReady( payload: Buffer, expectedNonce: Buffer, ): ProviderGuardianReady { assertV2Nonce(expectedNonce); const value = parseRecord(payload, READY_KEYS, "READY"); const ready: ProviderGuardianReady = { nonce: parseV2Nonce(value.nonce), rawDev: value.rawDev as number, rawIno: value.rawIno as number, sealedTempLeaf: value.sealedTempLeaf as string, sealedDev: value.sealedDev as number, sealedIno: value.sealedIno as number, }; if (value.type !== "ready" || value.version !== 2) { throw new TypeError("provider guardian READY version is invalid"); } assertReady(ready); assertAuthenticatedNonce(ready.nonce, expectedNonce, "READY"); assertCanonical(payload, encodeReadyPayload(ready), "READY"); return ready; } export function encodeProviderGuardianPublish(input: ProviderGuardianPublish): Buffer { assertPublish(input); return prefixFrame(encodePublishPayload(input)); } export function decodeProviderGuardianPublish( payload: Buffer, expectedNonce: Buffer, ): ProviderGuardianPublish { assertV2Nonce(expectedNonce); const value = parseRecord(payload, PUBLISH_KEYS, "publish"); const publish: ProviderGuardianPublish = { nonce: parseV2Nonce(value.nonce), sealedDev: value.sealedDev as number, sealedIno: value.sealedIno as number, size: value.size as number, sha256: value.sha256 as string, }; if (value.type !== "publish" || value.version !== 2) { throw new TypeError("provider guardian publish version is invalid"); } assertPublish(publish); assertAuthenticatedNonce(publish.nonce, expectedNonce, "publish"); assertCanonical(payload, encodePublishPayload(publish), "publish"); return publish; } export function encodeProviderGuardianPublished(input: ProviderGuardianPublished): Buffer { assertPublished(input); return prefixFrame(encodePublishedPayload(input)); } export function decodeProviderGuardianPublished( payload: Buffer, expectedNonce: Buffer, expectedIdentity: Readonly<{ dev: number; ino: number }>, ): void { assertV2Nonce(expectedNonce); const value = parseRecord(payload, PUBLISHED_KEYS, "PUBLISHED"); const published: ProviderGuardianPublished = { nonce: parseV2Nonce(value.nonce), sealedDev: value.sealedDev as number, sealedIno: value.sealedIno as number, }; if (value.type !== "published" || value.version !== 2) { throw new TypeError("provider guardian PUBLISHED version is invalid"); } assertPublished(published); assertAuthenticatedNonce(published.nonce, expectedNonce, "PUBLISHED"); if (published.sealedDev !== expectedIdentity.dev || published.sealedIno !== expectedIdentity.ino) { throw new TypeError("provider guardian PUBLISHED identity is invalid"); } assertCanonical(payload, encodePublishedPayload(published), "PUBLISHED"); } function encodeV2GuardPayload(input: ProviderGuardianGuard): Buffer { return Buffer.from(JSON.stringify({ type: "guard", version: 2, kind: input.kind, nonce: input.nonce.toString("hex"), deadlineEpochMs: input.deadlineEpochMs, })); } export function encodeProviderGuardianCommit(nonce: Buffer): Buffer { assertV2Nonce(nonce); return prefixFrame(encodeCommitPayload(nonce)); } export function decodeProviderGuardianCommit(payload: Buffer, expectedNonce: Buffer): void { assertPayloadSize(payload); assertV2Nonce(expectedNonce); const decoded = decodeUtf8(payload); let value: unknown; try { value = JSON.parse(decoded); } catch { throw new TypeError("provider guardian commit JSON is invalid"); } if (!isRecord(value) || !hasExactKeys(value, COMMIT_KEYS)) { throw new TypeError("provider guardian commit fields are invalid"); } const nonce = parseV2Nonce(value.nonce); if ( value.type !== "commit" || value.version !== 2 || nonce.byteLength !== expectedNonce.byteLength || !timingSafeEqual(nonce, expectedNonce) ) { throw new TypeError("provider guardian commit authentication failed"); } if (!payload.equals(encodeCommitPayload(nonce))) { throw new TypeError("provider guardian commit is not canonical"); } } function encodeCommitPayload(nonce: Buffer): Buffer { return Buffer.from(JSON.stringify({ type: "commit", version: 2, nonce: nonce.toString("hex"), })); } function encodeReadyPayload(input: ProviderGuardianReady): Buffer { return Buffer.from(JSON.stringify({ type: "ready", version: 2, nonce: input.nonce.toString("hex"), rawDev: input.rawDev, rawIno: input.rawIno, sealedTempLeaf: input.sealedTempLeaf, sealedDev: input.sealedDev, sealedIno: input.sealedIno, })); } function encodePublishPayload(input: ProviderGuardianPublish): Buffer { return Buffer.from(JSON.stringify({ type: "publish", version: 2, nonce: input.nonce.toString("hex"), sealedDev: input.sealedDev, sealedIno: input.sealedIno, size: input.size, sha256: input.sha256, })); } function encodePublishedPayload(input: ProviderGuardianPublished): Buffer { return Buffer.from(JSON.stringify({ type: "published", version: 2, nonce: input.nonce.toString("hex"), sealedDev: input.sealedDev, sealedIno: input.sealedIno, })); } function prefixFrame(payload: Buffer): Buffer { if (payload.byteLength <= 0 || payload.byteLength > MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES) { throw new TypeError("provider guardian frame size is invalid"); } const frame = Buffer.allocUnsafe(payload.byteLength + 4); frame.writeUInt32BE(payload.byteLength, 0); payload.copy(frame, 4); return frame; } function assertV2Guard(input: ProviderGuardianGuard): void { if ( (input.kind !== "vulnerability" && input.kind !== "provenance") || !isV2Nonce(input.nonce) || !Number.isSafeInteger(input.deadlineEpochMs) || input.deadlineEpochMs <= 0 ) { throw new TypeError("provider guardian guard fields are invalid"); } } function baseLeaf(kind: ProviderGuardianKind): string { if (kind === "vulnerability") return "vulnerability-report.json"; if (kind === "provenance") return "provenance-attestation.json"; throw new TypeError("provider guardian kind is invalid"); } function assertReady(input: ProviderGuardianReady): void { if ( !isV2Nonce(input.nonce) || !isIdentityPart(input.rawDev) || !isIdentityPart(input.rawIno) || typeof input.sealedTempLeaf !== "string" || !/^\.(?:vulnerability-report|provenance-attestation)\.json\.guardian-[0-9a-f]{32}\.tmp$/u .test(input.sealedTempLeaf) || !isIdentityPart(input.sealedDev) || !isIdentityPart(input.sealedIno) ) { throw new TypeError("provider guardian READY fields are invalid"); } } function assertPublish(input: ProviderGuardianPublish): void { if ( !isV2Nonce(input.nonce) || !isIdentityPart(input.sealedDev) || !isIdentityPart(input.sealedIno) || !Number.isSafeInteger(input.size) || input.size <= 0 || input.size > MAX_PROVIDER_SEALED_BYTES || typeof input.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(input.sha256) ) { throw new TypeError("provider guardian publish fields are invalid"); } } function assertPublished(input: ProviderGuardianPublished): void { if ( !isV2Nonce(input.nonce) || !isIdentityPart(input.sealedDev) || !isIdentityPart(input.sealedIno) ) { throw new TypeError("provider guardian PUBLISHED fields are invalid"); } } function assertV2Nonce(nonce: Buffer): void { if (!isV2Nonce(nonce)) throw new TypeError("provider guardian nonce is invalid"); } function isV2Nonce(nonce: Buffer): boolean { return Buffer.isBuffer(nonce) && nonce.byteLength === 32; } function parseV2Nonce(value: unknown): Buffer { return typeof value === "string" && /^[0-9a-f]{64}$/u.test(value) ? Buffer.from(value, "hex") : Buffer.alloc(0); } function isIdentityPart(value: unknown): value is number { return Number.isSafeInteger(value) && (value as number) > 0; } function assertAuthenticatedNonce(received: Buffer, expected: Buffer, label: string): void { if (received.byteLength !== expected.byteLength || !timingSafeEqual(received, expected)) { throw new TypeError(`provider guardian ${label} authentication failed`); } } function parseRecord( payload: Buffer, expectedKeys: readonly string[], label: string, ): Record { assertPayloadSize(payload); let value: unknown; try { value = JSON.parse(decodeUtf8(payload)); } catch (error) { if (error instanceof TypeError && /provider guardian/u.test(error.message)) throw error; throw new TypeError(`provider guardian ${label} JSON is invalid`, { cause: error }); } if (!isRecord(value) || !hasExactKeys(value, expectedKeys)) { throw new TypeError(`provider guardian ${label} fields are invalid`); } return value; } function assertCanonical(payload: Buffer, canonical: Buffer, label: string): void { if (!payload.equals(canonical)) { throw new TypeError(`provider guardian ${label} frame is not canonical`); } } function assertPayloadSize(payload: Buffer): void { if (!Buffer.isBuffer(payload) || payload.byteLength <= 0 || payload.byteLength > MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES) { throw new TypeError("provider guardian frame size is invalid"); } } function decodeUtf8(payload: Buffer): string { let decoded: string; try { decoded = new TextDecoder("utf-8", { fatal: true }).decode(payload); } catch { throw new TypeError("provider guardian frame UTF-8 is invalid"); } if (decoded.includes("\0")) throw new TypeError("provider guardian frame contains NUL"); return decoded; } function isRecord(value: unknown): value is Record { return Boolean(value && typeof value === "object" && !Array.isArray(value)); } function hasExactKeys(value: Record, expected: readonly string[]): boolean { const keys = Object.keys(value); return keys.length === expected.length && keys.every((key, index) => key === expected[index]); }