Skip to content

fix(accounts): prevent Team users sharing account_id from overwriting auth snapshots #175

@tinywind

Description

@tinywind

Problem

The accounts UI/storage still uses tokens.account_id as the unique identity for saved Codex auth snapshots. In ChatGPT Team/Workspace setups, multiple users can share the same tokens.account_id while having distinct user ids and emails. When two users from the same team import or log in on the same machine, codexUI can store them under the same identity, so the later snapshot can overwrite the earlier one and account switch/remove/active restore can target the wrong user.

This was previously addressed in PR #104, but that PR was closed without merge:
#104

Current main still has the same collision path:

  • src/server/accountRoutes.ts:58-60 stores activeAccountId as the active key.
  • src/server/accountRoutes.ts:180-182 derives storageId from accountId only.
  • src/server/accountRoutes.ts:290-295 upserts by accountId, so accounts sharing one account_id replace each other.
  • src/server/accountRoutes.ts:298-311 sorts and marks active accounts by accountId.
  • src/server/accountRoutes.ts:700-707 imports snapshots under toStorageId(imported.accountId).
  • src/api/codexGateway.ts:397-399, src/api/codexGateway.ts:494-501, and src/api/codexGateway.ts:1360-1368 expose/normalize only activeAccountId and accountId for account list state.
  • src/api/codexGateway.ts:1426-1450 sends switch/remove requests by accountId.
  • src/App.vue:168, src/App.vue:197, src/App.vue:209, src/App.vue:2465, and src/App.vue:2495 key and route account UI actions through account.accountId.

Impact

Users from the same ChatGPT Team cannot reliably keep separate Codex auth snapshots in codexUI. A second import/login can overwrite the first user's saved auth snapshot. Account switching or active-account restoration can also become ambiguous because account_id is not unique for this scenario.

Expected behavior

When the access token exposes a user id, saved account identity should be derived from both account_id and user_id or another deterministic per-user storage key. Tokens without a user id can keep the legacy account_id fallback.

Legacy state should migrate deterministically so active account restoration never falls back to the first account with a matching accountId when multiple users share that value.

Suggested fix

  • Add a stable storage identity separate from display accountId, for example storageId = hash(account_id + ':' + user_id) when user_id is available.
  • Persist and expose userId/storageId on account records.
  • Use storageId for switch/remove/hover/key UI actions while keeping accountId as display metadata.
  • Store activeStorageId; migrate legacy activeAccountId only when it maps to exactly one stored account or can be resolved from the current ~/.codex/auth.json.
  • Keep snapshot import and account-copy helper paths on the same identity rule.

Prior art

PR #104 implemented this direction and later added deterministic migration after the active-account restoration ambiguity was pointed out in review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions