From 1237cf849186ad46f7b15f32def0d8c15d8cc395 Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 11:34:32 -0800 Subject: [PATCH 1/7] security: increase minimum password length from 8 to 12 characters --- src/popup/views/addWallet.js | 4 ++-- src/popup/views/importKey.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/popup/views/addWallet.js b/src/popup/views/addWallet.js index 2e8e20e..eed7ac9 100644 --- a/src/popup/views/addWallet.js +++ b/src/popup/views/addWallet.js @@ -49,8 +49,8 @@ function init(ctx) { showFlash("Please choose a password."); return; } - if (pw.length < 8) { - showFlash("Password must be at least 8 characters."); + if (pw.length < 12) { + showFlash("Password must be at least 12 characters."); return; } if (pw !== pw2) { diff --git a/src/popup/views/importKey.js b/src/popup/views/importKey.js index 874e6cf..a3324a3 100644 --- a/src/popup/views/importKey.js +++ b/src/popup/views/importKey.js @@ -30,8 +30,8 @@ function init(ctx) { showFlash("Please choose a password."); return; } - if (pw.length < 8) { - showFlash("Password must be at least 8 characters."); + if (pw.length < 12) { + showFlash("Password must be at least 12 characters."); return; } if (pw !== pw2) { From 95314ff2298bb4088ee7e37d69fc9673cae91799 Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 11:34:48 -0800 Subject: [PATCH 2/7] security: replace predictable sequential approval IDs with crypto.randomUUID() --- src/background/index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/background/index.js b/src/background/index.js index 4d81256..a027264 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -30,7 +30,6 @@ const connectedSites = {}; // Pending approval requests: { id: { origin, hostname, resolve } } const pendingApprovals = {}; -let nextApprovalId = 1; async function getState() { const result = await storageApi.get("autistmask"); @@ -127,7 +126,7 @@ function openApprovalWindow(id) { // Prefers the browser-action popup (anchored to toolbar, no macOS Space switch). function requestApproval(origin, hostname) { return new Promise((resolve) => { - const id = nextApprovalId++; + const id = crypto.randomUUID(); pendingApprovals[id] = { origin, hostname, resolve }; if (actionApi && typeof actionApi.openPopup === "function") { @@ -152,7 +151,7 @@ function requestApproval(origin, hostname) { // Uses the toolbar popup only — no fallback window. function requestTxApproval(origin, hostname, txParams) { return new Promise((resolve) => { - const id = nextApprovalId++; + const id = crypto.randomUUID(); pendingApprovals[id] = { origin, hostname, @@ -184,7 +183,7 @@ function requestTxApproval(origin, hostname, txParams) { // popup URL is still set, so the user can click the toolbar icon to respond. function requestSignApproval(origin, hostname, signParams) { return new Promise((resolve) => { - const id = nextApprovalId++; + const id = crypto.randomUUID(); pendingApprovals[id] = { origin, hostname, @@ -216,7 +215,7 @@ function requestSignApproval(origin, hostname, signParams) { // popups naturally close on focus loss and the user can reopen them. runtime.onConnect.addListener((port) => { if (port.name.startsWith("approval:")) { - const id = parseInt(port.name.split(":")[1], 10); + const id = port.name.split(":")[1]; port.onDisconnect.addListener(() => { const approval = pendingApprovals[id]; if (approval) { From 13e2bdb0b00d238c6a500420fa6dddcda7693fde Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 11:35:21 -0800 Subject: [PATCH 3/7] security: add prominent danger warning for eth_sign requests --- src/background/index.js | 7 +++++++ src/popup/index.html | 6 ++++++ src/popup/views/approval.js | 12 ++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/background/index.js b/src/background/index.js index a027264..55bb12c 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -441,6 +441,13 @@ async function handleRpc(method, params, origin) { ? { method, message: params[0], from: params[1] } : { method, message: params[1], from: params[0] }; + if (method === "eth_sign") { + signParams.dangerWarning = + "\u26a0\ufe0f DANGER: This site is requesting to sign a raw hash. " + + "This can be used to sign transactions that drain your funds. " + + "Only proceed if you fully understand what you are signing."; + } + const decision = await requestSignApproval( origin, hostname, diff --git a/src/popup/index.html b/src/popup/index.html index 653093b..07ddb4d 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -1015,6 +1015,12 @@ wants you to sign a message.

+ +
Type
diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index 509557f..c86a1a0 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -294,6 +294,18 @@ function showSignApproval(details) { } } + // Display danger warning for eth_sign (raw hash signing) + const warningEl = $("approve-sign-danger-warning"); + if (warningEl) { + if (sp.dangerWarning) { + warningEl.textContent = sp.dangerWarning; + warningEl.classList.remove("hidden"); + } else { + warningEl.textContent = ""; + warningEl.classList.add("hidden"); + } + } + $("approve-sign-password").value = ""; $("approve-sign-error").classList.add("hidden"); $("btn-approve-sign").disabled = false; From d59ebfd46111f68ae3fec2d95a299d2ee15c44ff Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 11:35:31 -0800 Subject: [PATCH 4/7] security: derive RPC origin from sender instead of trusting msg.origin --- src/background/index.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/background/index.js b/src/background/index.js index 55bb12c..356b8d2 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -617,7 +617,19 @@ if (windowsApi && windowsApi.onRemoved) { // Listen for messages from content scripts and popup runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg.type === "AUTISTMASK_RPC") { - handleRpc(msg.method, msg.params, msg.origin).then((response) => { + // Derive origin from trusted sender info to prevent origin spoofing. + // Chrome MV3 provides sender.origin; Firefox MV2 fallback uses sender.tab.url. + let trustedOrigin = msg.origin; // fallback only if sender info unavailable + if (sender.origin) { + trustedOrigin = sender.origin; + } else if (sender.tab && sender.tab.url) { + try { + trustedOrigin = new URL(sender.tab.url).origin; + } catch { + // keep fallback + } + } + handleRpc(msg.method, msg.params, trustedOrigin).then((response) => { sendResponse(response); }); return true; From b478d9efa94fc9beec97b8073b86eb32a241ec85 Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 11:35:42 -0800 Subject: [PATCH 5/7] security: validate sender URL for popup-only messages --- src/background/index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/background/index.js b/src/background/index.js index 356b8d2..02ddeb5 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -635,6 +635,21 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { return true; } + // Validate that popup-only messages originate from the extension itself. + const POPUP_ONLY_TYPES = [ + "AUTISTMASK_GET_APPROVAL", + "AUTISTMASK_APPROVAL_RESPONSE", + "AUTISTMASK_TX_RESPONSE", + "AUTISTMASK_SIGN_RESPONSE", + ]; + if (POPUP_ONLY_TYPES.includes(msg.type)) { + const extUrl = runtime.getURL(""); + if (!sender.url || !sender.url.startsWith(extUrl)) { + sendResponse({ error: "Unauthorized sender" }); + return false; + } + } + if (msg.type === "AUTISTMASK_GET_APPROVAL") { const approval = pendingApprovals[msg.id]; if (approval) { From f13cd0fd4704529b8b34a1315c3ba0054b14e346 Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 11:36:19 -0800 Subject: [PATCH 6/7] security: add TODO comments for password plaintext over runtime.sendMessage --- src/background/index.js | 14 ++++++++++++-- src/popup/views/approval.js | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/background/index.js b/src/background/index.js index 02ddeb5..db30f43 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -714,7 +714,8 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { if (wallet) break; } if (!wallet) throw new Error("Wallet not found"); - const decrypted = await decryptWithPassword( + // TODO(security): Move decryption to popup to avoid sending password via runtime.sendMessage + let decrypted = await decryptWithPassword( wallet.encryptedSecret, msg.password, ); @@ -723,6 +724,10 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { addrIndex, decrypted, ); + // Best-effort: clear decrypted secret after use. + // Note: JS strings are immutable; this nulls the reference but + // the original string may persist in memory until GC. + decrypted = null; const provider = getProvider(state.rpcUrl); const connected = signer.connect(provider); const tx = await connected.sendTransaction(approval.txParams); @@ -768,7 +773,8 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { if (wallet) break; } if (!wallet) throw new Error("Wallet not found"); - const decrypted = await decryptWithPassword( + // TODO(security): Move decryption to popup to avoid sending password via runtime.sendMessage + let decrypted = await decryptWithPassword( wallet.encryptedSecret, msg.password, ); @@ -777,6 +783,10 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { addrIndex, decrypted, ); + // Best-effort: clear decrypted secret after use. + // Note: JS strings are immutable; this nulls the reference but + // the original string may persist in memory until GC. + decrypted = null; const sp = approval.signParams; let signature; diff --git a/src/popup/views/approval.js b/src/popup/views/approval.js index c86a1a0..359b506 100644 --- a/src/popup/views/approval.js +++ b/src/popup/views/approval.js @@ -385,6 +385,7 @@ function init(ctx) { type: "AUTISTMASK_TX_RESPONSE", id: approvalId, approved: true, + // TODO(security): Move decryption to popup to avoid sending password via runtime.sendMessage password: password, }, (response) => { @@ -424,6 +425,7 @@ function init(ctx) { type: "AUTISTMASK_SIGN_RESPONSE", id: approvalId, approved: true, + // TODO(security): Move decryption to popup to avoid sending password via runtime.sendMessage password: password, }, (response) => { From eec96f905425a2fb5e8e1bc369f04591f15c218e Mon Sep 17 00:00:00 2001 From: clawbot Date: Fri, 27 Feb 2026 11:36:38 -0800 Subject: [PATCH 7/7] security: clear decrypted secrets after use (best-effort) --- src/popup/index.html | 7 ++++++- src/popup/views/confirmTx.js | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/popup/index.html b/src/popup/index.html index 07ddb4d..6922bcd 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -1018,7 +1018,12 @@
diff --git a/src/popup/views/confirmTx.js b/src/popup/views/confirmTx.js index e7a4ca6..f11cf68 100644 --- a/src/popup/views/confirmTx.js +++ b/src/popup/views/confirmTx.js @@ -334,8 +334,13 @@ function init(ctx) { tx = await contract.transfer(pendingTx.to, amount); } + // Best-effort: clear decrypted secret after use. + // Note: JS strings are immutable; this nulls the reference but + // the original string may persist in memory until GC. + decryptedSecret = null; txStatus.showWait(pendingTx, tx.hash); } catch (e) { + decryptedSecret = null; const hash = tx ? tx.hash : null; txStatus.showError(pendingTx, hash, e.shortMessage || e.message); }