Changing a password leaves every existing session and API token valid #334
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the security-audit skill (run-1, 2026-10-20). Source-read only: nothing was run, so this is a lead that still needs its local check, not a confirmed finding. Two independent verifiers did not disprove it.
The only password-change path is the operator CLI
ipx user passwd(main.rs:501-506), the step taken when an account's password is suspected compromised. It calls Db::set_password, which updates users.pass_hash only. Sessions issued with the old password stay in the sessions table, and session_user refreshesseenon every use, so a session an attacker keeps using never reaches the idle cutoff. API tokens minted by that session also survive the reset. The account owner can list and revoke their own API tokens in the web UI (GET/DELETE /api/tokens, web.rs:43-44, 603-648), so token cleanup is possible by hand. No route lists or ends a user's other sessions: logout deletes only the session presented (web.rs:328-331). Short of deleting the account or editing the database, a stolen session cookie outlives the password reset. Impact is limited to the compromised account's own authority, which is admin if the account is an admin.Claimed root cause. Db::set_password (db.rs:2060-2062) changes users.pass_hash without deleting the user's rows in sessions (or api_tokens). delete_user (db.rs:2092-2096) does delete them, and nothing else invalidates sessions per user.
Local check. {'local': 'In a sandbox, add a db.rs unit test on in-memory SQLite with a dummy user u: create_session(u, "s1"), create_api_token(u, "t", api_token_hash("x")), then set_password(u, hash_password("new password here")). Assert that session_user("s1", 86400) and api_token_user(api_token_hash("x")) both return None. On the current code both return Some(u), which demonstrates the gap. Fix: in set_password, or a change_password that UserCmd::Passwd calls, delete sessions where user_id = u in the same call, and print that the user's sessions were ended. Decide separately whether to revoke API tokens too, or tell the operator they remain and can be revoked in Settings.', 'deployment': 'Owner-observed and non-destructive, on a scratch instance only: sign in as a dummy account in one browser, run
ipx user passwd <dummy>from the CLI, then reload the first browser. If it is still signed in with no new sign-in, the finding is reproduced.'}Owner check (no probing). Owner-observed and non-destructive, on a scratch instance only: sign in as a dummy account in one browser, run
ipx user passwd <dummy>from the CLI, then reload the first browser. If it is still signed in with no new sign-in, the finding is reproduced.Full detail:
/config/security-audit-skill/ipx/run-1/NEEDS-VALIDATION.md(fingerprintcrates/ipx/src/main.rs:UserCmd::Passwd:sessions-and-api-tokens-not-revoked).Fixed in
37c0819: Db::set_password deletes the account's sessions, soipx user passwdsigns it out of every browser. API tokens are kept (removing them would break scripts on every reset); the command says how many still work and points to Settings to remove them. Deployed.