harden: stop the background reading the shared state singleton, and enforce it at build time (closes #324)
Five defects, one of which destroyed every wallet, came from src/background reading and writing the module-level state singleton the MV3 worker never populates, which silently served DEFAULT_STATE. Each point fix created the next defect. The background now has its own per-call getState() and a queued read-modify-write updateState(); the singleton is unreachable from it, and an unpopulated read throws instead of serving defaults. The prohibition is enforced by the build, not by review: build.js asserts over esbuild's own metafile that no forbidden module is an input of a background bundle, so every specifier syntax esbuild resolves is covered, and both halves of the table are checked for rot -- a stale key, a stale module, an empty list, or an unlisted entry point under src/background/ all fail the build. The ESLint rule remains as fast local feedback and reads the same shared table. Known bounds are documented where the table lives. Also closes #320: getProvider() now requires a validated network id, so a cold worker no longer prepares a non-mainnet dApp transaction for mainnet and gets refused by the wallet's own verifier. backgroundRefresh() no longer mutates address objects across a network round trip, the broadcast path takes its endpoint and chain id from one snapshot, and eight test storage stubs now structured-clone on get as the real chrome.storage.local does. closes #320
This commit was merged in pull request #344.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
// first, so neither can see this.
|
||||
|
||||
const { networkById } = require("../src/shared/networks");
|
||||
const { makeStorageStub } = require("./support/storageStub");
|
||||
|
||||
const ADDRESS = "0x66133E8ea0f5D1d612D2502a968757D1048c214a";
|
||||
|
||||
@@ -64,9 +65,12 @@ afterEach(() => {
|
||||
delete global.chrome;
|
||||
});
|
||||
|
||||
// Load the background worker with the real state and chain-switch modules
|
||||
// behind it, over a storage stub that actually keeps what is written — a
|
||||
// wipe is only observable against storage that remembers.
|
||||
// Load the background worker with the real chain-switch and persistence
|
||||
// modules behind it, over a storage stub that actually keeps what is written —
|
||||
// a wipe is only observable against storage that remembers — and that clones
|
||||
// in both directions, as the real API does. It used to alias, so the record
|
||||
// the worker held and the "stored" one were a single object; see
|
||||
// tests/support/storageStub.js.
|
||||
function loadColdWorker(networkId) {
|
||||
jest.resetModules();
|
||||
|
||||
@@ -84,20 +88,13 @@ function loadColdWorker(networkId) {
|
||||
registerAlarmHandlers: jest.fn(),
|
||||
}));
|
||||
|
||||
const store = { autistmask: storedProfile(networkId) };
|
||||
const storage = makeStorageStub({ autistmask: storedProfile(networkId) });
|
||||
|
||||
let messageListener = null;
|
||||
const toTabs = [];
|
||||
|
||||
global.chrome = {
|
||||
storage: {
|
||||
local: {
|
||||
get: jest.fn(async () => ({ autistmask: store.autistmask })),
|
||||
set: jest.fn(async (items) => {
|
||||
store.autistmask = items.autistmask;
|
||||
}),
|
||||
},
|
||||
},
|
||||
storage,
|
||||
runtime: {
|
||||
getURL: (path) => "chrome-extension://autistmask/" + path,
|
||||
onMessage: {
|
||||
@@ -147,7 +144,7 @@ function loadColdWorker(networkId) {
|
||||
|
||||
return {
|
||||
switchChain,
|
||||
persisted: () => store.autistmask,
|
||||
persisted: () => storage.read("autistmask"),
|
||||
chainChangedEvents: () =>
|
||||
toTabs.filter((m) => m.eventName === "chainChanged"),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user