Lemur manages TLS certificate creation. Prior to 1.9.2, lemur.users.service.update assigned a replacement password directly to users.password, while lemur/users/models.py registered User.hash_password only for the before_insert event. Because no before_update listener ran, administrator-initiated password changes through PUT /api/1/users/ were committed as plaintext. The affected user could no longer authenticate normally because bcrypt verification received an unhashed value. A database, backup, replica, query-log, or administrative read compromise exposed immediately usable credentials without offline cracking. The fix registers hashing for before_update and avoids rehashing values that already have a bcrypt prefix. This issue is fixed in version 1.9.2.
Affected: PyPI: lemur
## Summary
`lemur.users.service.update()` writes a user's new password as plaintext to the `users.password` column. The `User` model wires bcrypt hashing to SQLAlchemy's `before_insert` event but registers no equivalent listener for `before_update`, and `service.update()` does not call `user.hash_password()` after assigning the new value. Every password change performed through the admin-gated `PUT /api/1/users/<id>` endpoint persists the user's password to the database in cleartext.
## Root Cause
`lemur/users/models.py`:
```python # line 38 class User(BaseModel): __tablename__ = "users" id = Column(Integer, primary_key=True) password = Column(String(128)) # plain column, no setter, no Vault descriptor
# line 74 def hash_password(self): if self.password: self.password = bcrypt.generate_password_hash(self.password).decode("utf-8")
# line 111 listen(User, "before_insert", hash_password) # only before_insert is wired ```
`lemur/users/service.py`:
```python # line 46 def update(user_id, username, email, active, profile_picture, roles, password=None): ... user = get(user_id) user.username = username user.email = email user.active = active user.profile_picture = profile_picture if password: user.password = password # raw assignment update_roles(user, roles) return database.update(user) # commits, no hashing ```
No `before_update` listener exists. `User.pas…
Affected: PyPI: lemur
## Summary
`lemur.users.service.update()` writes a user's new password as plaintext to the `users.password` column. The `User` model wires bcrypt hashing to SQLAlchemy's `before_insert` event but registers no equivalent listener for `before_update`, and `service.update()` does not call `user.hash_password()` after assigning the new value. Every password change performed through the admin-gated `PUT /api/1/users/<id>` endpoint persists the user's password to the database in cleartext.
## Root Cause
`lemur/users/models.py`:
```python # line 38 class User(BaseModel): __tablename__ = "users" id = Column(Integer, primary_key=True) password = Column(String(128)) # plain column, no setter, no Vault descriptor
# line 74 def hash_password(self): if self.password: self.password = bcrypt.generate_password_hash(self.password).decode("utf-8")
# line 111 listen(User, "before_insert", hash_password) # only before_insert is wired ```
`lemur/users/service.py`:
```python # line 46 def update(user_id, username, email, active, profile_picture, roles, password=None): ... user = get(user_id) user.username = username user.email = email user.active = active user.profile_picture = profile_picture if password: user.password = password # raw assignment update_roles(user, roles) return database.update(user) # commits, no hashing ```
No `before_update` listener exists. `User.pas…