allowedSites and deniedSites held the bare hostname, so a grant to https://dapp.example also authorised http://dapp.example and every port on that host, and the connection, transaction and signature prompts named only the hostname. Both lists now store and match the full origin (scheme://host[:port]), the key the connections approved without Remember already used. The prompts, the Settings site lists and AUTISTMASK_REMOVE_SITE use the origin too. Entries saved by hostname are not migrated (pre-1.0): they match no site. Model: opus-5-5
141 lines
4.3 KiB
JavaScript
141 lines
4.3 KiB
JavaScript
// The connection, transaction and signature prompts name the site by its full
|
|
// origin, scheme and port included, not by its bare hostname
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/402). A page served over http,
|
|
// or on another port, of a host the user trusts over https must not raise a
|
|
// prompt that reads as that trusted site.
|
|
//
|
|
// Driven against a minimal DOM stub in the shape
|
|
// tests/contractCreation.test.js uses.
|
|
|
|
globalThis.chrome = {
|
|
storage: { local: { get: async () => ({}), set: async () => {} } },
|
|
};
|
|
|
|
const { state } = require("../src/shared/state");
|
|
const approval = require("../src/popup/views/approval");
|
|
|
|
// The site asking, in cleartext and on a port, which is what the hostname
|
|
// alone, dapp.example, used to hide.
|
|
const ORIGIN = "http://dapp.example:8080";
|
|
const FROM = "0x0000000000000000000000000000000000000a11";
|
|
const RECIPIENT = "0x66133E8ea0f5D1d612D2502a968757D1048c214a";
|
|
|
|
function makeElement(id) {
|
|
const classes = new Set();
|
|
const el = {
|
|
id,
|
|
textContent: "",
|
|
value: "",
|
|
innerHTML: "",
|
|
disabled: false,
|
|
style: {},
|
|
dataset: {},
|
|
classList: {
|
|
add: (...names) => names.forEach((n) => classes.add(n)),
|
|
remove: (...names) => names.forEach((n) => classes.delete(n)),
|
|
contains: (n) => classes.has(n),
|
|
toggle: (n, force) => {
|
|
const on = force === undefined ? !classes.has(n) : force;
|
|
if (on) classes.add(n);
|
|
else classes.delete(n);
|
|
return on;
|
|
},
|
|
},
|
|
addEventListener: () => {},
|
|
querySelectorAll: () => [],
|
|
appendChild: () => {},
|
|
};
|
|
// Views reach for .parentElement to hide whole sections.
|
|
Object.defineProperty(el, "parentElement", {
|
|
get: () => node(id + "-parent"),
|
|
});
|
|
return el;
|
|
}
|
|
|
|
function makeDocument() {
|
|
const els = new Map();
|
|
return {
|
|
getElementById(id) {
|
|
// The debug banner is created on demand by helpers.js; absent
|
|
// is the state a non-debug, non-testnet popup is in.
|
|
if (id === "debug-banner") return null;
|
|
if (!els.has(id)) els.set(id, makeElement(id));
|
|
return els.get(id);
|
|
},
|
|
createElement: () => makeElement("created"),
|
|
body: { prepend: () => {} },
|
|
};
|
|
}
|
|
|
|
function node(id) {
|
|
return globalThis.document.getElementById(id);
|
|
}
|
|
|
|
// Open the prompt the background describes with `details`, the way the popup
|
|
// does: it asks for the approval and show() draws it.
|
|
async function openApproval(details) {
|
|
globalThis.document = makeDocument();
|
|
globalThis.window = { location: { search: "" } };
|
|
globalThis.chrome.runtime = {
|
|
connect: () => ({ postMessage: () => {} }),
|
|
sendMessage: (msg, reply) => {
|
|
if (!reply) return;
|
|
if (msg.type !== "AUTISTMASK_GET_APPROVAL") return reply(null);
|
|
reply({
|
|
origin: ORIGIN,
|
|
isPhishingDomain: false,
|
|
approvedFrom: FROM,
|
|
...details,
|
|
});
|
|
},
|
|
};
|
|
approval.init({});
|
|
await approval.show(1);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
state.wallets = [];
|
|
state.activeAddress = FROM;
|
|
state.viewData = {};
|
|
state.viewStack = [];
|
|
state.currentView = null;
|
|
});
|
|
|
|
test("the connection prompt shows the origin", async () => {
|
|
await openApproval({});
|
|
expect(node("approve-origin").textContent).toBe(ORIGIN);
|
|
});
|
|
|
|
test("the transaction prompt shows the origin", async () => {
|
|
await openApproval({
|
|
type: "tx",
|
|
approvedTx: {
|
|
type: 2,
|
|
from: FROM,
|
|
chainId: "0x1",
|
|
nonce: "0x7",
|
|
gasLimit: "0x5208",
|
|
maxPriorityFeePerGas: "0x3b9aca00",
|
|
maxFeePerGas: "0x77359400",
|
|
to: RECIPIENT,
|
|
value: "0x0",
|
|
data: "0x",
|
|
accessList: [],
|
|
},
|
|
});
|
|
expect(node("approve-tx-origin").textContent).toBe(ORIGIN);
|
|
});
|
|
|
|
test("the signature prompt shows the origin", async () => {
|
|
await openApproval({
|
|
type: "sign",
|
|
// "Hello" as the hex a dApp passes to personal_sign.
|
|
signParams: {
|
|
method: "personal_sign",
|
|
message: "0x48656c6c6f",
|
|
from: FROM,
|
|
},
|
|
});
|
|
expect(node("approve-sign-origin").textContent).toBe(ORIGIN);
|
|
});
|