#!/usr/bin/env node // K0 only: synthetic, in-memory inputs. No env/config/backup loader or K2 entry. const { randomBytes } = require("node:crypto"); const { resolveFieldEncryptionKey, encryptStringOrThrow, decryptStringOrThrow, } = require("../../server/src/services/crypto"); function result(status, reason) { return { status, reason }; } // The expected value belongs to a synthetic fixture, never a real-data probe. // Return fixed categories only, including when crypto throws or returns null. function checkSyntheticSample(config, encrypted, expectedPlaintext) { try { const key = resolveFieldEncryptionKey(config); if (!key) return result("STOP", "KEY_UNAVAILABLE_OR_INVALID"); key.fill(0); if (encrypted === null || encrypted === undefined || encrypted === "") { return result("NOT_TESTED", "EMPTY_FIELD"); } if (typeof encrypted !== "string") return result("STOP", "INVALID_PAYLOAD"); if (typeof expectedPlaintext !== "string" || !expectedPlaintext) { return result("STOP", "SYNTHETIC_EXPECTATION_REQUIRED"); } const plaintext = decryptStringOrThrow(config, encrypted); if (plaintext === null) return result("STOP", "INVALID_PAYLOAD"); if (plaintext !== expectedPlaintext) return result("STOP", "PLAINTEXT_MISMATCH"); return result("PASS", "SYNTHETIC_MATCH"); } catch { // Never call decryptStringSafe: its warning can contain a raw Error object. return result("STOP", "DECRYPTION_FAILED"); } } function runSyntheticContract() { const key = randomBytes(32); const otherKey = Buffer.from(key); otherKey[0] ^= 1; try { const config = { fieldEncryptionKey: key.toString("base64") }; const wrongConfig = { fieldEncryptionKey: otherKey.toString("base64") }; const plaintext = `synthetic-only-${randomBytes(16).toString("hex")}`; const encrypted = encryptStringOrThrow(config, plaintext); const parts = encrypted.split(":"); const tag = Buffer.from(parts[2], "base64url"); tag[0] ^= 1; parts[2] = tag.toString("base64url"); const cases = [ ["correct", config, encrypted, "PASS", "SYNTHETIC_MATCH"], ["missing", {}, encrypted, "STOP", "KEY_UNAVAILABLE_OR_INVALID"], ["invalid_key", { fieldEncryptionKey: "invalid" }, encrypted, "STOP", "KEY_UNAVAILABLE_OR_INVALID"], ["wrong", wrongConfig, encrypted, "STOP", "DECRYPTION_FAILED"], ["tampered", config, parts.join(":"), "STOP", "DECRYPTION_FAILED"], ["empty", config, "", "NOT_TESTED", "EMPTY_FIELD"], ["malformed", config, "v1:invalid", "STOP", "INVALID_PAYLOAD"], ]; const checks = cases.map(([category, inputConfig, payload, status, reason]) => { const actual = checkSyntheticSample(inputConfig, payload, plaintext); return { category, ...actual, matched: actual.status === status && actual.reason === reason }; }); return { scope: "K0_SYNTHETIC_ONLY", status: checks.every((check) => check.matched) ? "PASS" : "FAIL", checks, originalKeySource: "UNKNOWN", custody: "NOT_TESTED", realDataDecryption: "NOT_TESTED", }; } finally { key.fill(0); otherKey.fill(0); } } if (require.main === module) { let report; try { report = process.argv.length === 2 ? runSyntheticContract() : { scope: "K0_SYNTHETIC_ONLY", status: "FAIL", reason: "ARGUMENTS_NOT_ALLOWED" }; } catch { report = { scope: "K0_SYNTHETIC_ONLY", status: "FAIL", reason: "SYNTHETIC_CHECK_FAILED" }; } process.stdout.write(`${JSON.stringify(report)}\n`); process.exitCode = report.status === "PASS" ? 0 : 1; } module.exports = { checkSyntheticSample, runSyntheticContract };