Skip to content

Block reference — Risk & Security

Risk blocks score signals. They do not make access decisions on their own. The pattern is always the same: a risk block gathers evidence, writes a recommendation to state, and then a Decision block downstream acts on it — routing to MFA, to a Deny terminal, or straight through to Issue Session.

The platform ships seven risk and security blocks covering every layer of the signal stack: network (IP reputation), automated traffic (bot detection), credential hygiene (brute force, breached passwords), account quality (email validation), and policy compliance (password policy). Add Risk Evaluate when you want all signals collapsed into a single score. Add individual signal blocks when you need finer control — for example, blocking bots at the top of a flow before any credential check runs.

The platform's defaults are tuned for general-purpose CIAM. You compose them; you decide what each signal triggers. A block that reports severity: high does nothing by itself until a Decision block acts on that value.

  • "Aggregate all active signals into one score and branch on it" — Risk Evaluate
  • "Check this IP against threat feeds, VPN/Tor detection, and country denylist" — IP Reputation
  • "Is this User-Agent or request rate pattern a known bot?" — Bot Detection
  • "Has this identifier hammered us with failed logins?" — Brute Force Check
  • "Has this password appeared in a public breach?" — Breached Password Check
  • "Is this email address well-formed and not a throwaway domain?" — Email Validation
  • "Does this password meet our complexity rules?" — Password Policy

Block id: risk_evaluate  ·  Available in: Login, Registration, Password Recovery, MFA Step-up

Run all registered risk signals — Brute Force, IP Reputation, HIBP, Bot Detection — and aggregate them into a single severity level and numeric score. The block writes a recommendation (allow, challenge, or deny) to state and sets the routing branch key so a downstream Decision block can act without writing its own expression. It also writes event.authentication.risk_score so the risk score is visible anywhere in the flow, including to Custom Actions running later.

This block is the fastest way to add adaptive MFA to an existing login flow. Drop it into your Post-Auth stage, wire a Decision block on step.<your-slug>.recommendation, and route challenge to an MFA block.

Reads from state

  • event.user.id — identifies whose signals to query
  • event.request.ip — IP used for IP Reputation and Brute Force
  • event.request.user_agent — User-Agent passed to bot detection
  • event.request.device_fp — device fingerprint enriching the bot signal

Writes to state

  • step.<slug>.severity — aggregate risk level: none, low, medium, high, or critical
  • step.<slug>.score — numeric score 0–100 derived from all active signals
  • step.<slug>.recommendation — engine recommendation: allow, challenge, or deny
  • step.<slug>.reasons — list of signal IDs that contributed, e.g. ["brute_force", "vpn"]
  • step.<slug>.signal_results — detailed per-signal results for audit and debugging
  • event.authentication.risk_score — the same score written to the event namespace for downstream access

Use when — you want a single block to run all risk signals and produce one authoritative recommendation. Pair immediately with a Decision block branching on step.<slug>.recommendation.

Cautions — This block fails open: if individual signals are unavailable (for example, the HIBP service is unreachable), they contribute zero to the score rather than halting the flow. Check step.<slug>.signal_results in audit if you need to know which signals fired.


Block id: ip_reputation  ·  Available in: Login, Registration, Password Recovery, MFA Enrollment, MFA Step-up, and custom flows

Check the client IP against threat feeds, GeoIP country data, and ASN classification. You configure a per-classification policy — allow, challenge, or block — for each of VPN, Tor, datacenter, and bogon IPs. CIDR allow/blocklists provide unconditional overrides (useful for corporate office ranges). A country denylist lets you refuse traffic from specific regions outright.

The block sets a branch key that a downstream Decision block reads directly. You don't need to write your own expression; the engine routing key is already set.

Reads from state

  • event.request.ip — the client IP to evaluate (populated automatically at flow start)

Writes to state

  • step.<slug>.ip_reputation — overall reputation label: clean, suspicious, or blocked
  • step.<slug>.ip_classifications — list of ASN/behavioural labels that matched, e.g. ["vpn", "datacenter"]
  • step.<slug>.ip_threat_feed_hits — threat feed entries the IP matched
  • step.<slug>.ip_threat_categories — threat categories (malware, phishing, spam, etc.)
  • step.<slug>.ip_country — ISO country code, e.g. IN
  • step.<slug>.ip_asn — autonomous system number
  • step.<slug>.ip_asn_org — autonomous system name
  • step.<slug>.branch — routing key for a downstream Decision block

Use when — you want to challenge or block traffic from VPNs, Tor exit nodes, known malicious ASNs, or specific countries without writing custom logic.


Block id: bot_detection  ·  Available in: All flows

Check the incoming request for automated bot signals — headless browser User-Agents, unusual request-rate patterns, and known crawler signatures. The block is fail-open: if signal data is absent (for example, no User-Agent header) it writes detected: false and the flow continues.

