security: announce a fresh EIP-6963 provider UUID per page load (closes #398)
The provider UUID was generated once, stored in extension storage, and announced verbatim to every page on every load and across restarts, so any site — connected or not — could read a stable cross-site, cross-session identifier for the install: a supercookie contradicting the "no tracking" promise. EIP-6963 wants a fresh UUIDv4 per announcement instead. inpage.js now announces a per-load crypto.randomUUID() and persists nothing; the eip6963Uuid storage key and the AUTISTMASK_PROVIDER_UUID content-script message are removed. That key was a standalone top-level storage entry, never part of the versioned autistmask profile, so stateSchema.js and the persisted-field harness are untouched and no existing profile is affected. A jest test asserts two loads announce different UUIDv4s and that one load reuses a single UUID across re-announcements. Model: opus-4-8
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
// The EIP-6963 provider UUID inpage.js announces (src/content/inpage.js).
|
||||
//
|
||||
// The bug this pins down (issue #398): the UUID used to be generated once,
|
||||
// persisted in extension storage, and announced verbatim to every page on
|
||||
// every load and across browser restarts, so any site — connected or not —
|
||||
// could read a stable cross-site, cross-session identifier for the install.
|
||||
// EIP-6963 wants a fresh UUIDv4 per announcement instead. The fix generates
|
||||
// it per page load and stores nothing.
|
||||
//
|
||||
// inpage.js is a bare IIFE injected into the page's JS context, not a module;
|
||||
// see tests/inpageErrors.test.js for why it is evaluated against a stub window
|
||||
// rather than imported. Here the stub captures the CustomEvent that carries
|
||||
// the announcement, so the UUID this file reads is the one a real dApp's
|
||||
// eip6963:announceProvider listener would see.
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { webcrypto } = require("crypto");
|
||||
|
||||
const SOURCE = fs.readFileSync(
|
||||
path.join(__dirname, "..", "src", "content", "inpage.js"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const loadInto = new Function(
|
||||
"window",
|
||||
"self",
|
||||
"crypto",
|
||||
"Event",
|
||||
"CustomEvent",
|
||||
SOURCE,
|
||||
);
|
||||
|
||||
class StubEvent {
|
||||
constructor(type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
class StubCustomEvent extends StubEvent {
|
||||
constructor(type, init) {
|
||||
super(type);
|
||||
this.detail = init && init.detail;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate inpage.js once against a fresh stub window and return every UUID it
|
||||
// announced. A `requestProvider` event is dispatched too, so a re-announcement
|
||||
// within one load is observed as well as the announcement at load.
|
||||
function announcedUuids() {
|
||||
const listeners = {};
|
||||
const uuids = [];
|
||||
|
||||
const win = {
|
||||
addEventListener(type, fn) {
|
||||
(listeners[type] || (listeners[type] = [])).push(fn);
|
||||
},
|
||||
removeEventListener(type, fn) {
|
||||
const fns = listeners[type];
|
||||
if (!fns) return;
|
||||
const i = fns.indexOf(fn);
|
||||
if (i !== -1) fns.splice(i, 1);
|
||||
},
|
||||
postMessage() {},
|
||||
dispatchEvent(event) {
|
||||
if (event.type === "eip6963:announceProvider") {
|
||||
uuids.push(event.detail.info.uuid);
|
||||
}
|
||||
for (const fn of (listeners[event.type] || []).slice()) fn(event);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
win.window = win;
|
||||
|
||||
loadInto(win, win, webcrypto, StubEvent, StubCustomEvent);
|
||||
win.dispatchEvent(new StubEvent("eip6963:requestProvider"));
|
||||
return uuids;
|
||||
}
|
||||
|
||||
const UUID_V4 =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||
|
||||
describe("the EIP-6963 provider UUID is fresh per page load", () => {
|
||||
test("a load announces a UUIDv4, unprompted, with nothing delivered", () => {
|
||||
const uuids = announcedUuids();
|
||||
expect(uuids.length).toBeGreaterThan(0);
|
||||
expect(uuids[0]).toMatch(UUID_V4);
|
||||
});
|
||||
|
||||
test("every announcement within one load carries the same UUID", () => {
|
||||
const uuids = announcedUuids();
|
||||
expect(uuids.length).toBeGreaterThan(1);
|
||||
expect(new Set(uuids).size).toBe(1);
|
||||
});
|
||||
|
||||
test("two page loads announce different UUIDs", () => {
|
||||
const first = announcedUuids()[0];
|
||||
const second = announcedUuids()[0];
|
||||
expect(first).toMatch(UUID_V4);
|
||||
expect(second).toMatch(UUID_V4);
|
||||
expect(second).not.toBe(first);
|
||||
});
|
||||
});
|
||||
Fai riferimento in un nuovo problema
Block a user