From 076902c0a79eb654814a3c4978b4287af988e72f Mon Sep 17 00:00:00 2001 From: Edgars Date: Thu, 9 Jul 2026 21:36:16 +0100 Subject: [PATCH] fix(wallet): harden bridge (verify signer, Host check, constant-time token, 0700, url scrub, body cap) Targeted hardening of the browser-wallet signing bridge per security review; all existing behavior and tests preserved. - Verify signer: reject a wallet result whose from differs from the connected/expected signer, both in the bridge (handleResult) and in browserSend (assertResultSigner); plumb from through sessionClient.waitForTxResult. On accountsChanged the page re-POSTs /api/connected so the daemon stays coherent, and the misleading CLI-will-verify-the-sender copy now matches the enforced behavior. - Host-header validation: reject Host != 127.0.0.1:/localhost: with 403 before route dispatch (validated against the port captured at start() so long-polls flushed during teardown still pass). - Constant-time token compare via length-guarded timingSafeEqual. - Create ~/.genlayer and keystores dir with mode 0o700 + chmodSync. - Scrub the hash token from the URL via history.replaceState. - Cap request bodies at 64KB (413 PayloadTooLargeError). - Warn against ssh -g / GatewayPorts yes / public-interface binding. Existing no-CORS, origin-fail-closed, and token-on-every-route checks are unchanged. --- src/commands/wallet/WalletAction.ts | 5 +- src/lib/config/ConfigFileManager.ts | 6 +- src/lib/wallet/bridgePage.ts | 29 ++++- src/lib/wallet/browserBridge.ts | 170 +++++++++++++++++++++------ src/lib/wallet/browserSend.ts | 38 ++++-- src/lib/wallet/sessionClient.ts | 14 ++- tests/libs/browserBridge.test.ts | 55 +++++++++ tests/libs/configFileManager.test.ts | 7 +- tests/libs/sessionClient.test.ts | 4 +- 9 files changed, 271 insertions(+), 57 deletions(-) diff --git a/src/commands/wallet/WalletAction.ts b/src/commands/wallet/WalletAction.ts index e66982d6..edc5c5a5 100644 --- a/src/commands/wallet/WalletAction.ts +++ b/src/commands/wallet/WalletAction.ts @@ -83,7 +83,10 @@ export class WalletAction extends BaseAction { const client = new WalletSessionClient(ready); const state = await client.state(); this.logInfo(`Open this URL in a browser with your wallet to connect:\n ${state.url}`); - this.logInfo("(Remote/SSH? Forward the port first: ssh -L :127.0.0.1: ...)"); + this.logInfo( + "(Remote/SSH? Forward the port first: ssh -L :127.0.0.1: ...; " + + "do not use -g, GatewayPorts yes, or bind the local side to a public interface.)", + ); this.startSpinner("Waiting for wallet connection..."); try { diff --git a/src/lib/config/ConfigFileManager.ts b/src/lib/config/ConfigFileManager.ts index 4393e5a3..48e7564e 100644 --- a/src/lib/config/ConfigFileManager.ts +++ b/src/lib/config/ConfigFileManager.ts @@ -26,14 +26,16 @@ export class ConfigFileManager { private ensureFolderExists(): void { if (!fs.existsSync(this.folderPath)) { - fs.mkdirSync(this.folderPath, { recursive: true }); + fs.mkdirSync(this.folderPath, { recursive: true, mode: 0o700 }); } + fs.chmodSync(this.folderPath, 0o700); } private ensureKeystoresDirExists(): void { if (!fs.existsSync(this.keystoresPath)) { - fs.mkdirSync(this.keystoresPath, { recursive: true }); + fs.mkdirSync(this.keystoresPath, { recursive: true, mode: 0o700 }); } + fs.chmodSync(this.keystoresPath, 0o700); } private ensureConfigFileExists(): void { diff --git a/src/lib/wallet/bridgePage.ts b/src/lib/wallet/bridgePage.ts index 90f04c4d..178fff4a 100644 --- a/src/lib/wallet/bridgePage.ts +++ b/src/lib/wallet/bridgePage.ts @@ -63,6 +63,7 @@ export const BRIDGE_PAGE_HTML = /* html */ `