Changing a password leaves every existing session and API token valid #334

Closed
opened 2026-10-23 05:26:51 -07:00 by rays · 1 comment
Owner

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 refreshes seen on 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 (fingerprint crates/ipx/src/main.rs:UserCmd::Passwd:sessions-and-api-tokens-not-revoked).

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 refreshes `seen` on 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` (fingerprint `crates/ipx/src/main.rs:UserCmd::Passwd:sessions-and-api-tokens-not-revoked`).
rays added the bug label 2026-10-23 05:26:51 -07:00
Author
Owner

Fixed in 37c0819: Db::set_password deletes the account's sessions, so ipx user passwd signs 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.

Fixed in 37c0819: Db::set_password deletes the account's sessions, so `ipx user passwd` signs 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.
rays closed this issue 2026-10-23 07:47:03 -07:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rays/ipx#334