**RESOLVED 2026-07-12 (w6 follow-ups).** Webhook verify now computes every configured key's MAC with no early return (timing can't reveal which key matched); the internal tenant-API bearer uses crypto/subtle.ConstantTimeCompare instead of string ==.

# Constant-time comparison hardening — from w6/m6/t003 + t005

Two secret comparisons are correct but not constant-time end-to-end. Neither is
an exploitable bypass; both are defense-in-depth polish.

1. **Webhook either-key path** (`lego/backend/internal/apps/webhook.go:62-69`).
   Each key's MAC is compared with `hmac.Equal` (constant-time), but the loop
   returns on the **first** valid signature, so a remote timing analysis could in
   principle distinguish "the manual key matched" from "the GitHub-App key
   matched." That reveals only which of two server-held keys verified a delivery
   — it cannot forge a signature or recover a key. **Fix:** compute both MACs
   unconditionally and OR the results with a constant-time OR.

2. **Internal tenant-API bearer** (`lego/backend/internal/store/api.go:351-357`).
   `r.Header.Get("Authorization") != "Bearer "+a.Token` is a plain string
   compare (timing side-channel on `BEX_CP_TOKEN`). No value leaks (the 401 body
   is a static string), and the API is documented cluster-internal (no Ingress).
   **Fix:** `subtle.ConstantTimeCompare([]byte(got), []byte(want)) != 1`.

Source: w6/m6/t003 (webhook) + t005 (internal token), 2026-07-11.
