Skip to content

Feat/pwd change failure recovery - #10148

Open
lwin-kyaw wants to merge 16 commits into
mainfrom
feat/pwd-change-failure-recovery
Open

Feat/pwd change failure recovery#10148
lwin-kyaw wants to merge 16 commits into
mainfrom
feat/pwd-change-failure-recovery

Conversation

@lwin-kyaw

@lwin-kyaw lwin-kyaw commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Explanation

Seedless password changes span multiple independently persisted states (remote Seedless/TOPRF, local Seedless vault, local KeyringController vault, the stored Keyring encryption key, and a lifecycle marker). These cannot be committed atomically, so a crash, lost response, or partial local update could previously leave the wallet in an ambiguous state where neither the old nor the new password reliably unlocks, and the client had no way to tell that recovery was needed.

This PR adds a server-first password-change recovery model to SeedlessOnboardingController:

  • A persisted, non-sensitive passwordChangePhase field (SeedlessPasswordChangePhase: SEEDLESS_CHANGE_PENDINGSEEDLESS_COMMITTEDLOCAL_KEYRING_PENDINGKEY_SYNC_PENDING, plus UNKNOWN) acts as a recovery signal. An unset/undefined phase means no change is in progress. The phase is not proof of remote or local state — recovery always re-verifies actual state before acting.
  • changePassword is now lifecycle-aware: it writes each phase at the irreversible boundaries, preserves the last known phase on error, and rejects a second concurrent change with PasswordChangeInProgress.
  • Two new public methods own the Seedless-side recovery sequencing (the controller owns the Seedless side; the client owns the Keyring side):
    • resolvePasswordSyncState({ skipCache }) — password-less, called at unlock (render + submit). Replaces the public checkIsPasswordOutdated read and returns a PasswordChangeRecoveryStatus that tells the client which recovery step to run next.
    • reconcilePassword({ globalPassword }) — password-consuming. Internally runs password-chain unlock and local vault rewrite, re-encrypts encryptedKeyringEncryptionKey under the new wrapping key so loadKeyringEncryptionKey keeps working, and advances to LOCAL_KEYRING_PENDING. Used both for an interrupted local password change and for a password change made on another device.
  • Lifecycle-advance methods: markPasswordChangeKeySyncPending after the Keyring encryption key is stored, and clearPasswordChangePhase once key synchronization and local persistence are verified. clearPasswordChangePhase is the only way back to "no change in progress".
  • A lost/ambiguous changeEncKey response is classified via fetchAuthPubKey comparison into old / new / unknown; ambiguous results stay UNKNOWN and keep the wallet locked.

Breaking changes vs main:

  • Remove checkIsPasswordOutdated and SeedlessOnboardingControllerCheckIsPasswordOutdatedAction. Call resolvePasswordSyncState({ skipCache }) instead (true maps to PasswordChangeRecoveryStatus.PasswordOutdated).
  • Remove submitGlobalPassword, syncLatestGlobalPassword, and their messenger actions. Call reconcilePassword({ globalPassword }) instead.
  • changePassword now writes lifecycle phases and rejects a concurrent change with PasswordChangeInProgress. Clients must not start a second password change while a lifecycle is unfinished, and must drive it to completion with clearPasswordChangePhase.

There is no awaitable durability hook on the controller for lifecycle writes — the phase is persisted as ordinary debounced controller state, so recovery re-verifies actual state (a stale/missing marker is recoverable via the outdated check + cryptographic Keyring verification). The controller does not call KeyringController (AllowedActions = never); clients own the Keyring-side steps and wallet locking.

Full design, recovery flow, and a step-by-step client integration guide are in docs/0002-password-change-recovery-flow.md. The ADR is in docs/0001-seedless-password-change-recovery.md.

Test plan

  • yarn workspace @metamask/seedless-onboarding-controller run test
  • Confirm changePassword writes SEEDLESS_CHANGE_PENDINGSEEDLESS_COMMITTEDLOCAL_KEYRING_PENDING and rejects a second concurrent change with PasswordChangeInProgress
  • Interrupted local change: resolvePasswordSyncStatereconcilePassword → Keyring old/new branch → storeKeyringEncryptionKey / markPasswordChangeKeySyncPendingclearPasswordChangePhase
  • Another-device outdated password: resolvePasswordSyncState returns password-outdated, reconcilePassword returns reconcile-keyring, and loadKeyringEncryptionKey still decrypts after vault rewrite
  • Ambiguous remote changeEncKey result stays UNKNOWN and does not infer success from a rejected Promise
  • Client unlock path no longer calls checkIsPasswordOutdated, submitGlobalPassword, or syncLatestGlobalPassword

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

