Carved out from #26 because the fix needs work that isn't in scope yet.
What
/api/console/signup returns 409 email_taken for existing accounts vs 201 for fresh ones. A botnet within the 10/15min/IP limiter can enumerate the tenant directory (high signal for spear-phishing).
Why we didn't close this in PR for #26
The byte-identical fix per the security review is: return 202 { message: "If new, check your inbox" } regardless of whether the email exists, then send a verification link out-of-band. We have no email infrastructure yet (no SES / Postmark / SMTP wired up), so the verification link can't actually be sent.
The interim option ("uniform 400 invalid_request") also leaks because fresh signups still return 201 — the status-code split is the channel, not the body.
What needs to happen first
- ADR for email service adoption (likely Postmark or AWS SES). Track via
dep-add skill.
- Wire email delivery + a
verification_tokens table.
- Then this issue's actual fix: return uniform 202; emit the verification link on a worker; let the user complete signup by clicking the link.
Severity holding pattern
This stays at Medium. It's not exploitation-imminent (any pilot buyer doing security diligence today would flag it, but no buyer is in diligence yet). Hard deadline: before first pilot SOW signing.
🤖 Generated with Claude Code
Carved out from #26 because the fix needs work that isn't in scope yet.
What
/api/console/signupreturns 409email_takenfor existing accounts vs 201 for fresh ones. A botnet within the 10/15min/IP limiter can enumerate the tenant directory (high signal for spear-phishing).Why we didn't close this in PR for #26
The byte-identical fix per the security review is: return 202
{ message: "If new, check your inbox" }regardless of whether the email exists, then send a verification link out-of-band. We have no email infrastructure yet (no SES / Postmark / SMTP wired up), so the verification link can't actually be sent.The interim option ("uniform 400 invalid_request") also leaks because fresh signups still return 201 — the status-code split is the channel, not the body.
What needs to happen first
dep-addskill.verification_tokenstable.Severity holding pattern
This stays at Medium. It's not exploitation-imminent (any pilot buyer doing security diligence today would flag it, but no buyer is in diligence yet). Hard deadline: before first pilot SOW signing.
🤖 Generated with Claude Code