MCPcopy Create free account
hub / github.com/PerpetualSoftware/pad / EncryptWebhookSecretsAtRest

Method EncryptWebhookSecretsAtRest

internal/store/encryption.go:165–254  ·  view source on GitHub ↗

EncryptWebhookSecretsAtRest encrypts plaintext webhook HMAC secrets (BUG-2057). Called on startup when an encryption key is configured. It handles two populations without ever corrupting genuine ciphertext: - First run (flag unset): webhook-secret encryption is new in this release, so every existin

()

Source from the content-addressed store, hash-verified

163//
164// Idempotent and safe to run on every boot.
165func (s *Store) EncryptWebhookSecretsAtRest() (int, error) {
166 if !s.HasEncryptionKey() {
167 // Keyless: nothing to encrypt, and the flag stays unset so the full
168 // legacy migration still runs if a key is configured later.
169 return 0, nil
170 }
171
172 migrated, err := s.GetPlatformSetting(webhookSecretsEncryptedFlag)
173 if err != nil {
174 return 0, fmt.Errorf("read webhook-secret migration flag: %w", err)
175 }
176 firstRun := migrated != "1"
177
178 query := `SELECT id, secret FROM webhooks WHERE secret != '' AND secret NOT LIKE 'enc:%'`
179 if firstRun {
180 // Every pre-migration secret is plaintext — include "enc:"-prefixed ones.
181 query = `SELECT id, secret FROM webhooks WHERE secret != ''`
182 }
183
184 rows, err := s.db.Query(s.q(query))
185 if err != nil {
186 return 0, fmt.Errorf("query webhook secrets: %w", err)
187 }
188 defer rows.Close()
189
190 type row struct {
191 id, secret string
192 }
193 var toEncrypt []row
194 for rows.Next() {
195 var r row
196 if err := rows.Scan(&r.id, &r.secret); err != nil {
197 return 0, fmt.Errorf("scan row: %w", err)
198 }
199 // First-run only: an "enc:" value that already decrypts under the
200 // current key is genuine ciphertext — skip it so it isn't double-
201 // encrypted. A decrypt failure means legacy plaintext that merely looks
202 // prefixed (no ciphertext under a different key can exist before the
203 // migration has ever run), so fall through and encrypt it.
204 if firstRun && strings.HasPrefix(r.secret, encryptedPrefix) {
205 if _, derr := s.decrypt(r.secret); derr == nil {
206 continue
207 }
208 }
209 toEncrypt = append(toEncrypt, r)
210 }
211 if err := rows.Err(); err != nil {
212 return 0, err
213 }
214 rows.Close() // release the read before opening the write transaction (SQLite)
215
216 if len(toEncrypt) == 0 && !firstRun {
217 return 0, nil
218 }
219
220 // Apply every row update AND the completion flag in one transaction. A crash
221 // mid-migration must not leave some rows encrypted while the flag is unset —
222 // otherwise a later key change would mis-read those rows as legacy plaintext

Calls 10

HasEncryptionKeyMethod · 0.95
GetPlatformSettingMethod · 0.95
qMethod · 0.95
decryptMethod · 0.95
encryptMethod · 0.95
nowFunction · 0.85
QueryMethod · 0.80
ScanMethod · 0.80
ExecMethod · 0.80
CloseMethod · 0.65