Repository navigation
feat: identify social login users with incomplete metadata backup - #10568
Conversation
|
Looks good for Shapes A/B/C (missing primary, V1 earliest-imported, V2 mislabeled PrimarySrp). Two asks before we treat this as SSOT: (1) fix the mermaid Yes/No on “Primary SRP Different” - it’s inverted vs the prose; (2) specify controller vs client ownership; Keyring compare + profile_id telemetry aren’t in seedless today (AllowedActions = never). Also worth noting identify can’t see users who already rehydrated onto a wrong primary |
Thanks for the comment @grvgoel81 |
…portSeedPhraseAction to the SeedlessController allowedActions list
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
| await trackEventSafely({ | ||
| name: SeedlessPrimarySrpMismatchEventName, | ||
| properties: {}, | ||
| sensitiveProperties: {}, | ||
| saveDataRecording: false, | ||
| hasProperties: false, | ||
| }); |
There was a problem hiding this comment.
Both SeedlessPrimarySrpMismatchEventName and SeedlessPrimarySrpMissingEventName emit with properties: {}. Without a profile_id, metaMetricsId, or keyringId, you can only count affected users, cannot identify who they are for manual remediation. This defeats the purpose of the detection for incident response.
There was a problem hiding this comment.
Without a profile_id, metaMetricsId,
We're calling the AnalyticsController:trackEvent directly, both of these will be assigned by the controller.
I've also updated with the relevant properties here dc77a1e
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
| globalPassword, | ||
| maxKeyChainLength = 5, | ||
| }: { | ||
| globalPassword: string; | ||
| maxKeyChainLength?: number; | ||
| }): Promise<void> { | ||
| return await this.#withControllerLock(async () => { | ||
| return await this.#executeWithTokenRefresh(async () => { | ||
| const currentDeviceAuthPubKey = this.#recoverAuthPubKey(); | ||
| await this.#submitGlobalPassword({ | ||
| targetAuthPubKey: currentDeviceAuthPubKey, | ||
| globalPassword, | ||
| maxKeyChainLength, | ||
| }); | ||
| }, 'submitGlobalPassword'); |
There was a problem hiding this comment.
Users who unlock via the global password path (Mobile) are never scanned. Inside #submitGlobalPassword, decryptedVaultData with its toprfEncryptionKey and toprfAuthKeyPair is available - the same ingredients used in submitPassword. The detection needs to be wired in here too.
There was a problem hiding this comment.
I've exposed a new public method, identifyIncompleteMetadataBackup to track the analytics, for this case.
We can't do this in the controller side because currently, in Password sync cases, the wallet unlock, KeyringController:submitPassword is done in the client side.
So, you should call identifyIncompleteMetadataBackup method after wallet is unlocked with global password.
Normal unlock should be still tracked by the controller internally.
Eventually, after we move every orchestration inside the controller, we will mark this method, identifyIncompleteMetadataBackup as internal private method.
| ); | ||
|
|
||
| // Sort: PrimarySrp first, then by client timestamp (oldest first). | ||
| results.sort((a, b) => SecretMetadata.compare(a, b, 'asc')); |
There was a problem hiding this comment.
SecretMetadata.compare returns 0 when both items have dataType === PrimarySrp (the V2 corruption shape from this incident). That makes the relative order between them dependent on the TOPRF node fetch order, which is not stable across unlocks. findIndex then picks whichever ends up first. For a user with two PrimarySrp-tagged items where the wrong one is first, areUint8ArraysEqual in identifyIncompleteMetadataBackup returns true and the user is silently classified as healthy.
The dual-PrimarySrp case should be detected explicitly here e.g., check if more than one PrimarySrp-tagged item exists after the sort and surface that as a distinct error or return value so the caller can emit a separate analytics event for it, rather than silently picking one.
There was a problem hiding this comment.
@grvgoel81 , I've replied to this same comment here, #10568 (comment) :)
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
grvgoel81
left a comment
There was a problem hiding this comment.
Approving. Remaining action item: the Mobile side needs to wire up identifyIncompleteMetadataBackup after submitGlobalPassword unlock; global-password users won't be scanned until that call site is added.
|
|
||
| ### Changed | ||
|
|
||
| - **BREAKING:** Delegate `AnalyticsController:trackEvent` and `KeyringController:exportSeedPhrase` from the wallet root messenger to `SeedlessOnboardingController`. ([#10568](https://github.com/MetaMask/core/pull/10568)) |
There was a problem hiding this comment.
This is more in line with other changelog entries.
| - **BREAKING:** Delegate `AnalyticsController:trackEvent` and `KeyringController:exportSeedPhrase` from the wallet root messenger to `SeedlessOnboardingController`. ([#10568](https://github.com/MetaMask/core/pull/10568)) | |
| - **BREAKING:** Grant `SeedlessOnboardingController` access to `AnalyticsController:trackEvent` and `KeyringController:exportSeedPhrase` ([#10568](https://github.com/MetaMask/core/pull/10568)) |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 1f496f5. Configure here.

Explanation
Social-login users may have an incomplete or incorrect TOPRF metadata backup after a partial backup failure. This change adds read-only detection after a successful unlock so affected users can be measured before a future repair flow.
After
submitPasswordsucceeds,identifyIncompleteMetadataBackupruns asynchronously and:PrimarySrptype.Seedless Onboarding Primary SRP MissingorSeedless Onboarding Primary SRP Mismatchevents.The
SeedlessOnboardingControllerMessengernow requires:AnalyticsController:trackEventKeyringController:exportSeedPhraseThe wallet initializer delegates these actions from the root messenger. The seedless onboarding controller also adds the analytics-controller dependency, exposes its
./utilsentry point, and refactors secret metadata parsing and migration helpers.References
Test plan
Checklist
Note
High Risk
Runs on unlock and calls exportSeedPhrase to compare SRPs, with breaking messenger wiring for analytics and keyring export; behavior is read-only but touches wallet recovery secrets.
Overview
Adds read-only detection for social-login wallets whose remote TOPRF backup is missing or wrong for the primary SRP, so affected users can be measured before a future repair.
After a successful
submitPasswordunlock, the controller kicks offidentifyIncompleteMetadataBackupin the background (no await). It fetches remote secret metadata, picks the primary candidate (legacy v1 ordering vs v2PrimarySrp), compares it to the local keyring SRP viaKeyringController:exportSeedPhrase, and emitsSeedless Onboarding Primary SRP MissingorMismatchthroughAnalyticsController:trackEvent. Failures are logged only; unlock is not blocked and nothing is written locally or remotely. The same flow is exposed as a public messenger action for explicit calls when unlocked.Breaking: integrators must allow and delegate
AnalyticsController:trackEventandKeyringController:exportSeedPhraseon the seedless messenger (wallet initializer updated). The package adds@metamask/analytics-controller, a./utilsexport, and moves secret-metadata parsing/migration helpers plus identification logic intoutils/(includingtrackIncompleteMetadataBackupEvents). Docs describe the identification plan for production remediation.Reviewed by Cursor Bugbot for commit 1f496f5. Bugbot is set up for automated code reviews on this repo. Configure here.