Skip to content

feat(keyring-eth-money): validate serialized state with superstruct#474

Open
danroc wants to merge 8 commits intomainfrom
feat/money-keyring-superstruct-validation
Open

feat(keyring-eth-money): validate serialized state with superstruct#474
danroc wants to merge 8 commits intomainfrom
feat/money-keyring-superstruct-validation

Conversation

@danroc
Copy link
Contributor

@danroc danroc commented Mar 19, 2026

The MoneyKeyring serialized state was previously unvalidated. serialize returned the parent's full state and deserialize accepted a loosely typed partial object.

This adds a superstruct schema (MoneyKeyringSerializedStateStruct) that enforces the serialized state shape:

  • mnemonic: number[]
  • numberOfAccounts: 0 | 1
  • hdPath: the exact MONEY_DERIVATION_PATH literal

Both serialize and deserialize now assert against this schema. The MoneyKeyringSerializedState type is inferred from the struct via Infer.

Examples

N/A - this is an internal validation change with no external usage yet.


Note

Medium Risk
Tightens MoneyKeyring serialize/deserialize contracts and will now reject previously accepted malformed state, which could break callers persisting older/loose formats. Risk is moderate but contained to a single keyring package and is covered by updated tests.

Overview
MoneyKeyring now validates its serialized state with a superstruct schema and exposes a new MoneyKeyringSerializedState type. serialize() asserts the parent state matches { mnemonic: number[], numberOfAccounts: 0|1, hdPath: MONEY_DERIVATION_PATH }, and deserialize() now requires and validates this exact shape (instead of coercing/overriding invalid inputs).

Tests were updated to use byte-array mnemonics, cover numberOfAccounts: 0, and verify invalid mnemonic/numberOfAccounts/hdPath are rejected; the package also exports the new type and adds @metamask/superstruct as a dependency (with changelog updated).

Written by Cursor Bugbot for commit c251ebd. This will update automatically on new commits. Configure here.

danroc added 5 commits March 19, 2026 15:27
Add a superstruct schema to enforce that MoneyKeyring serialized state
contains only a number[] mnemonic, a numberOfAccounts of 0 or 1, and
the exact MONEY_DERIVATION_PATH. Both serialize and deserialize now
assert against this schema. The MoneyKeyringSerializedState type is
inferred from the struct via Infer.
Remove unnecessary casts, use `as const` for sample state,
flatten test structure, and require full state in deserialize.
@danroc danroc marked this pull request as ready for review March 19, 2026 16:52
@danroc danroc requested a review from a team as a code owner March 19, 2026 16:52
Copy link

@cursor cursor bot left a comment

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.

mnemonic: array(number()),
numberOfAccounts: union([literal(0), literal(1)]),
hdPath: literal(MONEY_DERIVATION_PATH),
});
Copy link

Choose a reason for hiding this comment

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

Struct permits empty mnemonic that fails on deserialize

Low Severity

MoneyKeyringSerializedStateStruct allows mnemonic: [] (empty array), which is exactly what serialize() produces for a freshly constructed keyring. However, passing this state to deserialize() will throw, because the parent HdKeyring.deserialize treats [] as truthy (if (opts.mnemonic)) and calls #initFromMnemonic([]), which converts it to an empty string and fails BIP39 mnemonic validation. The struct validates a state that can't actually be round-tripped.

Additional Locations (1)
Fix in Cursor Fix in Web

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.

2 participants