fix: malformed allowedSites and fraudContracts entries are dereferenced without a floor #362
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 re-review of #360 (#360 (comment)), and a sibling case found during its rework. Both pre-existing on
next, both deliberately left out of that PR's scope.1.
allowedSites— a working popup that silently never persists againA stored
allowedSiteswhose value is not a list — e.g.{"0xabc…": "notalist"}— passes the state gate and renders a working popup, then throws an unhandledTypeError: base.map is not a functionatsrc/shared/state.js:217.The failure mode is the nastiest shape available: the UI looks entirely healthy, and every subsequent save silently fails. The user keeps using the wallet while nothing they do is persisted. That is worse than the blank popup #311 fixed, because a blank popup is at least visibly broken.
Reproduced independently twice — by the reviewer and again during rework, where
allowedSites: {a: 1}killed the jest worker as an unhandled rejection after the popup had rendered.2.
fraudContracts— same class, milder consequencefraudContractsis assigned assaved.x || defaultwith no type check, and is then structurally dereferenced atsrc/popup/views/send.js:124:(state.fraudContracts || []).map((a) => a.toLowerCase()).It does not blank the popup — the boot path reaches it only via
loadHomeTxs(), which catches and renders "Failed to load transactions", and the send view is not on boot. So the consequence is a broken send screen rather than silent data loss, but the shape is identical: a container floored (or not) while its entries are dereferenced unchecked.Why these are together
#360 established the idiom for this class — floor the container and its entries, dropping anything that cannot be safely dereferenced — and applied it to
trackedTokensandtokenBalances. These are the two fields it did not reach. Fix them with that same idiom rather than inventing a third one.In milestone 1.0.0 on the strength of case 1: silent non-persistence in a wallet is a data-loss defect, and the 1.0 standard is being able to actually use this in low-volume production.
Definition of done
allowedSites,deniedSitesandfraudContractsentries cannot reach a.map()or a.toLowerCase(). Floor the per-entry shape, not just the container, following the idiom settled in #360.make checkgreen.fix: a malformed allowedSites entry gives a working popup that silently never persists againto fix: malformed allowedSites and fraudContracts entries are dereferenced without a floor