From 5083bcbd54818496bddca88b4131abbf2572caca Mon Sep 17 00:00:00 2001 From: Matthew Meszaros Date: Sun, 4 Oct 2026 05:21:56 -0700 Subject: [PATCH] feat: carry webhook and OAuth app signing secrets still stored as whsec_ plaintext into a workspace export as is, so they arrive sealed under the destination key instead of blank --- internal/app/orgtransfer/export.go | 3 +++ internal/app/orgtransfer/spec.go | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/app/orgtransfer/export.go b/internal/app/orgtransfer/export.go index 432b53fd3..8719587bd 100644 --- a/internal/app/orgtransfer/export.go +++ b/internal/app/orgtransfer/export.go @@ -258,6 +258,9 @@ func (s *service) exportRow( // openSecret returns the plaintext behind one stored value, using whichever // key domain sealed it. func (s *service) openSecret(ctx context.Context, sc SecretColumn, stored string, orgCipher *cipher.Cipher) (string, error) { + if sc.PlaintextPrefix != "" && strings.HasPrefix(stored, sc.PlaintextPrefix) { + return stored, nil + } switch sc.Domain { case KeyDomainInstance: if s.creds == nil { diff --git a/internal/app/orgtransfer/spec.go b/internal/app/orgtransfer/spec.go index 610ba0d02..40ade36f0 100644 --- a/internal/app/orgtransfer/spec.go +++ b/internal/app/orgtransfer/spec.go @@ -51,6 +51,8 @@ type SecretColumn struct { // column to hold ciphertext. email_tasks predates unconditional sealing, // so its rows carry a flag rather than a format that can be sniffed. Guard string + // PlaintextPrefix marks a value still stored in the clear from before sealing; it travels as is. + PlaintextPrefix string } // Table is one exported relation and the policy for moving it. @@ -275,7 +277,7 @@ var Tables = []Table{ ResetOnImport: []string{"suspended_at", "suspended_reason", "suspended_by"}, // The app's webhook signing secret is sealed under the instance key, like each endpoint's copy. Secrets: []SecretColumn{ - {Column: "webhook_secret", Domain: KeyDomainInstance}, + {Column: "webhook_secret", Domain: KeyDomainInstance, PlaintextPrefix: "whsec_"}, }, }, { @@ -295,7 +297,7 @@ var Tables = []Table{ // re-sealed on the way across or the destination hands the receiver // signatures computed from ciphertext it could not read. Secrets: []SecretColumn{ - {Column: "secret", Domain: KeyDomainInstance}, + {Column: "secret", Domain: KeyDomainInstance, PlaintextPrefix: "whsec_"}, }, }, {