From 7a61eddd07c4bbc6d0f05de212e5a1032b673365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20R=C3=A9mond?= Date: Fri, 22 May 2026 15:28:35 +0200 Subject: [PATCH] test(e2ee): guard resend/correction paths against plaintext downgrade on plugin failure --- .../src/core/modules/Chat.e2ee.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/fluux-sdk/src/core/modules/Chat.e2ee.test.ts b/packages/fluux-sdk/src/core/modules/Chat.e2ee.test.ts index 1e6fa9f9f..916f2fbae 100644 --- a/packages/fluux-sdk/src/core/modules/Chat.e2ee.test.ts +++ b/packages/fluux-sdk/src/core/modules/Chat.e2ee.test.ts @@ -1051,6 +1051,20 @@ describe('Chat E2EE wiring', () => { expect(captured).toHaveLength(0) }) + it('blocks the resend (no plaintext) when a selected plugin throws mid-encrypt', async () => { + // resendMessage shares applyE2EEToOutboundChat with sendMessage: a + // selected plugin failing mid-encrypt must block the retry, never fall + // back to resending the body in cleartext. + vi.spyOn(manager, 'encryptOutbound').mockRejectedValue( + new E2EEPluginError('permanent', 'pin-mismatch', 'fingerprint changed'), + ) + + await expect( + chat.resendMessage('bob@example.com', 'leaky retry', 'msg-retry-fail'), + ).rejects.toBeInstanceOf(E2EEPluginError) + expect(captured).toHaveLength(0) + }) + it('throws when retrying a message whose attachment carries an aesgcm key', async () => { const emptyManager = new E2EEManager({ storage: new InMemoryStorageBackend(), @@ -1126,6 +1140,20 @@ describe('Chat E2EE wiring', () => { expect(captured).toHaveLength(0) }) + it('blocks the correction (no plaintext) when a selected plugin throws mid-encrypt', async () => { + // sendCorrection shares applyE2EEToOutboundChat: a selected plugin + // failing mid-encrypt must block the edit, never push the corrected + // body to the server in cleartext. + vi.spyOn(manager, 'encryptOutbound').mockRejectedValue( + new E2EEPluginError('permanent', 'pin-mismatch', 'fingerprint changed'), + ) + + await expect( + chat.sendCorrection('bob@example.com', 'orig-id', 'leaky edit'), + ).rejects.toBeInstanceOf(E2EEPluginError) + expect(captured).toHaveLength(0) + }) + it('throws when correcting a message whose attachment carries an aesgcm key', async () => { const emptyManager = new E2EEManager({ storage: new InMemoryStorageBackend(),