Reads from state

  • event.request.user_agent — the request's User-Agent string
  • event.request.ip — used for rate-burst pattern analysis

Writes to state

  • step.<slug>.detectedtrue when the request matches bot patterns
  • step.<slug>.severity — confidence level: none, low, medium, or high
  • step.<slug>.reasons — signal IDs that triggered classification, e.g. ["ua_headless", "rate_burst"]
  • branch — routing key for a downstream Decision block

Use when — you want to short-circuit credential checks for clearly automated requests, or you want to log bot activity for threat analysis without blocking users.


Block id: brute_force  ·  Available in: All flows

Check how many consecutive failed login attempts have been recorded for this identifier within the current window. The block increments the counter on each failed attempt recorded upstream; the admin configures per-IP and per-identifier thresholds in the tenant's security settings. When a threshold is exceeded the block sets branch so a downstream Decision block can lock the account, require MFA, or prompt a CAPTCHA.

Reads from state

  • event.user.email (or the identifier supplied to the flow) — the account being checked

Writes to state

  • step.<slug>.severity — failed-attempt severity: none, low, medium, high, or critical
  • step.<slug>.count — number of consecutive failed attempts in the current window
  • step.<slug>.reasons — which limit was exceeded, e.g. ["per_ip_limit"]
  • branch — routing key for a downstream Decision block

Use when — you want to gate on failed-attempt history before a password check runs, or you want to trigger account lockout / CAPTCHA after N failures.


Block id: breached_password  ·  Available in: All flows

Check whether the supplied password appears in the HaveIBeenPwned breach corpus. The platform uses k-anonymity — only the first five characters of the SHA-1 hash are sent to the HIBP API, so the plaintext password and full hash never leave the platform. The block is fail-open: a network error or API timeout writes breached: false and the flow continues.

Reads from state

  • event.credentials.password — the password candidate submitted by the user

Writes to state

  • step.<slug>.breachedtrue when the password appears in the HIBP corpus
  • step.<slug>.severity — severity based on exposure count: none, low, medium, or high

Use when — you want to reject known-bad passwords at registration or login, or you want to prompt users to change a compromised credential without hard-denying them.

Cautions — The block never stores or logs the plaintext password. Audit records mark the password field as redacted. Place this block early in a registration flow so a policy violation is caught before the identity is created.


Block id: email_validation  ·  Available in: All flows

Validate an email address format and check it against a disposable-domain blocklist. The check runs entirely in-process — no external calls, no latency. You can extend the built-in blocklist by adding domains to the block's blocklist config field. Set fail_on_invalid: true to have the block deny the flow automatically when the address fails; leave it false to branch on step.<slug>.valid manually via a Decision block.

For example, Cymmetri blocks free-tier signups from throwaway email services by adding a short domain list to this block's config and routing step.<slug>.valid == false to a Deny terminal with a friendly message.

Reads from state

  • event.user.email — the address to validate

Writes to state

  • step.<slug>.validtrue when the address passes format checks and is not on the disposable-domain blocklist
  • step.<slug>.reason — why the address was rejected, e.g. "disposable_domain" or "invalid_format"; empty when valid

Use when — you want to reject disposable or malformed addresses before creating an account or sending a verification email.


Block id: password_policy  ·  Available in: All flows

Enforce password complexity rules — minimum length, uppercase letter, lowercase letter, digit, and symbol — against a candidate password. The check is pure in-process with no external calls. Configure the rules you want in the block's settings; leave a rule disabled to not check for it. When the password fails, step.<slug>.reasons lists the specific rules that were not met so you can surface a precise message to the user.

Reads from state

  • event.credentials.password — the password candidate to evaluate

Writes to state

  • step.<slug>.passedtrue when the password satisfies all configured complexity rules
  • step.<slug>.reasons — rules the password failed, e.g. ["min_length", "require_upper"]; empty when passed

Use when — you want to enforce a password policy at registration or when a user sets a new password during recovery.

Cautions — Place this block before any credential-set or identity-create block so a policy violation is caught before any write happens.


Drop Risk Evaluate into your Post-Auth stage. Add a Decision block immediately after it. In the Decision block, create two branches:

  • When step.risk-eval.recommendation == "challenge" — route to your MFA block
  • Default — route to Issue Session

Users who present clean signals go straight to a session. Users who present elevated signals are challenged. No hardcoded rules; the risk engine adapts as signals change.

At the top of your Registration flow — before identity_create runs — add Breached Password Check followed by a Decision block:

  • When step.breach-check.breached == true — route to a Deny terminal with the message "That password has appeared in a known data breach. Please choose a different one."
  • Default — continue to Email Validation, then Identity Create

This pattern stops compromised passwords from ever being stored. It costs one in-process check and has no false-positive risk because you're branching on true/false, not a score.