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);
}