From 1606b5e5689260cf366eaf30e881ade38066463e Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 5 Oct 2026 13:30:20 +0000 Subject: [PATCH] chore: drop eth_chainId and net_version from PROXY_METHODS (closes #326) handleRpc answers both methods itself before it reaches its proxy branch, so the two list entries were never used and the list named two methods that are never sent to the RPC endpoint. No other entry is answered earlier. PROXY_METHODS is now exported from the background script so that tests/proxyMethods.test.js can send every listed method from a page and fail on any that does not reach the RPC endpoint. Model: opus-5-5 --- TODO.md | 8 ++++ src/background/index.js | 8 ++-- tests/proxyMethods.test.js | 89 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 tests/proxyMethods.test.js diff --git a/TODO.md b/TODO.md index 6feb978..5dd4b1e 100644 --- a/TODO.md +++ b/TODO.md @@ -45,6 +45,14 @@ but the review is broader than any of them. # Completed Steps +- 2026-10-05: `PROXY_METHODS` in `src/background/index.js` no longer lists + `eth_chainId` and `net_version` + ([#326](https://git.eeqj.de/sneak/AutistMask/issues/326)). `handleRpc` answers + both itself before its proxy branch, so the list named two methods that are + never sent to the RPC endpoint. No other entry is answered earlier. + `tests/proxyMethods.test.js` sends every listed method from a page and fails + on any that does not reach the RPC endpoint. + - 2026-10-05: The popup's Content Security Policy no longer allows inline style ([#328](https://git.eeqj.de/sneak/AutistMask/issues/328)): `style-src` is `'self'` in both manifests, pinned in `tests/manifest.test.js`. The 42 diff --git a/src/background/index.js b/src/background/index.js index 76c902d..bbeb4c9 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -741,11 +741,12 @@ async function handleConnectionRequest(origin) { } } -// Methods that are safe to proxy directly to the RPC node +// Methods that are safe to proxy directly to the RPC node. A method handleRpc +// answers before its proxy branch does not belong here: it would never reach +// the node. tests/proxyMethods.test.js sends every one of these. const PROXY_METHODS = [ "eth_blockNumber", "eth_call", - "eth_chainId", "eth_estimateGas", "eth_gasPrice", "eth_getBalance", @@ -759,7 +760,6 @@ const PROXY_METHODS = [ "eth_getTransactionReceipt", "eth_maxPriorityFeePerGas", "eth_sendRawTransaction", - "net_version", "web3_clientVersion", "eth_feeHistory", "eth_getBlockTransactionCountByHash", @@ -1802,3 +1802,5 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { return false; } }); + +module.exports = { PROXY_METHODS }; diff --git a/tests/proxyMethods.test.js b/tests/proxyMethods.test.js new file mode 100644 index 0000000..03abe51 --- /dev/null +++ b/tests/proxyMethods.test.js @@ -0,0 +1,89 @@ +// Every method in PROXY_METHODS is sent to the RPC node. +// +// handleRpc answers some methods itself before it reaches its proxy branch. A +// method listed in PROXY_METHODS but answered earlier never reaches the node, +// so the list would name a method that is not proxied +// (https://git.eeqj.de/sneak/AutistMask/issues/326). Each method is sent from +// a page here and must come back with what the node answered. + +const { makeStorageStub } = require("./support/storageStub"); + +async function settle() { + for (let i = 0; i < 50; i++) await Promise.resolve(); +} + +afterEach(() => { + delete global.chrome; + delete global.fetch; +}); + +test("every method in PROXY_METHODS reaches the RPC node", async () => { + jest.resetModules(); + + jest.doMock("../src/shared/balances", () => ({ + getProvider: () => ({}), + refreshBalances: jest.fn(async () => {}), + })); + jest.doMock("../src/shared/phishingDomains", () => ({ + isPhishingDomain: () => false, + })); + jest.doMock("../src/shared/alarms", () => ({ + BALANCE_REFRESH_ALARM: "balance", + BALANCE_REFRESH_PERIOD_MINUTES: 1, + ensureRecurringAlarms: jest.fn(async () => {}), + registerAlarmHandlers: jest.fn(), + })); + + // The node answers each method with a value naming that method. + global.fetch = jest.fn(async (url, opts) => ({ + status: 200, + json: async () => ({ + jsonrpc: "2.0", + id: 1, + result: "node answered " + JSON.parse(opts.body).method, + }), + })); + + let messageListener = null; + global.chrome = { + storage: makeStorageStub({ + autistmask: { + networkId: "mainnet", + wallets: [], + allowedSites: {}, + deniedSites: {}, + }, + }), + runtime: { + getURL: (path) => "chrome-extension://autistmask/" + path, + onMessage: { + addListener: (fn) => { + messageListener = fn; + }, + }, + onConnect: { addListener: () => {} }, + lastError: null, + }, + windows: { onRemoved: { addListener: () => {} } }, + action: { setPopup: () => {} }, + }; + + const { PROXY_METHODS } = require("../src/background/index"); + + const answers = {}; + const expected = {}; + for (const method of PROXY_METHODS) { + messageListener( + { type: "AUTISTMASK_RPC", method, params: [] }, + { origin: "https://dapp.example" }, + (r) => { + answers[method] = r; + }, + ); + await settle(); + expected[method] = { result: "node answered " + method }; + } + + expect(PROXY_METHODS.length).toBeGreaterThan(0); + expect(answers).toEqual(expected); +});