fix: floor the entries of the site maps and the fraud list, and stop a failed save from failing silently (closes #362)
allowedSites was checked as a container while its entries were dereferenced unchecked. A stored {"0x...": "notalist"} passed the state gate and rendered a completely healthy popup, then threw "base.map is not a function" inside saveState()'s per-hostname merge, so every save from that moment on failed and the user went on operating a wallet that was persisting nothing. Measured against the previous head: the popup showed the main view with no page errors, and chrome.storage.local.set was never called at all. deniedSites has the identical shape; fraudContracts the same class with a milder consequence, throwing "(state.fraudContracts || []).map is not a function" on the send screen; and the sweep for the class turned up selectedToken, which is truthiness-gated on restore and then dereferenced as text, blanking the popup outright with "tokenId.toLowerCase is not a function".
All four now get the floor issue 311 settled -- the container AND its entries, with a malformed entry dropped -- through textList() and siteMap() beside the existing tokenRefs() in persistedState.js, rather than a third mechanism. Site-map keys are written with defineProperty for the same reason networkEndpoints' keys are: a stored own "__proto__" key would otherwise be handed to the prototype setter. The background's allowed.includes(hostname) gate is covered by the same floor, where a stored string would have answered a substring match rather than merely throwing.
A save that fails is no longer swallowed. onSaveFailure() in state.js reports every failed save, awaited or not -- the save queue has to attach a rejection handler to keep advancing, which is what made a failure disappear entirely -- and the popup raises a persistent "NOT SAVED" banner naming the reason. The popup's background refresh loop no longer turns a save failure into an unhandled rejection instead of a report. Both halves are needed: the floor only covers the causes it knows about, and storage can still fail for a quota or a revoked permission.
The field-by-field categorisation in the header of stateSchema.js, and its mirror in README.md, were re-verified against the code and moved with the change; the fields left on a loose floor now carry the reason each one is still safe. The popup boot harness moved to tests/support/popupBoot.js so the new tests drive the real entry point rather than duplicating it.
This commit is contained in:
25
TODO.md
25
TODO.md
@@ -45,6 +45,31 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-23: A persisted container whose ENTRIES were dereferenced unchecked no
|
||||
longer reaches a `.map()` or a `.toLowerCase()`
|
||||
([#362](https://git.eeqj.de/sneak/AutistMask/issues/362)). `allowedSites` was
|
||||
the worst shape available: a stored `{"0x…": "notalist"}` passed the gate,
|
||||
rendered a completely healthy popup, and then threw inside `saveState()`'s
|
||||
per-hostname merge, so every save from that moment on failed silently and the
|
||||
user went on operating a wallet that was persisting nothing — measured as
|
||||
`chrome.storage.local.set` never being called at all. `deniedSites` has the
|
||||
same shape, `fraudContracts` the same class with a milder consequence (a
|
||||
broken send screen, since the boot path only reaches it through
|
||||
`loadHomeTxs()`, which catches), and the sweep for the class turned up
|
||||
`selectedToken`, which blanked the popup outright when restoring onto
|
||||
address-token. All four now get the floor
|
||||
[#311](https://git.eeqj.de/sneak/AutistMask/issues/311) settled — container
|
||||
AND entries, malformed entries dropped — through `textList()` and `siteMap()`
|
||||
beside the existing `tokenRefs()` in `src/shared/persistedState.js`. Site-map
|
||||
keys are written with `defineProperty` for the same reason `networkEndpoints`'
|
||||
are. Separately, a save that fails is no longer swallowed: `onSaveFailure()`
|
||||
in `src/shared/state.js` reports every failed save, awaited or not, and the
|
||||
popup raises a persistent "NOT SAVED" banner; the background refresh loop no
|
||||
longer turns a failure into an unhandled rejection instead of a report. The
|
||||
field-by-field categorisation in the header of `src/shared/stateSchema.js`,
|
||||
and its mirror in `README.md`, were re-verified against the code and moved
|
||||
with the change.
|
||||
|
||||
- 2026-08-23: A swap whose output token the calldata never named is said to be
|
||||
unknown instead of being called ETH
|
||||
([#353](https://git.eeqj.de/sneak/AutistMask/issues/353)). `tokenInfo(null)`
|
||||
|
||||
Reference in New Issue
Block a user