diff --git a/crates/manager-core/migrations/0038_email_realtime_secret_check.sql b/crates/manager-core/migrations/0038_email_realtime_secret_check.sql new file mode 100644 index 0000000..7d0096f --- /dev/null +++ b/crates/manager-core/migrations/0038_email_realtime_secret_check.sql @@ -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));