Password policy
Every password on create / change must satisfy:
- Length - at least
Z4J_PASSWORD_MIN_LENGTHcharacters (default 8). Max 256. - Character classes - at least 3 of 4 of: lowercase, uppercase, digit, symbol.
- Not in denylist - a ~1,500-entry list of common passwords and breached patterns.
Enforcement points
Section titled “Enforcement points”The same validate_policy() runs on every server-side write path
that accepts a plaintext password:
- First-boot setup form (
POST /setup) - CLI bootstrap (
z4j bootstrap-admin --password-stdinorz4j createsuperuser --password-stdin) - Login-time admin creation via
Z4J_BOOTSTRAP_ADMIN_PASSWORD - Self-service signup via project invitation (
POST /invitations/accept) - Self-service password change (
POST /auth/change-password) - Admin-driven password reset (
POST /auth/reset-password) - Admin user create (
POST /users) - Admin user password set (
PATCH /users/{id}/password)
There is no path that bypasses the policy. The dashboard’s client-side strength meter is advisory; the server is the authority.
Denylist composition
Section titled “Denylist composition”- Top 1,000 common passwords (rockyou, SecLists breach compilations).
- Seasonal patterns (
Summer2024,Winter24, etc.) with year variants for the past 10 years. - Product-name variants for common SaaS (
<product>123,<product>!1).
Generated via _generate_patterns() in z4j_brain.auth.common_passwords. Static; baked at build time.
argon2id, OWASP 2024 defaults:
time_cost=3memory_cost=64 MiBparallelism=4- 32-byte hash, 16-byte salt
Tuneable via Z4J_ARGON2_* env vars. Target: 50-80 ms per verify on a 2024 server CPU.
Rehash on login
Section titled “Rehash on login”If stored-hash parameters are weaker than current config, z4j rehashes on successful verify. Silent upgrade for users who were around before a parameter bump.
Error codes
Section titled “Error codes”password_too_short(< min length)password_too_long(> 256)password_too_simple(< 3 of 4 classes)password_in_breach_list(matches denylist entry)
All are 422-returned from the password-setting endpoints.
Timing
Section titled “Timing”The wrong-username branch of login verifies against a dummy hash generated at brain boot. Takes the same wall time as a real verify, so timing doesn’t disclose account existence.
Why 3-of-4 (not a fixed regex)
Section titled “Why 3-of-4 (not a fixed regex)”An audit flagged the old “letter + digit” rule: Summer24 and qwertyui1 both passed, both are in the top 1,000. 3-of-4 classes + denylist knocks out the long tail of dictionary-plus-one-digit passwords.
Password strength meters
Section titled “Password strength meters”The dashboard’s password input shows a strength indicator (zxcvbn-like) as guidance - but the server is authoritative. The meter can show “strong” for a password the server rejects because of denylist match, and that’s fine.