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:
@@ -8,6 +8,10 @@
|
||||
|
||||
const js = require("@eslint/js");
|
||||
const globals = require("globals");
|
||||
const backgroundState = require("./script/lib/eslint/noStateSingletonInBackground");
|
||||
const {
|
||||
BACKGROUND_ENTRY_PREFIX,
|
||||
} = require("./script/lib/forbiddenBundleInputs");
|
||||
|
||||
// The extension APIs. MV3 Chrome exposes `chrome`; Firefox exposes both, and
|
||||
// the code feature-detects between them.
|
||||
@@ -78,12 +82,36 @@ module.exports = [
|
||||
},
|
||||
|
||||
// MV3 background: a service worker, with no window and no document.
|
||||
//
|
||||
// It also may not reach src/shared/state.js. That module's `state` export
|
||||
// is a per-bundle singleton loaded once and mutated in place, which is the
|
||||
// popup's lifetime and not the worker's: the worker is killed when idle,
|
||||
// nothing loads state at module scope, and an unpopulated read used to be
|
||||
// served DEFAULT_STATE silently. Five defects came from background code
|
||||
// reading or writing it (https://git.eeqj.de/sneak/AutistMask/issues/324),
|
||||
// and each point fix added a loadState() that created the next one. The
|
||||
// rule below checks reachability through the whole require graph, not just
|
||||
// the direct require, because a re-export from any shared module the
|
||||
// background already pulls in would put the singleton back in the bundle
|
||||
// with no background file naming it.
|
||||
//
|
||||
// It is not the guarantee: build.js asserts the same prohibition against
|
||||
// esbuild's own metafile, from the shared table in
|
||||
// script/lib/forbiddenBundleInputs.js. This is the early report.
|
||||
//
|
||||
// The glob comes from that same file, because build.js uses the prefix to
|
||||
// decide which entry points must be listed in the table at all: the two
|
||||
// layers must not disagree about which files are "the background".
|
||||
{
|
||||
files: ["src/background/**/*.js"],
|
||||
files: [`${BACKGROUND_ENTRY_PREFIX}**/*.js`],
|
||||
plugins: { background: backgroundState },
|
||||
languageOptions: {
|
||||
...commonjs,
|
||||
globals: { ...globals.serviceworker, ...extensionGlobals },
|
||||
},
|
||||
rules: {
|
||||
"background/no-state-singleton-in-background": "error",
|
||||
},
|
||||
},
|
||||
|
||||
// src/shared is bundled into both, so it may only use what both provide:
|
||||
@@ -107,9 +135,9 @@ module.exports = [
|
||||
},
|
||||
},
|
||||
|
||||
// Unit tests: jest on node.
|
||||
// Unit tests, and the helpers they require: jest on node.
|
||||
{
|
||||
files: ["tests/**/*.test.js"],
|
||||
files: ["tests/**/*.test.js", "tests/support/**/*.js"],
|
||||
languageOptions: {
|
||||
...commonjs,
|
||||
globals: { ...globals.node, ...globals.jest },
|
||||
|
||||
Reference in New Issue
Block a user