Enhancement Proposal

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

  1. 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.

  2. Challenges are a pluggable trait; proof-of-work is the self-hostable default. A ChallengeProvider seam 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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 ChallengeProvider seam 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 acr policy (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.