feat(account-tree-controller): add payload/snapshot support - #9826
Conversation
|
Using See: 9e0e993 |
gantunesr
left a comment
There was a problem hiding this comment.
Looks good overall
- Would be good to have an integration test for the flow export→filter→serialize→deserialize. This is probably covered in the next PR for the controller wiring
| export type ExportStateOptions = { | ||
| /** When `true`, secrets (mnemonic / private keys) are included. Requires the vault to be unlocked. */ | ||
| includeSecrets?: boolean; | ||
| }; |
There was a problem hiding this comment.
do you think there is any explicit cleanup/teardown to do when secrets are exported?
There was a problem hiding this comment.
To like clearing them out of memory?
Like you'd like to have them being passed as options to control this?
There was a problem hiding this comment.
I investigated this a bit with claude.
The real problem is that we use raw string for the secrets, and it makes it hard to zero-ing memory completely with those (since we don't own the real memory buffer for those, only the view/value on it).
If we really wanted to do this, we would need to use real buffer types like ArrayBuffer or Uint8Array. But that means our payload will need special encoding functions (at least, to make it compatible with JSON-encoding for example).
Like we could have a .stringify() and .parse(json) for example.
But that would yet another steps before/after the snapshot's serialize and deserialize.
There was a problem hiding this comment.
After discussing a bit about this internally, I actually went for a different way of encoding those in our payload, so we "limit" the window of when those sensitive values are used.
They are just encoded differently, so it's more or less security-by-obscurity, but it's slightly better than having them in clear I think (and that's aligned with how we encode them elsewhere in the codebase)
| * @throws If `raw` is not a valid payload or its version is unsupported. | ||
| */ | ||
| static async deserialize(raw: unknown): Promise<AccountTreeSnapshot> { | ||
| // TODO: Use migration framework here. |
There was a problem hiding this comment.
Decided to omit the migration part for now. My plan was to use our new generic migration framework that we created for keyrings. Though, it's not needed here (we expect a v1 payload + the migration framework needs a slight rework around version handling + flattening the state/data being migrated).
Delaying this work for a bit to not block this PR.
We indeed have some round-trips "integration" tests on the controller itself, to make sure the combo If needed, I'll add even more scenarios to also include filtering in this mix! |
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 7aaee50. Configure here.
| value: exactOptional(sensitive(BytesStruct)), | ||
| metadata: AccountWalletPayloadMetadataStruct, | ||
| groups: AccountWalletMnemonicGroupsStruct, | ||
| }); |
There was a problem hiding this comment.
Group IDs lack cross-field checks
Medium Severity
Mnemonic group validation checks contiguous groupIndex values but never checks that each group id belongs to the parent wallet or that its sub-id matches groupIndex, even though the documented format is <walletPayloadId>/<groupIndex>. Inconsistent ids therefore pass deserialize and can desync import mapping from derivation indices.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7aaee50. Configure here.
There was a problem hiding this comment.
That's true even though we are the producer of this payload, so we "know" we won't be doing that.
We can fix that with refine and a stricter validation, but that's not vital honestly.
…etaMask#9663) ## Explanation New `{export,import}State` actions. This is the implementation for this ADR: - MetaMask/decisions#231 ## References - ADR MetaMask/decisions#231 - Split to: - MetaMask#9826 - MetaMask#9863 - MetaMask#9864 ## 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](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Touches secret export/import and keyring/multichain wallet creation; incorrect handling could leak or corrupt credentials or account metadata. > > **Overview** > **Adds `AccountTreeController:exportState` and `AccountTreeController:importState`** so clients can snapshot and restore the account tree via a **version-1 wire format** (`AccountTreeSnapshot`, payload types, and `IdMap` are re-exported from the package). > > `exportState` builds a snapshot of wallets/groups (names, pinned, hidden, etc.); with **`includeSecrets: true`** and an unlocked vault it can include mnemonics and private keys. **`exportState` rejects when the vault is locked**, including metadata-only exports. `importState` accepts only validated `AccountTreeSnapshot` instances (untrusted data must use `deserialize` first); it can create new HD wallets via `MultichainAccountService` and private-key accounts via `KeyringController`, then apply metadata to existing and new entries. > > Tests add messenger action coverage, a controller-level metadata round-trip, dedicated **export→serialize→deserialize→import** integration tests for mnemonic/private-key secrets, and test harness updates (`KeyringController:withController`, multichain create actions, shared wallet fixtures). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 6078457. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->


Explanation
First part of the new
:{import,export}Stateactions for the account-tree.This PR only exposes the new payload/snapshot types that will be used to import/export.
References
{import,export}Stateactions #9663Checklist
Note
Medium Risk
Touches secret-bearing payload shapes and validation redaction for mnemonics/private keys; behavior is new and isolated to types/tests until controller actions land.
Overview
Introduces a new
state/layer for upcoming account-tree import/export: a versionedAccountTreePayload(v1, mnemonic + private-key wallets),IdMapfor local controller IDs ↔ stablewallet:…payload IDs, and an immutableAccountTreeSnapshotfor serialize/deserialize and selective export viafilterWallets/filterGroups/filterAllGroups.payload.tsdefines portable wallet/group shapes (optional secrets), Superstruct validation with fail-closed version pinning, contiguous mnemonic group indices, andsensitive()on mnemonic/private-key fields so validation errors do not leak secrets.utilsadds byte encoding helpers,deepFreeze, and safe validation error formatting.Controller
exportState/importStateare not wired in this PR—only the types and snapshot machinery plus tests.Reviewed by Cursor Bugbot for commit 7aaee50. Bugbot is set up for automated code reviews on this repo. Configure here.