High Risk
Breaking changes and new recovery paths touch Seedless/TOPRF passwords, vault rewrites, and unlock routing; incorrect client adoption or remaining concurrency gaps could leave wallets locked or skip Keyring reconciliation.

Overview
Introduces a server-first password-change recovery model for Seedless: a persisted passwordChangePhase signals interrupted changes, and clients route unlock/recovery via PasswordSyncStatus instead of ad-hoc outdated checks.

Breaking API: removes checkIsPasswordOutdated, submitGlobalPassword, and syncLatestGlobalPassword (and their messenger actions). Use resolvePasswordSyncState({ skipCache }) at unlock (password-less) and reconcilePassword({ globalPassword }) to run chain unlock, local vault rewrite, and re-wrap encryptedKeyringEncryptionKey internally. changePassword now writes lifecycle phases, preserves phase on failure, and throws PasswordChangeInProgress on overlap. Adds clearPasswordChangePhase, markPasswordChangeKeySyncPending, and related actions; Keyring reconciliation stays client-owned.

Fixes a high-severity race where resolvePasswordSyncState could clear a newer phase after concurrent changePassword by acquiring the controller lock before reading passwordChangePhase (documented in audit 0004). Ships ADRs/guides for client integration and notes remaining lock/durability follow-ups.

Reviewed by Cursor Bugbot for commit ae94d42. Bugbot is set up for automated code reviews on this repo. Configure here.

