A page with an opaque origin, such as one its server sandboxes with a Content-Security-Policy, is reported to the background with the origin "null". The background took that as a site, so every such page on every website was one site: a connection remembered for one covered them all, and the phishing check never matched. A request whose origin is "null" is now refused with 4100, like one with no origin, before any method runs, and is not credited to the site its URL names. tests/rpcOrigin.test.js sends one, and README's SiteApproval says so. Model: opus-5-5
173 lines
5.4 KiB
JavaScript
173 lines
5.4 KiB
JavaScript
// 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).
|
|
//
|
|
// A page with an opaque origin, such as one its server sandboxes with a
|
|
// Content-Security-Policy, is reported with the origin "null". That names no
|
|
// site, so its request is refused
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/527).
|
|
//
|
|
// Every other 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 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_ORIGIN] },
|
|
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.",
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("a request from a page with an opaque origin", () => {
|
|
test("is refused, whatever the page's URL says", async () => {
|
|
const accounts = loadBackground();
|
|
|
|
const result = await accounts({
|
|
origin: "null",
|
|
url: CONNECTED_ORIGIN + "/sandboxed.html",
|
|
tab: { url: CONNECTED_ORIGIN + "/sandboxed.html" },
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
error: {
|
|
code: 4100,
|
|
message:
|
|
"The wallet could not tell which site sent this request.",
|
|
},
|
|
});
|
|
});
|
|
});
|