| Round-1 finding | Disposition | Rev-2 assessment |
|---|---|---|
| 1. Login CSRF | fixed | §5.1 requires a confirmation nonce and request-origin checks, including legacy confirmation. |
| 2. Unlimited guessing budgets | fixed | §5 adds shared online guessing limits and creates no redeemable challenge for refused requests. |
| 3. Pending-action hash mismatch | fixed | §6 specifies redemption by stored hash with the existing safeguards. |
| 4. SMTP fallback to Keymail | fixed | §4 adopts the settled B4 classification; §11 applies that policy to notifications. |
| 5. First CLI step-up email suppressed | fixed | §§5.3 and 7 implement B2: fresh email sign-in counts as step-up, with separate step-up send limits. |
| 6. Challenge outlives pending action | fixed | §6 gives step-up challenges the pending action’s ten-minute lifetime. |
| 7. Recipient validation | fixed | §11 requires normalized, strictly parsed bare addresses throughout. |
| 8. `next` bypasses onboarding | fixed | §8 requires account selection or creation before following `next`. |
| 9. Existing SES identity ownership | fixed | §9 explicitly requires the adoption check and ownership TXT record. |

Reviewed spec rev 2 at `ac21aa3b`. These dispositions assess the design, not completed implementation.

1. **P1 — Read-only challenge-table access permits offline code recovery and account takeover.**

   **Spec:** §5, particularly [the hash definition and database-read guarantee](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/email-signin/docs/superpowers/specs/2026-10-21-console-email-signin-design.md:143).

   **Evidence:** [server.go:2733](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/email-signin/internal/console/server.go:2733) takes the target address from an unauthenticated requester. Existing session hashes protect a 32-byte random secret generated at [store.go:2394](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/email-signin/internal/console/store.go:2394); the proposed code hash instead protects six digits using an ID known to that requester.

   **Failure:** An attacker requests a challenge for the victim and retains their own challenge cookie. With read-only access to the resulting database row, they enumerate `sha256(id || code)` offline, then submit the recovered code once. Neither online guessing limit helps. An in-memory reproduction recovered a sample code in under one second. This violates the explicit database-read guarantee without requiring mailbox access.

   **Fix:** Authenticate the code digest with a server secret stored outside the database, such as HMAC over the challenge ID and code. Retain the online limits.

2. **P2 — The new guessing limit lets one browser invalidate another browser’s valid links.**

   **Spec:** [§5’s address-wide challenge deletion](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/email-signin/docs/superpowers/specs/2026-10-21-console-email-signin-design.md:167).

   **Evidence:** The request entry point at [server.go:2730](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/email-signin/internal/console/server.go:2730) requires no session or mailbox proof. The existing redemption precedent deletes only the presented token at [store.go:2486](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/email-signin/internal/console/store.go:2486); rev 2 explicitly expands deletion to every live sign-in challenge for the address.

   **Failure:** An attacker creates challenges for a victim in attacker-controlled browsers and exhausts the shared wrong-code budget. That burns a legitimate link already delivered to the victim’s separate browser, despite the attacker never possessing that link. Three challenge requests, spaced to respect the cooldown, suffice to exceed ten wrong guesses.

   **Fix:** Keep the shared code-verification limit, but preserve other browsers’ high-entropy links. Burn only the offending challenge; allow existing valid links to redeem while code verification is throttled.

No files were edited. No repository test suites were run.