Olympus Docs
SecurityIdentity protection

Breached Password Check

HaveIBeenPwned k-anonymity password breach check on registration and password change

Overview

Hera checks passwords against the Have I Been Pwned (HIBP) dataset during registration, password reset, and password change flows. If the password appears in a known data breach, the user is warned. The check uses the k-anonymity API, the user's password is never sent to HIBP or any third party.

V1 ships warn mode only. Block mode (where Kratos rejects the password outright via passwordPolicyURL) is deferred to V2 pending the Kratos dynamic config reload mechanism (ADR-003).

An admin enables or disables the feature via the security.breached_password.enabled setting in Athena. The feature is enabled by default.


How It Works

K-Anonymity Implementation

The breach check is implemented entirely server-side in src/lib/breach-check.ts. The algorithm:

  1. Compute the SHA-1 hash of the password (server-side only, never in the browser)
  2. Send only the first 5 uppercase hex characters of the hash to HIBP's range API: GET https://api.pwnedpasswords.com/range/{5-char-prefix}
  3. HIBP returns a list of hash suffixes (the remaining characters after the 5-char prefix) with occurrence counts
  4. Compare the remainder of the SHA-1 hash locally against the returned suffixes
  5. If a match is found, the password is breached; if not, it is clean

The full SHA-1 hash and the plaintext password never leave the Hera server. A 5-character hex prefix narrows the password space to approximately 500 candidates, HIBP cannot determine which specific password was checked.

Hera server                              HIBP API
    │                                       │
    │  SHA-1(password) = AABBCC...          │
    │  prefix = "AABBC"                     │
    │                                       │
    │  GET /range/AABBC ─────────────────→ │
    │                                       │
    │  ←──────── CC...1234:5               │
    │            DD...5678:2               │
    │            (suffix list)             │
    │                                       │
    │  Compare locally: does "C..." match? │
    │  → breached: true / false            │

Warn vs Block Mode

The admin configures behavior via the security.breached_password.mode setting:

Warn mode (V1, active):

  • An inline warning appears below the password field after the first submission
  • "This password has appeared in known data breaches. We recommend using a unique password with a password manager."
  • The user can submit again to proceed, the warning is advisory, not blocking
  • Hera uses a double-submit pattern: first submission shows the warning; second submission (with confirmation) proceeds to Kratos

Block mode (V2, deferred):

  • Block mode uses Kratos's passwordPolicyURL mechanism: Kratos calls POST /api/policy/password on Hera during registration. Hera returns 200 (allow) or 422 (reject with a Kratos validation error).
  • Block mode depends on the dynamic Kratos config reload mechanism defined in ADR-003, which is not yet resolved.
  • The /api/policy/password endpoint exists in Hera and is protected by a Caddy deny rule (returns 404 to external callers). Do not remove this deny rule.
  • Do not implement custom block logic in Hera application code outside the /api/policy/password endpoint. Block mode routes entirely through Kratos's policy enforcement.

The security.breached_password.mode setting key is stored in V1 but the admin UI shows "Block mode available in a future update." If the setting is set to "block" via direct SDK write, the current Hera implementation falls back to warn-mode behavior and logs a server-side warning.

Fail-Open Behavior

If the HIBP API is unreachable (network error, timeout, unexpected response format), BreachCheckError is thrown. Hera catches this, emits a structured log entry, and allows the user to proceed without showing any error.

Structured log entry (required, must emit on every BreachCheckError):

{ "event": "hibp_check_failed", "severity": "warn", "reason": "<error message>" }

Configure a log aggregation alert on event: "hibp_check_failed" to detect sustained HIBP outages. Silent fail-open without monitoring violates SOC2 availability controls.

This is an explicit security trade-off: registration UX continuity is prioritized over enforcement when HIBP is unavailable. The fail-open behavior is documented as an accepted operational risk.


Configuration

The feature is controlled by two admin settings, managed in Athena:

Setting KeyDefaultValuesDescription
security.breached_password.enabled"true""true" / "false"Enable or disable the breach check entirely
security.breached_password.mode"warn""warn"Behavior when a breach is detected. "block" reserved for V2.

Settings are read at request time using the SDK's getSettingOrDefault pattern with a 60-second cache TTL. When an admin disables the feature, the change takes effect within 60 seconds across all Hera instances.


Where the Check Runs

The breach check runs in Hera server actions (not in the browser) at the following points:

FlowFileWhen
Registrationsrc/app/registration/actions.tsAfter CAPTCHA check, before Kratos submission
Password resetsrc/app/reset-password/actions.tsAfter password input validation, before Kratos submission
Account settings (password change)FutureWhen account settings password change is implemented

The check is skipped entirely when security.breached_password.enabled is "false".


UI Behavior

Registration and Password Reset (Warn Mode)

  1. User submits the form
  2. Hera server action calls checkBreachedPassword(password) if enabled
  3. If breached: the action returns { breachWarning: true }, the form re-renders with an inline warning below the password field
  4. The user can read the warning and submit again, the second submission includes a hidden bypass_breach_warning: "true" field
  5. On second submission, Hera proceeds to Kratos regardless of the breach result

Warning copy (verbatim):

This password has appeared in known data breaches. We recommend using a unique password with a password manager.

The warning text does not reference "HIBP" or "Have I Been Pwned" by name. It does not reveal technical details of the check.


Edge Cases

User submits the same breached password twice

First submission shows the warning. Second submission proceeds. Hera does not repeatedly warn on the same session, the double-submit pattern clears the warning state after the second attempt.

HIBP unreachable during the second submission

If HIBP is unreachable on the second submission, the check still fails-open and Kratos receives the form data normally.

Admin changes mode from warn to block

The warn mode is currently the only active mode. Block mode implementation is deferred. If security.breached_password.mode is set to "block" via Athena, the current implementation reads the value but falls back to warn mode behavior with a server-side log warning until block mode is implemented.


Security Considerations

  • The SHA-1 hash is computed in src/lib/breach-check.ts, a server-only file with no "use client" directive. It must never be imported in a client component. If imported in a client component, the hash computation (and indirectly the password) would run in the browser and could be exposed.
  • The HIBP API call uses HTTPS only. Hera's Node.js runtime must not have NODE_TLS_REJECT_UNAUTHORIZED=0 set in production, this would disable TLS certificate verification and allow MITM attacks on the HIBP response.
  • The in-process LRU cache (1,000 entries max, LRU eviction, 60-minute TTL) stores HIBP responses by 5-character SHA-1 prefix. Each Hera container has its own cache, CIAM and IAM instances do not share cache state. A password whose prefix was cached as "safe" may not be detected as breached for up to 60 minutes after HIBP updates. This is a known limitation consistent with HIBP's API design guidance and acceptable for warn mode.
  • GDPR: Passwords are never sent to HIBP. Only a 5-character SHA-1 prefix is transmitted, this is not reversible to the user's specific password and does not constitute personal data processing requiring separate consent.
  • SOC2: Fail-open on HIBP outage is documented as an accepted operational risk. The on-call engineer is alerted via the server-side log entry when HIBP is unreachable.

  • hera#27, Breach warning UI implementation
  • platform#12, Platform layer: Caddy deny rule for /api/policy/password, V2 Kratos config reference
  • sdk#4, Closed: HIBP logic is in Hera, not the SDK

Last updated: 2026-04-02 (Technical Writer, platform#12 documentation)

On this page