Securityservice never stores a password as typed

FixSecurityService
Shipped
September 17, 2026 at 4:26 AM UTC
Author
Kamo
Commit
6fed61b

Two writers put plaintext into USERS.PASSWORD. Both called User.encodePassword(), which needs a static encoder that only initializerservice ever sets **************** has carried the setter commented out, "uncomment if available", since the initial commit). In this service it throws, and both callers swallowed the exception: - Claiming a pre-created account at /api/security/register set the typed password and saved it as typed. The account could not even sign in with it: the PBKDF2 matcher cannot read a value that is not its own hex, so login failed with "Authentication failed: <the decoder's exception>". Now **************** stores passwordEncoder.encode(chosen), with the injected platform encoder; without one the registration fails. - Adding a member (singly or in bulk) stored a random 24-character placeholder. Nobody was ever told it, so it now stores no password at all: login already refuses a null hash cleanly, a claim or a reset sets the real one, and a hundred-row bulk add does not pay PBKDF2 for throwaway values. Five such rows were on the live database on 2026-09-17 (by shape; no value was read). The other writers (RegistrationService, PasswordChangeController, RecoveryServiceImpl) always injected PasswordEncoder. PasswordChangeController's self-change now answers "Current password is incorrect" for an account with no password instead of a 500 (PBKDF2 matches() throws on null). The login debug line that printed the first ten characters of the submitted password is gone. **************** sets the five placeholders to NULL, the state the System User row is already in, matching them by shape only (the placeholder alphabet, 24 characters, never signed in, not the System User). A count preview on 2026-09-17 matched 5 and left 0 non-hash rows out. Run it after this deploys. Tests: **************** (the claim through the real PasswordEncoderConfig encoder: 96-hex, the login matcher accepts it, a NULL placeholder is still claimable, an active account keeps its password, no encoder fails loudly); **************** a PBKDF2 null-password case in PasswordChangeAuthTest; and **************** which scans this service for any setPassword/.password write that is not encode(...) or null, and for any encodePassword() call. Each fails against its mutant (plaintext claim, plaintext placeholder, the swallowed encodePassword restored, the null guard removed). Full suite 2662: the only reds are the five already on origin/main **************** x4, the ratchet's ****************

All changes

Like what you see shipping?

All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.

Start Free ForeverView Pricing