harden: end a site's unremembered connection when its address or wallet is removed (closes #245)
A site connected without "Remember" lives only in the background's in-memory connectedSites map. Removing an address or deleting a wallet dropped the remembered permissions but never told the background; the entry went only as a side effect of the accountsChanged broadcast, which empties the whole map when the active address changes. dropSitePermissions(), shared by both removal paths, now sends AUTISTMASK_ADDRESSES_REMOVED with the removed addresses, and the background deletes their entries. Only the extension's own pages may send it. Model: opus-5-5
This commit was merged in pull request #416.
This commit is contained in:
@@ -25,6 +25,10 @@ const { Network, Wallet } = require("ethers");
|
||||
// what the user is actually shown.
|
||||
const { describeSigningFailure } = require("../src/shared/approvalVerify");
|
||||
const { makeStorageStub } = require("./support/storageStub");
|
||||
const {
|
||||
removeAddressFromState,
|
||||
removeWalletFromState,
|
||||
} = require("../src/shared/walletDelete");
|
||||
|
||||
const SIGNER_KEY =
|
||||
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
|
||||
@@ -2096,3 +2100,95 @@ describe("a site connection decided as the popup closes", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// A site connected without "Remember" is held only in the background's memory,
|
||||
// keyed to the address it was connected to. Removing that address, or the
|
||||
// wallet holding it, must end the connection as part of the removal itself.
|
||||
// The accountsChanged broadcast the views send afterwards also clears it, but
|
||||
// only when the active address moved, and nothing waits for it to arrive.
|
||||
//
|
||||
// Each test asks the background while storage still names the removed address
|
||||
// as active, because the popup has not saved yet, so the answer turns on the
|
||||
// connection alone.
|
||||
describe("removing an address ends a site's connection to it", () => {
|
||||
// FRESH_ORIGIN connected to the active address without "Remember", in a
|
||||
// wallet holding a second address so that one can be removed at all. The
|
||||
// popup's messages reach the background as they would from the popup.
|
||||
async function connectedBackground() {
|
||||
const bg = loadBackground({ actionPopup: true });
|
||||
const stored = bg.storage.read("autistmask");
|
||||
stored.wallets[0].addresses.push({
|
||||
address: other.address,
|
||||
balance: "0",
|
||||
tokenBalances: [],
|
||||
});
|
||||
bg.storage.write("autistmask", stored);
|
||||
|
||||
const pending = bg.requestSite();
|
||||
await settle();
|
||||
bg.connectApproval(pending.id()).decide(true, false);
|
||||
await settle();
|
||||
expect(pending.result()).toEqual({ result: [signer.address] });
|
||||
|
||||
global.chrome.runtime.sendMessage = (msg) => {
|
||||
bg.send(msg, bg.fromPopup);
|
||||
};
|
||||
return bg;
|
||||
}
|
||||
|
||||
// What FRESH_ORIGIN is told when it asks which account it may use.
|
||||
async function siteAccounts(bg) {
|
||||
const { sendResponse } = bg.send(
|
||||
{ type: "AUTISTMASK_RPC", method: "eth_accounts", params: [] },
|
||||
{ origin: FRESH_ORIGIN },
|
||||
);
|
||||
await settle();
|
||||
return sendResponse.mock.calls[0][0];
|
||||
}
|
||||
|
||||
test("removing the connected address ends the connection", async () => {
|
||||
const bg = await connectedBackground();
|
||||
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
||||
|
||||
const popupState = bg.storage.read("autistmask");
|
||||
expect(removeAddressFromState(popupState, 0, 0).removed).toBe(true);
|
||||
|
||||
expect(await siteAccounts(bg)).toEqual({ result: [] });
|
||||
});
|
||||
|
||||
test("deleting the wallet holding the connected address ends the connection", async () => {
|
||||
const bg = await connectedBackground();
|
||||
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
||||
|
||||
const popupState = bg.storage.read("autistmask");
|
||||
removeWalletFromState(popupState, 0);
|
||||
|
||||
expect(await siteAccounts(bg)).toEqual({ result: [] });
|
||||
});
|
||||
|
||||
test("removing a different address leaves the connection alone", async () => {
|
||||
const bg = await connectedBackground();
|
||||
|
||||
const popupState = bg.storage.read("autistmask");
|
||||
expect(removeAddressFromState(popupState, 0, 1).removed).toBe(true);
|
||||
|
||||
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
||||
});
|
||||
|
||||
test("a page cannot end the connection", async () => {
|
||||
const bg = await connectedBackground();
|
||||
|
||||
const spoof = bg.send(
|
||||
{
|
||||
type: "AUTISTMASK_ADDRESSES_REMOVED",
|
||||
addresses: [signer.address],
|
||||
},
|
||||
{ url: FRESH_ORIGIN + "/index.html" },
|
||||
);
|
||||
|
||||
expect(spoof.sendResponse).toHaveBeenCalledWith({
|
||||
error: "Unauthorized sender",
|
||||
});
|
||||
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -407,7 +407,9 @@ describe("deleting without the password", () => {
|
||||
expect(saved.activeAddress).toBe(A0);
|
||||
expect(saved.selectedWallet).toBe(0);
|
||||
expect(saved.selectedAddress).toBe(0);
|
||||
expect(sent).toEqual([]);
|
||||
expect(sent).toEqual([
|
||||
{ type: "AUTISTMASK_ADDRESSES_REMOVED", addresses: [B0] },
|
||||
]);
|
||||
// Settings is stubbed, so this is where the route hands over, not
|
||||
// where it renders.
|
||||
expect(mockSettingsShow).toHaveBeenCalled();
|
||||
@@ -426,7 +428,10 @@ describe("deleting without the password", () => {
|
||||
"Wallet 3",
|
||||
]);
|
||||
expect(saved.activeAddress).toBe(B0);
|
||||
expect(sent).toEqual([{ type: "AUTISTMASK_ACTIVE_CHANGED" }]);
|
||||
expect(sent).toEqual([
|
||||
{ type: "AUTISTMASK_ADDRESSES_REMOVED", addresses: [A0, A1] },
|
||||
{ type: "AUTISTMASK_ACTIVE_CHANGED" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("deleting the last wallet lands on Welcome with nothing left", async () => {
|
||||
|
||||
Reference in New Issue
Block a user