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

This commit is contained in:
Matthew Meszaros
2026-10-04 05:21:56 -07:00
parent 79bf9a15c4
commit 5083bcbd54
2 changed files with 7 additions and 2 deletions
+3
View File
@@ -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 {
+4 -2
View File
@@ -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_"},
},
},
{