Proposal: Attack protection — bot detection, signup fraud, and credential-stuffing defense
Motivation
The moment a CIAM realm opens self-service registration (M4), its public
endpoints are farmed: scripted signups, credential stuffing against /token
and the login ceremony, recovery-flow abuse. The incumbents productize this
(Auth0 Attack Protection, Okta ThreatInsight); Vord's corpus has the
mechanisms scattered — layered rate limits (proposal 0006), breached-password
checks (proposal 0013), risk signals at issuance
(ADR-0031) — but
no product surface that composes them, and proposal 0006 left "proof-of-work vs
CAPTCHA vs device signals" as an open question. This proposal is that surface,
and it resolves that question.
Design
-
Enforcement lives at the issuance side only. Challenges, denials, and quarantine all happen at Modgud's ceremonies (ADR-0009) — registration, login, recovery — and at issuance policy (ADR-0031). Nothing here touches Vör: rule 1 keeps the verification path free of reputation lookups by construction.
-
Challenges are a pluggable trait; proof-of-work is the self-hostable default. A
ChallengeProviderseam offers: proof-of-work (built-in, no vendor, no user PII leaving the deployment) as the baseline, and hosted CAPTCHA providers (Turnstile, hCaptcha, reCAPTCHA) as opt-in per realm — each a dependency-audit row under the ADR-0008 tests, with the privacy cost of third-party challenge scripts named per ADR-0041. This resolves proposal 0006's open question: layered, PoW-first, CAPTCHA optional — never CAPTCHA-only, which would make a third party availability-critical for login. -
The deterministic layer ships at M4; the scoring layer rides M5. At M4 the controls are policy-evaluable without history: registration velocity per IP/prefix, disposable-email-domain and denylist checks, per-identifier and per-IP failure counters (enforced by proposal 0006's umbrella), the breached-password gate (proposal 0013), and challenge escalation. From M5 the risk pipeline of ADR-0031 consumes the event stream (ADR-0022) and upgrades these thresholds to scored decisions (impossible travel, device history, fleet-wide patterns). Same policy surface, richer inputs — realms configure responses, not engines.
-
Suspect accounts are quarantined, not silently deleted. A registration that trips the fraud policy lands in a quarantine state: the principal exists (directory write per ADR-0011) but is limited to contact verification (proposal 0003) until it clears. Quarantine is a directory state with an event trail, so false positives are recoverable and measurable — a fraud system that silently drops real users is itself an availability bug.
-
A realm can declare "under attack". A panic switch per realm — require a challenge for all interactive logins, tighten every velocity threshold — flipped via the admin API (ADR-0021), audited as an event, and auto-expiring (a stated duration, never a forgotten permanent state). Credential-stuffing waves are bursty; the defense posture should be too.
-
Every decision is an event. Challenges issued/passed/failed, quarantines, denials, panic-switch flips — all on the identity event stream (ADR-0022), feeding the audit projection, the M5 risk pipeline, and the security dashboard (proposal 0012 emits, proposal 0018 presents).
Alternatives considered
- A single hosted bot-detection vendor as the mechanism. Rejected: fails the ADR-0008 control-resilience test as the only path (a third party becomes availability-critical for login) — vendors are opt-in behind the trait, never the baseline.
- ML-first from day one. Rejected: before M5 there is no event history to score; deterministic controls are what M4 can honestly enforce, and they stay as the floor afterwards.
- Edge/WAF-level bot filtering. Out of scope: Vord composes behind a provider's WAF/CDN; shipping one would drag traffic filtering into the verification plane the constitution keeps stateless.
Graduation criteria
- Registration and login ceremonies enforce realm-configured challenge and velocity policy; a scripted-signup abuse-case test (ADR-0028) is defeated by the PoW baseline.
- A credential-stuffing replay against a realm trips the failure counters, escalates challenges, and emits the expected events — asserted as an abuse-case test.
- Quarantined principals can complete contact verification and clear; the false-positive path is tested, not assumed.
- The panic switch flips and auto-expires, audited.
Backward compatibility
- The
ChallengeProviderseam and the quarantine directory state are the reserved seats — both land with M4's schema and trait set; the scoring layer (M5) changes no interface, only inputs. - No token-format change: attack-protection outcomes surface as ceremony
behavior and
acrpolicy (ADR-0031), never new claims.
Open questions
- PoW difficulty economics on low-end mobile devices vs bot cost — needs measurement before the default is pinned.
- Is there an IP-reputation feed that passes the ADR-0008 tests (self-hostable or forkable), or does v1 ship with local lists only?
- Whether quarantine is expressed as a directory state (this draft) or purely as realm-policy predicate over signals — settled when the M4 schema is finalized.