Password policy
Olympus's defaults and how to tune them
Olympus uses Kratos's Argon2id hashing plus the HIBP breach check. Strong defaults; tune to your audience.
Defaults
| Setting | Default | Source |
|---|---|---|
| Min length | 8 characters | Kratos default |
| Hashing | Argon2id (memory=64MB, iterations=3, parallelism=4) | Kratos default |
| Breach check | HaveIBeenPwned k-anonymity | Olympus addition |
| Composition rules | None (no "must include digit + upper" by default) | Kratos default |
| Password history | Off (no remembering of previous passwords) | Kratos default |
| Expiration | None | Kratos default |
Modern guidance (NIST SP 800-63B)
The 2017 NIST guidance (still current in 2024):
- Length ≥ 8 chars. NIST allows up to 64+.
- No composition rules (don't require digit/symbol).
- Breach checks (HIBP-style).
- No rotation without cause.
Olympus matches this.
Tuning length
selfservice:
methods:
password:
config:
min_password_length: 12 # stricter than defaultFor admin (IAM) accounts, recommend 14+ characters.
Composition rules (anti-pattern)
Resist the temptation to add "must contain digit/symbol/upper" rules. They drive predictable patterns ("Password1!", "Spring2024!") and increase phishing-substitute risk.
If your auditor insists, add via a custom hook:
selfservice:
flows:
registration:
after:
password:
hooks:
- hook: web_hook
config:
url: https://your-backend/internal/validate-password
# Reject if too simpleBreach check
Olympus's HIBP integration is enabled by default. Disable only if:
- You're in an air-gapped environment.
- Your audience hates the friction.
// hera/src/lib/breach-check.ts disabled via env
HIBP_DISABLED=truePassword history
To prevent reuse, store SHA-256 hashes of the last N passwords. On password change:
const newHash = sha256(newPassword);
const recent = await db`SELECT old_password_hash FROM password_history WHERE identity_id = ${id} ORDER BY changed_at DESC LIMIT 10`;
if (recent.some(r => r.old_password_hash === newHash)) {
throw new Error("Cannot reuse a recent password");
}
await db`INSERT INTO password_history (identity_id, old_password_hash, changed_at) VALUES (${id}, ${newHash}, NOW())`;Tradeoff: storing hashes of old passwords creates a small new risk surface. Not currently shipped in Olympus.
Rotation
NIST says: only rotate on evidence of compromise. Routine rotation (every 90 days) is now considered counter-productive.
If your compliance regime requires rotation (some HIPAA / PCI interpretations), enforce via a hook that checks password_changed_at against a threshold and forces a settings flow.
Hashing tuning
Argon2id defaults: 64MB memory, 3 iterations, 4 parallelism. Adjust in kratos.yml:
hashers:
algorithm: argon2id
argon2:
memory: 131072 # 128 MB
iterations: 3
parallelism: 4
salt_length: 16
key_length: 32Higher memory makes brute-force expensive. Test on your VPS, too high and login latency increases.