Skip to content

fix: scope MD5 PASSWORD_HASHERS override to test runs only#1096

Open
mgradalska wants to merge 1 commit into
karrioapi:mainfrom
mgradalska:fix/password-hashers-production-override
Open

fix: scope MD5 PASSWORD_HASHERS override to test runs only#1096
mgradalska wants to merge 1 commit into
karrioapi:mainfrom
mgradalska:fix/password-hashers-production-override

Conversation

@mgradalska

Copy link
Copy Markdown

refs https://github.com/orgs/karrioapi/discussions/1094

Bug

After upgrading past 2026.1.22, existing users can't log in to the dashboard, and new users have their passwords stored as MD5.

Root Cause

The test-only PASSWORD_HASHERS override in [settings/base.py](https://github.com/karrioapi/karrio/blob/main/apps api/karrio/server/settings/base.py#L377-L383) (added in 2026.1.22, commit f19fe206e) fires in production because its second predicate matches the install path:

if "test" in _sys.argv or "karrio" in _sys.argv[0]:

In karrio/server, gunicorn's sys.argv[0] is /karrio/venv/bin/gunicorn - which contains "karrio". The override fires unconditionally.

Fix

Replace the argv predicate with a custom TEST_RUNNER that sets PASSWORD_HASHERS in setup_test_environment. Django loads TEST_RUNNER only during karrio test (the test management command), so production is never affected and the test-suite perf win from f19fe206e is preserved.

Tests

Couldn't run the full suite locally. Relying on this PR's karrio-tests workflow.

Notes

Already MD5-hashed users (created on any 2026.1.22+ deploy) will be locked out by this fix the same way PBKDF2 users were locked out by the original bug. Worth shipping a release that appends MD5PasswordHasher to PASSWORD_HASHERS.

The previous predicate (`"karrio" in _sys.argv[0]`) matches the install
path of the `karrio` virtualenv (`/karrio/venv/bin/gunicorn`), so the
test-only MD5 PASSWORD_HASHERS override was firing in production
gunicorn workers as well. Two consequences for any deployment upgraded
past 2026.1.22:

- Pre-upgrade users with PBKDF2-hashed passwords can no longer log in,
  because the loaded hasher list cannot verify their hash format.
- New users (createsuperuser, dashboard signup) have their passwords
  stored as MD5, which Django itself documents as test-only.

Replace the argv-based predicate with a custom TEST_RUNNER subclass that
sets PASSWORD_HASHERS in `setup_test_environment`. Django loads
TEST_RUNNER only for `manage.py test` / `karrio test`, so the override
fires exactly when intended and never touches production process state.

refs https://github.com/orgs/karrioapi/discussions/1094
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

@mgradalska is attempting to deploy a commit to the karrio Team on Vercel.

A member of the Team first needs to authorize it.

@ChrisNolan

Copy link
Copy Markdown
Contributor

Getting the dev environment setup is a bit daunting, but the docs here are the current ones afaik and using the ./bin/install-dev worked well for me recently when I got back into fixing things. If you want to run the tests fully too just in case.

Nice to see more PRs imo 🫂 #1094

If It was me... I'd wonder if anything would need to be done from a data standpoint for systems that might have gotten some messed up users in the interim... or if we just trust those users to be able to re-setup the users which have the wrong hashing...?

@ChrisNolan

Copy link
Copy Markdown
Contributor

FYI I used

sudo docker exec -it karrio.db psql -U postgres -d db -c "
select email, is_staff, is_superuser, is_active, left(password, 80) as password_prefix
from \"user_user\"
order by email;
"

To 'see' the hash used for the existing passwords to help me debug after upgrading without this PR and also not being able to login -- I saw pbkdf2_sha256 in the password prefix. After applying this PR, without any data changes I was able to login.

Thanks for the save mgradalska! Hopefully it'll get merged soon so others don't have the same struggle.

@mgradalska

Copy link
Copy Markdown
Author

@ChrisNolan Honestly, I don't think there's a clean automated remediation worth shipping here. Anything we'd do (force reset, append MD5 as a verifier, etc.) is an operator-policy call rather than a library decision.

For my own deployment I just created a fresh user once we hit this - that was an acceptable solution in my case. But I imagine this can be quite problematic for anyone with lots of users - for those MD5PasswordHasher could be appended at the end of PASSWORD_HASHERS so existing accounts continue to verify. Django then auto-rehashes to PBKDF2 on next successful login.

A note in the changelog calling out that user passwords created on 2026.1.22+ may be stored as MD5 (and the available migration paths) feels like the right level of disclosure to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants