fix: stored state has no version and no migration, and a corrupt blob bricks the popup and every dApp call #311
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the pre-1.0 deployability audit (#303). Blocker: no upgrade story, and no in-product way out of a bad blob.
saveState()(src/shared/state.js:83-113) enumerates 28 fields. Read back from a live install, there is noversionorschemaVersionamong them:loadState()coerces scalars but validates nothing structural.Reproduction
Hostile blobs written to storage, then the real popup opened:
A completely blank popup: no view visible, no message, no recovery control.
It is not only the UI. With such a blob in storage,
getActiveAddress()(src/background/index.js:191-199) dereferencess.wallets[0].addresses[0]and throws, so every dApp RPC call from every page answers-32603 AutistMask could not complete this request because of an internal error. And there is no reset or wipe control anywhere in the UI —src/popup/andsrc/background/were grepped for one.Consequence
An unversioned blob plus any future schema change is a bricked wallet on auto-update, with no way out from inside the product. Today the same state is reachable from a partial write or a downgrade. The version field is the cheap insurance that is missing.
Note the adjacent latent case:
networkById()(src/shared/networks.js:34-36) returns mainnet for any unknown id — a stored{networkId:"base"}renders the selector asmainnetwith no banner andeth_chainId0x1, whilerpcUrlstill points at Base. Unreachable through today's UI (two networks, no free-form field), but it is exactly the failure mode a downgrade would produce.Definition of done
saveState()stamps a schema version.loadState()validates the shape. On a version it does not understand, or awalletsarray it cannot parse, it refuses to run and shows an explicit screen naming the problem, rather than rendering blank.-32603.networkById()on an unknown id fails loudly rather than silently returning mainnet.make checkgreen.Trap for whoever implements this, introduced by #313:
state.networkIdis loaded unvalidated (src/shared/state.js:136) and is now used as an object key into the newstate.networkEndpointsmap. A corruptnetworkIdof"__proto__"sets the map's prototype instead of an own key, so the user's endpoint is silently not recorded and a switch away and back returns the public default.Not reachable today — only this codebase writes that field, and only with validated network ids — which is why it is a note here rather than its own issue. It becomes reachable the moment stored state is treated as untrusted, which is exactly what this issue asks for. Validate
networkIdagainstsrc/shared/networks.jsas part of the shape validation.