@lwin-kyaw
lwin-kyaw requested review from a team as code owners September 9, 2026 12:38

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts Outdated
@lwin-kyaw

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-09-09T16:51:26.722442Z abca9bc Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Provided git ref abca9bceacbc27108e1b5714e53b3fcbeb4e2cf1 does not exist
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@lwin-kyaw

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@8.1.0-preview-abca9bcea
@metamask-previews/accounts-controller@39.1.1-preview-abca9bcea
@metamask-previews/address-book-controller@7.1.2-preview-abca9bcea
@metamask-previews/ai-controllers@1.0.0-preview-abca9bcea
@metamask-previews/analytics-controller@2.1.0-preview-abca9bcea
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-abca9bcea
@metamask-previews/announcement-controller@8.1.0-preview-abca9bcea
@metamask-previews/app-metadata-controller@2.0.1-preview-abca9bcea
@metamask-previews/approval-controller@9.0.2-preview-abca9bcea
@metamask-previews/assets-controller@15.0.0-preview-abca9bcea
@metamask-previews/assets-controllers@111.1.3-preview-abca9bcea
@metamask-previews/authenticated-user-storage@3.0.2-preview-abca9bcea
@metamask-previews/base-controller@9.1.0-preview-abca9bcea
@metamask-previews/base-data-service@1.0.0-preview-abca9bcea
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-abca9bcea
@metamask-previews/bridge-controller@80.2.0-preview-abca9bcea
@metamask-previews/bridge-status-controller@75.5.0-preview-abca9bcea
@metamask-previews/build-utils@3.0.4-preview-abca9bcea
@metamask-previews/chain-agnostic-permission@1.7.0-preview-abca9bcea
@metamask-previews/chomp-api-service@4.0.2-preview-abca9bcea
@metamask-previews/claims-controller@0.6.1-preview-abca9bcea
@metamask-previews/client-controller@1.0.1-preview-abca9bcea
@metamask-previews/client-utils@2.1.1-preview-abca9bcea
@metamask-previews/compliance-controller@2.1.0-preview-abca9bcea
@metamask-previews/composable-controller@12.0.1-preview-abca9bcea
@metamask-previews/config-registry-controller@3.1.0-preview-abca9bcea
@metamask-previews/connectivity-controller@0.3.0-preview-abca9bcea
@metamask-previews/controller-utils@12.3.0-preview-abca9bcea
@metamask-previews/core-backend@9.0.0-preview-abca9bcea
@metamask-previews/cryptography@0.0.0-preview-abca9bcea
@metamask-previews/delegation-controller@3.0.2-preview-abca9bcea
@metamask-previews/earn-controller@12.2.6-preview-abca9bcea
@metamask-previews/eip-5792-middleware@3.0.5-preview-abca9bcea
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-abca9bcea
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-abca9bcea
@metamask-previews/eth-block-tracker@15.0.1-preview-abca9bcea
@metamask-previews/eth-json-rpc-middleware@24.0.2-preview-abca9bcea
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-abca9bcea
@metamask-previews/foundryup@1.0.1-preview-abca9bcea
@metamask-previews/gas-fee-controller@26.3.2-preview-abca9bcea
@metamask-previews/gator-permissions-controller@5.0.2-preview-abca9bcea
@metamask-previews/geolocation-controller@1.0.0-preview-abca9bcea
@metamask-previews/java-tron-up@1.0.0-preview-abca9bcea
@metamask-previews/json-rpc-engine@10.5.0-preview-abca9bcea
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-abca9bcea
@metamask-previews/keyring-controller@27.1.1-preview-abca9bcea
@metamask-previews/kyc-controller@0.0.0-preview-abca9bcea
@metamask-previews/local-node-utils@1.0.0-preview-abca9bcea
@metamask-previews/logging-controller@9.0.0-preview-abca9bcea
@metamask-previews/message-manager@14.1.2-preview-abca9bcea
@metamask-previews/messenger@2.0.0-preview-abca9bcea
@metamask-previews/messenger-cli@0.2.0-preview-abca9bcea
@metamask-previews/money-account-api-data-service@0.4.1-preview-abca9bcea
@metamask-previews/money-account-balance-service@2.4.3-preview-abca9bcea
@metamask-previews/money-account-controller@1.0.0-preview-abca9bcea
@metamask-previews/money-account-upgrade-controller@4.0.0-preview-abca9bcea
@metamask-previews/money-account-utils@1.2.0-preview-abca9bcea
@metamask-previews/multichain-account-service@13.0.2-preview-abca9bcea
@metamask-previews/multichain-api-middleware@4.0.3-preview-abca9bcea
@metamask-previews/multichain-network-controller@3.2.4-preview-abca9bcea
@metamask-previews/multichain-transactions-controller@7.1.2-preview-abca9bcea
@metamask-previews/name-controller@9.1.2-preview-abca9bcea
@metamask-previews/network-connection-banner-controller@0.2.1-preview-abca9bcea
@metamask-previews/network-controller@36.0.0-preview-abca9bcea
@metamask-previews/network-enablement-controller@6.0.5-preview-abca9bcea
@metamask-previews/notification-services-controller@27.0.1-preview-abca9bcea
@metamask-previews/passkey-controller@3.1.0-preview-abca9bcea
@metamask-previews/permission-controller@13.1.1-preview-abca9bcea
@metamask-previews/permission-log-controller@5.1.0-preview-abca9bcea
@metamask-previews/perps-controller@16.1.0-preview-abca9bcea
@metamask-previews/phishing-controller@17.4.1-preview-abca9bcea
@metamask-previews/platform-api-docs@0.1.0-preview-abca9bcea
@metamask-previews/polling-controller@16.0.9-preview-abca9bcea
@metamask-previews/preferences-controller@23.1.0-preview-abca9bcea
@metamask-previews/profile-metrics-controller@4.0.3-preview-abca9bcea
@metamask-previews/profile-sync-controller@29.0.0-preview-abca9bcea
@metamask-previews/ramps-controller@20.2.0-preview-abca9bcea
@metamask-previews/rate-limit-controller@7.0.1-preview-abca9bcea
@metamask-previews/react-data-query@1.0.0-preview-abca9bcea
@metamask-previews/remote-feature-flag-controller@6.1.1-preview-abca9bcea
@metamask-previews/sample-controllers@5.0.6-preview-abca9bcea
@metamask-previews/seedless-onboarding-controller@10.1.1-preview-abca9bcea
@metamask-previews/selected-network-controller@26.1.7-preview-abca9bcea
@metamask-previews/sentinel-api-service@1.0.1-preview-abca9bcea
@metamask-previews/shield-controller@6.0.1-preview-abca9bcea
@metamask-previews/signature-controller@39.2.10-preview-abca9bcea
@metamask-previews/smart-transactions-controller@26.0.0-preview-abca9bcea
@metamask-previews/snap-account-service@2.1.2-preview-abca9bcea
@metamask-previews/social-controllers@2.8.0-preview-abca9bcea
@metamask-previews/solana-test-validator-up@1.0.0-preview-abca9bcea
@metamask-previews/stellar-quickstart-up@0.0.0-preview-abca9bcea
@metamask-previews/storage-service@1.0.2-preview-abca9bcea
@metamask-previews/subscription-controller@8.0.1-preview-abca9bcea
@metamask-previews/transaction-controller@69.8.1-preview-abca9bcea
@metamask-previews/transaction-pay-controller@27.1.1-preview-abca9bcea
@metamask-previews/user-operation-controller@41.2.9-preview-abca9bcea
@metamask-previews/wallet@12.0.2-preview-abca9bcea
@metamask-previews/wallet-cli@0.0.0-preview-abca9bcea

@lwin-kyaw

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Provided git ref abca9bceacbc27108e1b5714e53b3fcbeb4e2cf1 does not exist
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0adf182. Configure here.

Comment thread packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts Outdated
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.

1 participant