DET-AUTH-001 — passkey enumeration
Rule ID: DET-AUTH-001
Title: Passkey enumeration via distinct-email cardinality at /api/auth/passkey/begin-assertion (and /api/auth/register/options)
Category: auth
Last validated: 2026-06-04 (initial catalog, dormant — no baseline yet)
State: dormant — activate once Heroku Logplex → log-aggregation drain is wired and 7 days of traffic populate a baseline
Telemetry source
- App log lines from Raptor app (
backend_v2/api/routes/auth.py): /api/auth/passkey/begin-assertion(POST)/api/auth/login/options(POST) — companion endpoint, same enumeration signature/api/auth/register/options(POST) — anti-enum 202 path, but enumeration attempts still log- Expected log destination once wired: Heroku Logplex → archival drain → queryable store. Today: only
heroku logs --tailon the live dyno. - Per-line fields needed: client IP (from
X-Forwarded-Forafter Cloudflare), email hash (route logsemail_hash=...), timestamp.
Statistical method + baseline window
- Method: cardinality drift on distinct
email_hashvalues per source IP per rolling 60-second window. - Baseline window: rolling 7 days, time-of-day-aware (US trading hours vs off-hours bucket).
- Fire condition: N distinct emails from one source IP within 60s where N exceeds the 99.9th percentile of the IP-class baseline, AND N >= 5 absolute floor (prevents single-real-user-with-typo-burst from tripping the rule).
- Sample size requirement: detection only fires when total per-IP request count for that 60s window >= 5. Below 5, statistical confidence is insufficient.
Threshold + expected FP rate
- Pre-launch placeholder threshold: N >= 10 distinct email hashes per IP per 60s. Conservative; will likely never fire pre-launch.
- Post-launch dynamic threshold: μ + 4σ of the 7d rolling baseline.
- Expected FP rate (post-launch): < 1 per week. Real-user retries on a single email do not trip this rule (cardinality is on distinct emails, not request count).
Alert route
- CRITICAL (active enumeration burst, N >= 20 distinct emails per IP per 60s OR repeat fires from same IP within 1 hour):
#raxx-ops-alert-sev1perproject_oncall_severity_routing. - HIGH (single fire, N below the CRITICAL ceiling):
#raxx-ops-alert-sev2-5during ET 12:00–22:30 UTC;#raxx-ops-alert-sev2outside. Per-event despite pre-launch digest posture — credential-enumeration is the explicit exception infeedback_pre_launch_digest_notifications. - MEDIUM (suspicious but sub-threshold trend, observed by daily roll-up): ops@ daily digest.
Escalation owner
- security-agent — adversary-shaped behavior; potential credential-stuffing campaign. Hand off the IP + email-hash set + window timestamp.
- operator if the IP resolves to a known good source (operator's VPN per
user_uses_vpn— cross-check before paging).
Test fixture / synthetic positive
See _fixtures/passkey_enumeration_positive.json for a synthetic 15-event burst against /passkey/begin-assertion from one IP across 12 distinct synth emails within 38s.
What to do when this fires
- Pull the IP's full request log from the last 24h. Look for prior probe patterns (slow drip, then burst).
- Cross-reference the IP against Cloudflare WAF events (
waf_eventstable whenwaf_eventsflag is on). - If the IP overlaps with a residential ISP range or commercial VPN block (per
user_uses_vpn), verify against operator's known VPN exits before paging. - If the email-hash set includes any real customer or waitlist email, escalate to CRITICAL immediately — the attacker has a partial list.
- Hand the cluster to security-agent for posture review (rate limit tuning, CF challenge rule, etc.). Do not silence the rule during investigation per charter §Anti-patterns.
What NOT to do
- Do not narrow the rule because of one FP fire during operator testing. Add the operator's VPN exit IPs to an allowlist instead.
- Do not auto-block the IP from this detection alone. Block decisions go to security-agent + operator. This rule is a detector, not an enforcer.
- Do not extend the rule to count failed assertions — failures look identical to successes at this seam (anti-enumeration design); cardinality of distinct emails is the signal, not failure count.