fix: loop-B adversarial (503c326 baseline) — IDEMPOTENCY_KEY_REUSED reclassified ambiguous, 2FA reissue fail-closed alerts, family-cache crash window, consolidation regression checks

Loop B red-team (money/security/dup-mod adversarial) findings on the full payments overhaul:

- CRITICAL-ish: IDEMPOTENCY_KEY_REUSED (409) no longer classified as a definitive
  402 in chargeFailureStatus — it means the ORIGINAL charge may have landed with
  a different body, so it is now AMBIGUOUS (503): the frontend keeps the same
  idempotency key, the pending row stays rescuable by the sweep (which already
  treated it as ambiguous), and the frontend no longer regenerates the key into
  a possible double charge. SCA verification-required codes remain definitive 402.
- HIGH: reissueTwoFACodeAfterFailedCharge now writes a CRITICAL admin notification
  (insertCriticalPaymentNotification) when issuance is refused (missing pepper /
  unavailable delivery) instead of silently stranding the customer; documented that
  a pepper CHANGE invalidates all pending codes.
- MEDIUM: family-alive cache invalidation crash window documented (invalidate-after-
  commit leaves up to 30s warm on a crash; the near-TTL DB re-check bounds it).
- Consolidation regression checks (8b2fe3b helpers): writeChargeSnapshot guard
  preserved at all sites, postChargeRecheck identical, squareRefundStatusToLocal
  mappings verified, reissue fresh-only semantics confirmed at all 5 call sites.

Verified: 26/26 dev packages, both vet tags, frontend tests + build, env-docs 42/42.
This commit is contained in:
2026-08-22 00:34:50 +01:00
parent 36887167c6
commit dfe856b181
10 changed files with 219 additions and 38 deletions
+10
View File
@@ -75,6 +75,16 @@ func twoFADeliveryAvailable() bool {
// stored code would be an offline-brute-forceable unsalted digest. Either way
// issuance is refused (fail-closed). Dev/test builds always allow issuance
// (twofa_dev.go).
//
// The pepper check is the ONLY hard gate here (plus the delivery channel), and
// it is also the ONLY hard gate on the payments re-issue path
// (payments.twoFAReissueIssueAllowed). PEPPER-CHANGE HAZARD (Loop B finding 2):
// the pepper keys the HMAC-SHA256 of every stored pending-code hash, so
// CHANGING TWO_FACTOR_PEPPER invalidates ALL pending codes — every stored hash
// was computed with the old pepper and can never match a code minted under the
// new one. An operator who changes the pepper must re-mint every user's code
// (or have each user re-run 2FA setup), or enforced saved-card charges will
// strand customers with 400 ErrMissingOrExpired forever.
func twoFAEnsureIssueAllowed() error {
if os.Getenv(twoFAPepperEnv) == "" {
return errTwoFAPepperRequired