const { randomBytes, createHash } = require("node:crypto"); const { spawnSync } = require("node:child_process"); const path = require("node:path"); const cryptoService = require("../../../src/services/crypto"); const { checkSyntheticSample, runSyntheticContract, } = require("../../../../scripts/qa/check-sensitive-recovery-contract.cjs"); describe("K0 synthetic sensitive recovery contract", () => { let config; let plaintext; let encrypted; beforeEach(() => { config = { fieldEncryptionKey: randomBytes(32).toString("base64") }; plaintext = `synthetic-only-${randomBytes(16).toString("hex")}`; encrypted = cryptoService.encryptStringOrThrow(config, plaintext); }); // Boolean assertions keep failed tests from printing fixture values/digests. function expectNoFixture(output, additional = []) { const key = Buffer.from(config.fieldEncryptionKey, "base64"); const forbidden = [ plaintext, encrypted, config.fieldEncryptionKey, key.toString("hex"), createHash("sha256").update(key).digest("hex"), createHash("sha256").update(config.fieldEncryptionKey).digest("hex"), ...encrypted.split(":").slice(1), ...additional, ]; expect(forbidden.some((value) => output.includes(value))).toBe(false); } it("round-trips with the actual production crypto exports and only reports status", () => { const report = checkSyntheticSample(config, encrypted, plaintext); expect(report).toEqual({ status: "PASS", reason: "SYNTHETIC_MATCH" }); expectNoFixture(JSON.stringify(report)); }); it.each([ ["no config", undefined], ["no key", {}], ["null", { fieldEncryptionKey: null }], ["empty", { fieldEncryptionKey: "" }], ["whitespace", { fieldEncryptionKey: " " }], ["wrong type", { fieldEncryptionKey: 32 }], ["unparseable", { fieldEncryptionKey: "%%%" }], ["short", { fieldEncryptionKey: Buffer.alloc(31).toString("base64") }], ["long", { fieldEncryptionKey: Buffer.alloc(33).toString("base64") }], ])("stops for an unavailable or invalid key: %s", (_label, candidate) => { expect(checkSyntheticSample(candidate, encrypted, plaintext)).toEqual({ status: "STOP", reason: "KEY_UNAVAILABLE_OR_INVALID", }); }); it("stops for a different but valid 32-byte key", () => { const wrongKey = Buffer.from(config.fieldEncryptionKey, "base64"); wrongKey[0] ^= 1; const report = checkSyntheticSample({ fieldEncryptionKey: wrongKey.toString("base64") }, encrypted, plaintext); expect(report).toEqual({ status: "STOP", reason: "DECRYPTION_FAILED" }); expectNoFixture(JSON.stringify(report), [wrongKey.toString("base64")]); }); it.each([["iv", 1], ["tag", 2], ["ciphertext", 3]])("stops after tampering with %s", (_label, index) => { const parts = encrypted.split(":"); const value = Buffer.from(parts[index], "base64url"); value[0] ^= 1; parts[index] = value.toString("base64url"); const tampered = parts.join(":"); const report = checkSyntheticSample(config, tampered, plaintext); expect(report).toEqual({ status: "STOP", reason: "DECRYPTION_FAILED" }); expectNoFixture(JSON.stringify(report), [tampered]); }); it.each([["empty", ""], ["null", null], ["absent", undefined]])("marks %s fields NOT_TESTED", (_label, value) => { expect(checkSyntheticSample(config, value, plaintext)).toEqual({ status: "NOT_TESTED", reason: "EMPTY_FIELD" }); }); it("does not let an empty field hide a missing key", () => { expect(checkSyntheticSample({}, "", plaintext)).toEqual({ status: "STOP", reason: "KEY_UNAVAILABLE_OR_INVALID" }); }); it.each([ ["legacy plaintext", "synthetic-unencrypted"], ["unknown version", "v2:a:b:c"], ["missing parts", "v1:a:b"], ["extra parts", "v1:a:b:c:d"], ["empty parts", "v1:::"], ["invalid encoding", "v1:%%:%%:%%"], ["wrong type", 123], ])("stops instead of treating a null decryption as success: %s", (_label, payload) => { expect(checkSyntheticSample(config, payload, plaintext)).toEqual({ status: "STOP", reason: "INVALID_PAYLOAD" }); }); it("requires the synthetic expectation and detects a plaintext mismatch", () => { expect(checkSyntheticSample(config, encrypted)).toEqual({ status: "STOP", reason: "SYNTHETIC_EXPECTATION_REQUIRED" }); const report = checkSyntheticSample(config, encrypted, "different-synthetic-value"); expect(report).toEqual({ status: "STOP", reason: "PLAINTEXT_MISMATCH" }); expectNoFixture(JSON.stringify(report)); }); it("drops raw exceptions, does not log, and never uses the logging decrypt branch", () => { const marker = `synthetic-error-${randomBytes(8).toString("hex")}`; const spies = ["log", "warn", "error", "info", "debug"].map((name) => vi.spyOn(console, name).mockImplementation(() => {})); const safe = vi.spyOn(cryptoService, "decryptStringSafe").mockImplementation(() => { throw new Error(marker); }); try { const throwingConfig = { get fieldEncryptionKey() { throw new Error(marker); } }; const report = checkSyntheticSample(throwingConfig, encrypted, plaintext); expect(report).toEqual({ status: "STOP", reason: "DECRYPTION_FAILED" }); expectNoFixture(JSON.stringify(report), [marker]); expect(runSyntheticContract().status).toBe("PASS"); expect(safe).not.toHaveBeenCalled(); expect(spies.every((spy) => spy.mock.calls.length === 0)).toBe(true); } finally { safe.mockRestore(); spies.forEach((spy) => spy.mockRestore()); } }); it("keeps real custody and real-data decryption untested after the synthetic matrix passes", () => { const report = runSyntheticContract(); expect(report.status).toBe("PASS"); expect(report.checks).toHaveLength(7); expect(report.checks.every((check) => check.matched)).toBe(true); expect(report).toMatchObject({ scope: "K0_SYNTHETIC_ONLY", originalKeySource: "UNKNOWN", custody: "NOT_TESTED", realDataDecryption: "NOT_TESTED" }); }); it("CLI ignores ambient key inputs and produces only the fixed aggregate contract", () => { const script = path.resolve(__dirname, "../../../../scripts/qa/check-sensitive-recovery-contract.cjs"); const child = spawnSync(process.execPath, [script], { encoding: "utf8", env: { FIELD_ENCRYPTION_KEY: config.fieldEncryptionKey, DATABASE_URL: "synthetic-unused" }, }); expect(child.status).toBe(0); expect(child.stderr.length).toBe(0); expectNoFixture(child.stdout + child.stderr); const report = JSON.parse(child.stdout); expect(Object.keys(report).sort()).toEqual(["checks", "custody", "originalKeySource", "realDataDecryption", "scope", "status"]); expect(report.status).toBe("PASS"); expect(report.checks.every((check) => Object.keys(check).sort().join(",") === "category,matched,reason,status")).toBe(true); }); it("CLI rejects arbitrary arguments without echoing them", () => { const script = path.resolve(__dirname, "../../../../scripts/qa/check-sensitive-recovery-contract.cjs"); const child = spawnSync(process.execPath, [script, plaintext], { encoding: "utf8", env: {} }); expect(child.status).toBe(1); expect(child.stderr.length).toBe(0); expectNoFixture(child.stdout + child.stderr); expect(JSON.parse(child.stdout)).toEqual({ scope: "K0_SYNTHETIC_ONLY", status: "FAIL", reason: "ARGUMENTS_NOT_ALLOWED" }); }); });