CVE-2026-49229: @actual-app/sync-server Insufficient Session Expiration on User Disable
Disabling an OpenID user in Actual Budget's sync server does not invalidate that user's existing session tokens, letting the disabled account keep accessing budgets and admin functions indefinitely.
The problem
In OpenID multi-user mode, the admin PATCH /admin/users endpoint flips users.enabled to 0 but never deletes the user's existing session rows. The shared session validation function (validateSession) accepts any non-expired token without checking the enabled flag.
Because the default token_expiration setting is 'never', stale sessions persist indefinitely after an admin disables an account. A disabled regular user retains read/write access to their own budgets; a disabled admin retains full administrative access.
Proof of concept
# 1. Capture a token before the account is disabled
TOKEN="<session_token_obtained_before_disable>"
HOST="https://actual.example.com"
# 2. Admin disables the user via PATCH /admin/users
# (enabled: false is set, but the session row is NOT deleted)
# 3. Reuse the stale token -- server still returns 200
curl -s "$HOST/account/validate" \
-H "X-Actual-Token: $TOKEN"
# 4. Disabled user can still list and operate on budget files
curl -s "$HOST/sync/list-user-files" \
-H "X-Actual-Token: $TOKEN"The bug is a split between two code paths: the OpenID login path queries 'WHERE user_name = ? AND enabled = 1', so a disabled account cannot obtain a new token. But validateSession in validate-user.ts only looks up the session row by token value and checks expiry; it never joins or queries the users table for enabled status.
Disabling or deleting a user through app-admin.js only ran an UPDATE on the users table, with no corresponding DELETE on the sessions table. PR #7940 (merged in 26.6.0) fixes both gaps: validateSession now rejects tokens whose owner has enabled = 0, and the user-update path deletes all session rows for the affected user on disable.
The fix
Upgrade @actual-app/sync-server to 26.6.0 or later. PR #7940 adds the enabled check inside validateSession and purges existing sessions when a user is disabled via PATCH /admin/users. There is no workaround on affected versions short of manually deleting session rows from the account database for disabled users.
Reported by MatissJanis.