harden: one connection and one signature prompt per site at a time (closes #405)
Each eth_requestAccounts or personal_sign call opened another approval window, so a page calling in a loop could cover the screen with identical prompts. While a site's connection or signature prompt is unanswered, a further request of that kind from the same site is now refused with EIP-1193 -32002, the code a second transaction already gets, and opens no window. The pending approval itself is the record: the check and the approval's creation have nothing awaited between them. Other sites are unaffected, and the site may ask again once the user answers. Model: opus-5-5
This commit is contained in:
@@ -331,7 +331,7 @@ function loadBackground(options) {
|
||||
|
||||
// The same for a message-signing approval, which pins the signing address
|
||||
// at approval time in exactly the same way.
|
||||
function requestSign(from) {
|
||||
function requestSign(from, origin) {
|
||||
let rpcResult = null;
|
||||
messageListener(
|
||||
{
|
||||
@@ -339,7 +339,7 @@ function loadBackground(options) {
|
||||
method: "personal_sign",
|
||||
params: [MESSAGE, from || signer.address],
|
||||
},
|
||||
{ origin: ORIGIN },
|
||||
{ origin: origin || ORIGIN },
|
||||
(r) => {
|
||||
rpcResult = r;
|
||||
},
|
||||
@@ -822,6 +822,87 @@ describe("one transaction approval at a time", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// A page that asks again before the user has answered its last connection or
|
||||
// signature request is refused, instead of opening one more window per call.
|
||||
describe("one connection and one signature approval per site at a time", () => {
|
||||
const PENDING_REFUSAL = {
|
||||
error: {
|
||||
code: -32002,
|
||||
message: expect.stringMatching(/already waiting for your answer/),
|
||||
},
|
||||
};
|
||||
|
||||
test("a loop of eth_requestAccounts opens one approval and refuses the rest", async () => {
|
||||
const bg = loadBackground();
|
||||
const requests = [];
|
||||
for (let i = 0; i < 5; i++) requests.push(bg.requestSite());
|
||||
await settle();
|
||||
|
||||
expect(bg.created).toHaveLength(1);
|
||||
expect(requests[0].result()).toBeNull();
|
||||
for (const extra of requests.slice(1)) {
|
||||
expect(extra.result()).toEqual(PENDING_REFUSAL);
|
||||
}
|
||||
|
||||
// Once the user has answered, the site may ask again.
|
||||
bg.closeWindow(1);
|
||||
await settle();
|
||||
expect(requests[0].result()).toEqual({
|
||||
error: { code: 4001, message: "User rejected the request." },
|
||||
});
|
||||
const again = bg.requestSite();
|
||||
await settle();
|
||||
expect(again.result()).toBeNull();
|
||||
expect(bg.created).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("a loop of personal_sign opens one approval and refuses the rest", async () => {
|
||||
const bg = loadBackground();
|
||||
const requests = [];
|
||||
for (let i = 0; i < 5; i++) requests.push(bg.requestSign());
|
||||
await settle();
|
||||
|
||||
expect(bg.created).toHaveLength(1);
|
||||
expect(requests[0].result()).toBeNull();
|
||||
for (const extra of requests.slice(1)) {
|
||||
expect(extra.result()).toEqual(PENDING_REFUSAL);
|
||||
}
|
||||
});
|
||||
|
||||
test("another site's connection request is not held up", async () => {
|
||||
const bg = loadBackground();
|
||||
bg.requestSite();
|
||||
const repeat = bg.requestSite();
|
||||
const other = bg.requestSite(UNCONNECTED_ORIGIN);
|
||||
await settle();
|
||||
|
||||
expect(repeat.result()).toEqual(PENDING_REFUSAL);
|
||||
expect(other.result()).toBeNull();
|
||||
expect(bg.created).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("another site's signature request is not held up", async () => {
|
||||
const bg = loadBackground();
|
||||
// Connect a second site, so that it may ask for a signature at all.
|
||||
const connecting = bg.requestSite();
|
||||
await settle();
|
||||
const port = bg.connectApproval(connecting.id());
|
||||
port.decide(true, false);
|
||||
port.disconnect();
|
||||
await settle();
|
||||
expect(connecting.result()).toEqual({ result: [signer.address] });
|
||||
|
||||
bg.requestSign();
|
||||
const repeat = bg.requestSign();
|
||||
const other = bg.requestSign(signer.address, FRESH_ORIGIN);
|
||||
await settle();
|
||||
|
||||
expect(repeat.result()).toEqual(PENDING_REFUSAL);
|
||||
expect(other.result()).toBeNull();
|
||||
expect(bg.created).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
// A nonce collision found before the transaction reaches the network is the
|
||||
// one send failure the wallet can speak about with certainty. The user is told
|
||||
// it did not go out and to send it again, rather than being warned it might
|
||||
|
||||
Reference in New Issue
Block a user