import { chmod, mkdtemp, mkdir, readFile, symlink, writeFile, } from "node:fs/promises"; import { createHash } from "node:crypto"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; import { canonicalJsonDigest } from "../../src/security/canonical-digest.ts"; // @ts-expect-error executable release modules intentionally have no browser declarations import { enumerateClosure } from "../../scripts/release/enumerate-closure.mjs"; // @ts-expect-error executable release modules intentionally have no browser declarations import * as privacyRunner from "../../scripts/release/run-privacy-workers.mjs"; // @ts-expect-error executable release modules intentionally have no browser declarations import { reconcileScan } from "../../scripts/release/reconcile-scan.mjs"; // @ts-expect-error container worker is executable JavaScript import { inspectObject } from "../../containers/privacy-worker-a/worker.mjs"; // @ts-expect-error container matcher is executable JavaScript import { matchReports } from "../../containers/privacy-matcher/matcher.mjs"; const { buildWorkerCommand, validateScanObjectDescriptor, requireLiveContainerValidation, } = privacyRunner; const h = (c: string) => c.repeat(64); const sourceClosure = (members: unknown[] = []) => { return { schema: "portfolio-publication-closure/v1", members }; }; const publicClosure = (members: unknown[] = []) => { return { schema: "portfolio-public-object-closure/v1", members }; }; function report( workerId: "worker-a" | "worker-b", override: Record = {}, ) { return { schema: "portfolio-privacy-worker-report/v1", workerId, imageDigest: workerId === "worker-a" ? h("a") : h("b"), toolchain: workerId === "worker-a" ? "bytes-js/v1" : "bytes-python/v1", inputDigest: h("1"), checkId: "bytes", normalizedOutputDigest: h("2"), normalizedMembers: ["text:" + h("3")], verdict: "pass", findingsDigest: h("0"), ...override, }; } function matcher(override: Record = {}) { return { schema: "portfolio-privacy-matcher-result/v1", inputDigest: h("1"), checkId: "bytes", normalizedOutputDigest: h("2"), normalizedMembers: ["text:" + h("3")], ruleSetDigest: h("4"), appliedRuleIds: ["denylist-v1"], verdict: "pass", workerImageDigests: [h("a"), h("b")], ...override, }; } describe("isolated privacy closure and reconciliation", () => { test("fails closed while live scanners are pending and for every non-empty public object closure", () => { expect(() => requireLiveContainerValidation({ schema: "portfolio-live-container-validation/v1", status: "pending", reason: "not verified", }), ).toThrow(/pending|verified/i); const canary = Buffer.from("PRIVATE-PROJECT-NAME\nTOKEN=secret-canary"); const inputDigest = createHash("sha256").update(canary).digest("hex"); expect( inspectObject( { schema: "portfolio-privacy-worker-request/v1", workerId: "worker-a", imageDigest: h("a"), inputDigest, checkId: "secret", }, canary, ).verdict, ).not.toBe("pass"); const pythonReport = report("worker-b", { inputDigest, checkId: "secret", verdict: "unsupported", }); expect(() => matchReports({ reports: [ inspectObject( { schema: "portfolio-privacy-worker-request/v1", workerId: "worker-a", imageDigest: h("a"), inputDigest, checkId: "secret", }, canary, ), pythonReport, ], rules: [{ id: "private-name-v1", deniedMemberDigests: [] }], ruleSetDigest: h("4"), }), ).toThrow(/pass|unsupported|agree/i); const source = sourceClosure(); const publicObjectClosure = publicClosure([ { objectId: "private-leak.txt", inputDigest, requiredChecks: ["secret"], }, ]); expect(() => reconcileScan({ sourceClosure: source, publicObjectClosure, reports: [ report("worker-a", { inputDigest, checkId: "secret" }), report("worker-b", { inputDigest, checkId: "secret" }), ], matcherResults: [matcher({ inputDigest, checkId: "secret" })], candidate: { schema: "portfolio-candidate-release-manifest/v1", buildId: "r1", sourceClosureDigest: canonicalJsonDigest( "portfolio-publication-closure/v1", { schema: source.schema, members: source.members }, ), publicObjectClosureDigest: canonicalJsonDigest( "portfolio-public-object-closure/v1", publicObjectClosure, ), }, }), ).toThrow(/unavailable|non-empty|scanner/i); }); test("derives exact scan coverage from canonical closures instead of caller expectedChecks", () => { const source = sourceClosure(); const publicObjectClosure = publicClosure(); const candidate = { schema: "portfolio-candidate-release-manifest/v1", buildId: "r1", sourceClosureDigest: canonicalJsonDigest( "portfolio-publication-closure/v1", source, ), publicObjectClosureDigest: canonicalJsonDigest( "portfolio-public-object-closure/v1", publicObjectClosure, ), }; const result = reconcileScan({ sourceClosure: source, publicObjectClosure, reports: [], matcherResults: [], candidate, }); expect(result.evidence).toMatchObject({ coverageCount: 0, passedCount: 0, sourceClosureDigest: candidate.sourceClosureDigest, publicObjectClosureDigest: candidate.publicObjectClosureDigest, }); expect(() => reconcileScan({ sourceClosure: source, publicObjectClosure, expectedChecks: [{ inputDigest: h("1"), checkId: "bytes" }], reports: [], matcherResults: [], candidate, }), ).toThrow(/missing|extra|unknown|caller|expectedChecks/i); }); test("enumerates an exact byte-sorted closure and rejects unsafe filesystem members", async () => { const root = await mkdtemp(join(tmpdir(), "portfolio-closure-")); await mkdir(join(root, "nested")); await writeFile(join(root, "z.txt"), "z"); await writeFile(join(root, "nested", "a.txt"), "a"); const closure = await enumerateClosure([{ class: "build", root }]); expect( closure.members.map((member: { path: string }) => member.path), ).toEqual(["nested/a.txt", "z.txt"]); expect( closure.members.every((member: { sha256: string }) => /^[a-f0-9]{64}$/u.test(member.sha256), ), ).toBe(true); await symlink("z.txt", join(root, "link")); await expect(enumerateClosure([{ class: "build", root }])).rejects.toThrow( /symlink/i, ); }); test("constructs networkless read-only non-root resource-limited jobs", () => { const command = buildWorkerCommand({ runtime: "docker", image: "scanner@sha256:" + h("a"), inputPath: "/safe/input", outputPath: "/safe/output", worker: "worker-a", }); expect(command).toContain("--network=none"); expect(command).toContain("--read-only"); expect(command).toContain("--cap-drop=ALL"); expect(command).toContain("--security-opt=no-new-privileges"); expect(command).toContain("--user=65532:65532"); expect(command).toContain("--memory=256m"); expect(command).toContain("--cpus=1"); expect(command).toContain("--pids-limit=64"); expect(command).toContain("--pull=never"); expect(command).toContain( "--mount=type=bind,src=/safe/input,dst=/input,readonly", ); expect(command).toContain("--mount=type=bind,src=/safe/output,dst=/output"); }); test.each([ ["encrypted archive", { encrypted: true }], ["malformed container", { malformed: true }], ["nested archive", { archiveDepth: 4 }], ["archive expansion", { expandedBytes: 67_108_865 }], ["traversal", { members: [{ path: "../secret", entryType: "file" }] }], ["absolute member", { members: [{ path: "/secret", entryType: "file" }] }], ["symlink member", { members: [{ path: "safe", entryType: "symlink" }] }], ["device member", { members: [{ path: "safe", entryType: "device" }] }], ])("fails closed before decoding an unsafe %s", (_name, override) => { expect(() => validateScanObjectDescriptor({ inputDigest: h("1"), checkId: "container", mediaType: "application/zip", encrypted: false, malformed: false, archiveDepth: 1, expandedBytes: 10, members: [], ...override, }), ).toThrow(); }); test("does not expose OCR or QR canary values when a matcher blocks them", () => { const secret = "PRIVATE-CANARY-DO-NOT-PRINT"; expect(() => reconcileScan({ expectedChecks: [{ inputDigest: h("1"), checkId: "ocr" }], reports: [ report("worker-a", { checkId: "ocr", verdict: "fail" }), report("worker-b", { checkId: "ocr", verdict: "fail" }), ], matcherResults: [matcher({ checkId: "ocr", verdict: "fail" })], candidate: { schema: "portfolio-candidate-release-manifest/v1", buildId: "r1", }, }), ).toThrowError(expect.not.stringContaining(secret)); }); test("accepts only the exact empty public object closure while scanners are unavailable", () => { const source = sourceClosure(); const publicObjectClosure = publicClosure(); const result = reconcileScan({ sourceClosure: source, publicObjectClosure, reports: [], matcherResults: [], candidate: { schema: "portfolio-candidate-release-manifest/v1", buildId: "r1", sourceClosureDigest: canonicalJsonDigest( "portfolio-publication-closure/v1", source, ), publicObjectClosureDigest: canonicalJsonDigest( "portfolio-public-object-closure/v1", publicObjectClosure, ), }, }); expect(result.evidenceDigest).toMatch(/^[a-f0-9]{64}$/); expect(result.candidateDigest).toMatch(/^[a-f0-9]{64}$/); expect(new TextDecoder().decode(result.evidenceBytes)).not.toContain( "decoded-secret", ); }); test.each([ ["single worker", [report("worker-a")], [matcher()]], [ "reordered workers", [report("worker-b"), report("worker-a")], [matcher()], ], [ "schema-valid false pass", [report("worker-a"), report("worker-b", { verdict: "fail" })], [matcher()], ], [ "normalized disagreement", [ report("worker-a"), report("worker-b", { normalizedOutputDigest: h("9") }), ], [matcher()], ], [ "substituted worker image", [report("worker-a"), report("worker-b", { imageDigest: h("a") })], [matcher()], ], [ "mismatched check", [report("worker-a"), report("worker-b", { checkId: "metadata" })], [matcher()], ], [ "matcher false pass", [report("worker-a"), report("worker-b")], [matcher({ normalizedOutputDigest: h("9") })], ], [ "unsupported container", [ report("worker-a", { verdict: "unsupported" }), report("worker-b", { verdict: "unsupported" }), ], [matcher({ verdict: "fail" })], ], ])("fails closed for %s", (_name, reports, matcherResults) => { expect(() => reconcileScan({ expectedChecks: [{ inputDigest: h("1"), checkId: "bytes" }], reports, matcherResults, candidate: { schema: "portfolio-candidate-release-manifest/v1", buildId: "r1", }, }), ).toThrow(); }); test("writes canonical evidence and candidate objects content-addressed without overwrite", async () => { const source = sourceClosure(); const publicObjectClosure = publicClosure(); const result = reconcileScan({ sourceClosure: source, publicObjectClosure, reports: [], matcherResults: [], candidate: { schema: "portfolio-candidate-release-manifest/v1", buildId: "r1", sourceClosureDigest: canonicalJsonDigest( "portfolio-publication-closure/v1", source, ), publicObjectClosureDigest: canonicalJsonDigest( "portfolio-public-object-closure/v1", publicObjectClosure, ), }, }); const root = await mkdtemp(join(tmpdir(), "portfolio-evidence-")); await result.writeContentAddressed(root); expect(await readFile(join(root, result.evidenceDigest), "utf8")).toBe( new TextDecoder().decode(result.evidenceBytes), ); await chmod(join(root, result.candidateDigest), 0o600); await writeFile(join(root, result.candidateDigest), "substituted"); await expect(result.writeContentAddressed(root)).rejects.toThrow( /immutable|content-addressed/i, ); }); });