mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
fix(encryption): sample several stored values before declaring a key mismatch
The boot check decided on the first value it found under a key id, so one damaged or foreign-sealed row could take a correctly keyed server down with 503 on every request. The probe now samples up to five values, one per column first. One value that opens clears the key; a mismatch needs at least two values and none opening. A single value that does not open is inconclusive: the server keeps serving, writes no canary, logs a warning and probes again at the next start. Integration setup truncates encryption_key_canaries between tests.
This commit is contained in:
1 parent
ced17569bf
commit
b04b1f2995
5 files changed
+215
-37
No files matched your search
@@ -170,6 +170,7 @@ describe("boot encryption key check (real Postgres)", () => {
|
||||
username: "existing",
|
||||
email: "existing@example.test",
|
||||
codexAccessTokenEncrypted: encrypt("a stored token"),
|
||||
codexRefreshTokenEncrypted: encrypt("another stored token"),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -186,6 +187,42 @@ describe("boot encryption key check (real Postgres)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("boot key check probe over existing data (real Postgres)", () => {
|
||||
it("one damaged row among good ones still lets the right key through", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: "damaged",
|
||||
email: "damaged@example.test",
|
||||
codexAccessTokenEncrypted:
|
||||
"v1." + Buffer.alloc(40, 7).toString("base64"),
|
||||
codexRefreshTokenEncrypted: encrypt("a good token"),
|
||||
},
|
||||
});
|
||||
expect(await runBootKeyCheck(prisma)).toBe(false);
|
||||
expect(isKeyMismatch()).toBe(false);
|
||||
expect(await prisma.encryptionKeyCanary.count()).toBe(1);
|
||||
});
|
||||
|
||||
it("a single value that does not open neither refuses nor writes a canary", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
username: "single",
|
||||
email: "single@example.test",
|
||||
codexAccessTokenEncrypted: encrypt("the only token"),
|
||||
},
|
||||
});
|
||||
useKey(KEY_B);
|
||||
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
expect(await runBootKeyCheck(prisma)).toBe(false);
|
||||
expect(warn.mock.calls[0]?.[0]).toContain("inconclusive");
|
||||
warn.mockRestore();
|
||||
expect(isKeyMismatch()).toBe(false);
|
||||
expect(await prisma.encryptionKeyCanary.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("encryption key backup step (real Postgres)", () => {
|
||||
it("is due, refuses a stale confirmation, records the right one, and is due again after a re-key", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
|
||||
@@ -51,6 +51,9 @@ export async function truncateAllTables(client: PrismaClient): Promise<void> {
|
||||
"coach_usage",
|
||||
"data_backups",
|
||||
"devices",
|
||||
// Instance-wide, keyed by encryption key id: a canary left by one test
|
||||
// would make the next test's boot check verify instead of write.
|
||||
"encryption_key_canaries",
|
||||
"host_metrics",
|
||||
"idempotency_keys",
|
||||
"integration_statuses",
|
||||
|
||||
Reference in new issue
Block a user