Compare commits
1
Commits
next
..
93cc072a0b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93cc072a0b |
+6
-5
@@ -8,11 +8,12 @@ WORKDIR /app
|
||||
# image sets it.
|
||||
ENV AUTISTMASK_LINT_NATIVE=1
|
||||
|
||||
# script/test's default 30s bound is the host figure. In here the same suite
|
||||
# starts on a cold jest cache and shares the runner with the rest of the build,
|
||||
# so 30s is too tight — it killed a healthy suite at 30.6s on a cold CI cache.
|
||||
# 180s still catches a hang in three minutes and cannot be tripped by a suite
|
||||
# that is merely running on contended hardware.
|
||||
# script/test's default 30s bound is the host figure, against a suite that
|
||||
# takes 23-29s there with three jest workers. In here the same suite starts on
|
||||
# a cold jest cache and shares the runner with the rest of the build, so 30s
|
||||
# is too tight — it killed a healthy suite at 30.6s on a cold CI cache. 180s
|
||||
# still catches a hang in three minutes and cannot be tripped by a suite that
|
||||
# is merely running on contended hardware.
|
||||
ENV AUTISTMASK_TEST_TIMEOUT=180
|
||||
|
||||
# script/bootstrap installs all prerequisites (make via apt here; node
|
||||
|
||||
@@ -45,24 +45,6 @@ but the review is broader than any of them.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-04: A page's request is credited only to the site the browser says
|
||||
sent it ([#407](https://git.eeqj.de/sneak/AutistMask/issues/407)). Where the
|
||||
browser does not give the sender's origin (Firefox before 126), the background
|
||||
used the tab's page, so a frame from another site would have been treated as
|
||||
the site embedding it, and with no tab it used an origin the page wrote into
|
||||
the message. It now uses the URL of the frame that sent the message, and
|
||||
refuses the request with code 4100 when the browser gives neither. The content
|
||||
script no longer writes an origin into the message.
|
||||
|
||||
- 2026-10-04: `make test` takes 8-13s on the shared build host, down from
|
||||
17-25s, measured in alternating runs before and after the change
|
||||
([#428](https://git.eeqj.de/sneak/AutistMask/issues/428)). Each popup boot in
|
||||
the tests (`tests/support/popupBoot.js`) resets jest's module registry so that
|
||||
everything under `src/` loads fresh, and that also reloaded `ethers`,
|
||||
`libsodium-wrappers-sumo`, `qrcode` and `ethereum-blockies-base64` every time.
|
||||
Those four libraries are now loaded once per test file and handed to every
|
||||
boot. No test or assertion changed.
|
||||
|
||||
- 2026-10-04: A token whose scale is unknown reads the same on the Send screen
|
||||
as on the confirmation screen
|
||||
([#377](https://git.eeqj.de/sneak/AutistMask/issues/377)). When two addresses'
|
||||
|
||||
+6
-6
@@ -5,12 +5,12 @@
|
||||
# many-core shared host one per core took gigabytes of RAM per run.
|
||||
#
|
||||
# The timeout bounds a hung suite; it is not a performance budget. On the busy
|
||||
# shared build host the suite takes 8-13s with three workers, inside
|
||||
# REPO_POLICIES' 20s budget. Inside the image the same suite also pays a cold
|
||||
# jest cache and shares the runner with the rest of the build, which is not what
|
||||
# that budget describes, so the Dockerfile raises the bound through
|
||||
# AUTISTMASK_TEST_TIMEOUT. A cap a healthy suite can trip on a cold cache
|
||||
# produces a red that means nothing, and teaches "just run it again".
|
||||
# shared build host the suite takes 23-29s with three workers, so
|
||||
# REPO_POLICIES' 30s cap is tight there, not comfortable. Inside the image the
|
||||
# same suite also pays a cold jest cache and shares the runner with the rest of
|
||||
# the build, which is not what that budget describes, so the Dockerfile raises
|
||||
# the bound through AUTISTMASK_TEST_TIMEOUT. A cap a healthy suite can trip on a
|
||||
# cold cache produces a red that means nothing, and teaches "just run it again".
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
+8
-19
@@ -1288,29 +1288,18 @@ if (windowsNs && windowsNs.onRemoved) {
|
||||
// Listen for messages from content scripts and popup
|
||||
runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (msg.type === "AUTISTMASK_RPC") {
|
||||
// The origin is the one the browser reports for the sender, never one
|
||||
// the message carries. Firefox before 126 gives no sender.origin, so
|
||||
// the origin of sender.url is used: the frame that sent the message,
|
||||
// not the tab's page, which may be another site embedding that
|
||||
// frame. With neither, the request is refused.
|
||||
let trustedOrigin = sender.origin;
|
||||
if (!trustedOrigin && sender.url) {
|
||||
// Derive origin from trusted sender info to prevent origin spoofing.
|
||||
// Chrome MV3 provides sender.origin; Firefox MV2 fallback uses sender.tab.url.
|
||||
let trustedOrigin = msg.origin; // fallback only if sender info unavailable
|
||||
if (sender.origin) {
|
||||
trustedOrigin = sender.origin;
|
||||
} else if (sender.tab && sender.tab.url) {
|
||||
try {
|
||||
trustedOrigin = new URL(sender.url).origin;
|
||||
trustedOrigin = new URL(sender.tab.url).origin;
|
||||
} catch {
|
||||
// an unparseable URL leaves the origin unknown
|
||||
// keep fallback
|
||||
}
|
||||
}
|
||||
if (!trustedOrigin) {
|
||||
sendResponse({
|
||||
error: {
|
||||
code: 4100,
|
||||
message:
|
||||
"The wallet could not tell which site sent this request.",
|
||||
},
|
||||
});
|
||||
return false;
|
||||
}
|
||||
handleRpc(msg.method, msg.params, trustedOrigin)
|
||||
.then((response) => {
|
||||
sendResponse(response);
|
||||
|
||||
@@ -30,6 +30,7 @@ window.addEventListener("message", (event) => {
|
||||
id,
|
||||
method,
|
||||
params,
|
||||
origin: location.origin,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
// Which site a page's request is attributed to.
|
||||
//
|
||||
// The background takes a request's origin from what the browser says sent the
|
||||
// message: sender.origin, or on Firefox before 126, which has no
|
||||
// sender.origin, the origin of sender.url — the frame that sent it. It used to
|
||||
// fall back to the tab's page and then to an origin the message itself
|
||||
// carried, so a request from a frame was credited to the site embedding it,
|
||||
// and a request the browser said nothing about was credited to whatever the
|
||||
// page wrote (https://git.eeqj.de/sneak/AutistMask/issues/407).
|
||||
//
|
||||
// Every sender here lacks sender.origin, as on old Firefox. The connection
|
||||
// check on eth_accounts is what shows which site a request was credited to.
|
||||
|
||||
const { makeStorageStub } = require("./support/storageStub");
|
||||
|
||||
const ADDRESS = "0x66133E8ea0f5D1d612D2502a968757D1048c214a";
|
||||
|
||||
// The site the persisted state has connected, and one it has never heard of.
|
||||
const CONNECTED_ORIGIN = "https://dapp.example";
|
||||
const CONNECTED_HOSTNAME = "dapp.example";
|
||||
const STRANGER_ORIGIN = "https://stranger.example";
|
||||
|
||||
async function settle() {
|
||||
for (let i = 0; i < 50; i++) await Promise.resolve();
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
delete global.chrome;
|
||||
});
|
||||
|
||||
function loadBackground() {
|
||||
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(),
|
||||
}));
|
||||
|
||||
const storage = makeStorageStub({
|
||||
autistmask: {
|
||||
networkId: "mainnet",
|
||||
wallets: [
|
||||
{
|
||||
name: "Wallet 1",
|
||||
type: "hd",
|
||||
addresses: [
|
||||
{ address: ADDRESS, balance: "0", tokenBalances: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
activeAddress: ADDRESS,
|
||||
allowedSites: { [ADDRESS]: [CONNECTED_HOSTNAME] },
|
||||
deniedSites: {},
|
||||
},
|
||||
});
|
||||
|
||||
let messageListener = null;
|
||||
global.chrome = {
|
||||
storage,
|
||||
runtime: {
|
||||
getURL: (path) => "chrome-extension://autistmask/" + path,
|
||||
onMessage: {
|
||||
addListener: (fn) => {
|
||||
messageListener = fn;
|
||||
},
|
||||
},
|
||||
onConnect: { addListener: () => {} },
|
||||
lastError: null,
|
||||
},
|
||||
windows: { onRemoved: { addListener: () => {} } },
|
||||
action: { setPopup: () => {} },
|
||||
};
|
||||
|
||||
require("../src/background/index");
|
||||
|
||||
// Ask for eth_accounts. `claimedOrigin` is an origin written into the
|
||||
// message, as the content script used to send.
|
||||
return async function accounts(sender, claimedOrigin) {
|
||||
let result = null;
|
||||
messageListener(
|
||||
{
|
||||
type: "AUTISTMASK_RPC",
|
||||
method: "eth_accounts",
|
||||
params: [],
|
||||
origin: claimedOrigin,
|
||||
},
|
||||
sender,
|
||||
(r) => {
|
||||
result = r;
|
||||
},
|
||||
);
|
||||
await settle();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
describe("a request is attributed to the frame that sent it", () => {
|
||||
test("a stranger's frame on a connected site gets no address", async () => {
|
||||
const accounts = loadBackground();
|
||||
|
||||
const result = await accounts({
|
||||
url: STRANGER_ORIGIN + "/frame.html",
|
||||
tab: { url: CONNECTED_ORIGIN + "/" },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ result: [] });
|
||||
});
|
||||
|
||||
test("a connected site's frame on a stranger's page gets the address", async () => {
|
||||
const accounts = loadBackground();
|
||||
|
||||
const result = await accounts({
|
||||
url: CONNECTED_ORIGIN + "/frame.html",
|
||||
tab: { url: STRANGER_ORIGIN + "/" },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ result: [ADDRESS] });
|
||||
});
|
||||
});
|
||||
|
||||
describe("a request the browser does not say the sender of", () => {
|
||||
test("is refused, whatever the tab or the message says", async () => {
|
||||
const accounts = loadBackground();
|
||||
|
||||
const result = await accounts(
|
||||
{ tab: { url: CONNECTED_ORIGIN + "/" } },
|
||||
CONNECTED_ORIGIN,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
error: {
|
||||
code: 4100,
|
||||
message:
|
||||
"The wallet could not tell which site sent this request.",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,26 +20,6 @@ const path = require("path");
|
||||
|
||||
const { makeStorageStub } = require("./storageStub");
|
||||
|
||||
// The four libraries the popup loads from node_modules, loaded once per test
|
||||
// file and handed to every boot. jest.resetModules() in bootPopup() empties the
|
||||
// module cache but keeps what jest.doMock() registered, so these registrations
|
||||
// hold for every boot in the file and the libraries are not loaded again. None
|
||||
// of them holds popup state; everything under src/ is still loaded fresh on
|
||||
// each boot.
|
||||
//
|
||||
// A test's own mock of one of them: a jest.doMock() made inside the test
|
||||
// replaces the registration here, as it would for any module. A top-of-file
|
||||
// jest.mock() is what the require() below gets, so it is kept, but its factory
|
||||
// runs once per file and every boot shares the same mock object.
|
||||
const ethers = require("ethers");
|
||||
const sodium = require("libsodium-wrappers-sumo");
|
||||
const QRCode = require("qrcode");
|
||||
const makeBlockie = require("ethereum-blockies-base64");
|
||||
jest.doMock("ethers", () => ethers);
|
||||
jest.doMock("libsodium-wrappers-sumo", () => sodium);
|
||||
jest.doMock("qrcode", () => QRCode);
|
||||
jest.doMock("ethereum-blockies-base64", () => makeBlockie);
|
||||
|
||||
const POPUP_HTML = fs.readFileSync(
|
||||
path.join(__dirname, "..", "..", "src", "popup", "index.html"),
|
||||
"utf8",
|
||||
|
||||
Reference in New Issue
Block a user