fix(manager-core): F-M-002 coupled-nullness CHECK on encrypted-secret column pairs

Two (encrypted, nonce) column pairs are each nullable independently in
the current schema:
- email_trigger_details.inbound_secret_encrypted / _nonce
- app_secrets.realtime_signing_key_encrypted / _nonce

A bug or partial write could leave one populated and the other NULL,
silently bypassing signature verification (receivers decrypt only if
the encrypted column is set).

Migration 0038 adds CHECK ((enc IS NULL) = (nonce IS NULL)) on both
tables — defence-in-depth: catches an invariant violation at the DB
boundary even if the writing code regresses.

AUDIT.md anchor: F-M-002.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 20:45:16 +02:00
parent 0bce113d28
commit 0b7ef11333

View File

@@ -0,0 +1,19 @@
-- F-M-002 (audit 2026-06-07): coupled-nullness CHECK constraints on
-- (encrypted, nonce) pairs.
--
-- The current schema has each column nullable independently:
-- email_trigger_details.inbound_secret_encrypted / _nonce
-- app_secrets.realtime_signing_key_encrypted / _nonce
--
-- A bug or partial write could leave one populated and the other NULL
-- — silently bypassing signature verification (receiver decrypts only
-- if the encrypted column is set). Defend with a coupled-nullness
-- CHECK on each pair so a partial state is rejected at the DB
-- boundary.
ALTER TABLE email_trigger_details
ADD CONSTRAINT email_trigger_details_inbound_secret_pair
CHECK ((inbound_secret_encrypted IS NULL) = (inbound_secret_nonce IS NULL));
ALTER TABLE app_secrets
ADD CONSTRAINT app_secrets_realtime_signing_key_pair
CHECK ((realtime_signing_key_encrypted IS NULL) = (realtime_signing_key_nonce IS NULL));