diff --git a/src/common/utils/WalletUtils.test.ts b/src/common/utils/WalletUtils.test.ts index 32b73fb0..1a97115c 100644 --- a/src/common/utils/WalletUtils.test.ts +++ b/src/common/utils/WalletUtils.test.ts @@ -96,28 +96,13 @@ describe("WalletUtils.getKeplr", () => { vi.restoreAllMocks(); }); - it("should return window.keplr when extension is already set and identifies as Keplr", async () => { - const fakeKeplr = { isKeplr: true, marker: "keplr-instance" }; + it("should return window.keplr when extension is already set", async () => { + const fakeKeplr = { marker: "keplr-instance" }; w.keplr = fakeKeplr; const result = await WalletUtils.getKeplr(); expect(result).toBe(fakeKeplr); }); - it("should return undefined when window.keplr is set but isKeplr !== true", async () => { - // Defends against wallet-aggregator placeholder injection: a hostile extension can - // populate window.keplr with a Keplr-shaped object lacking the identity flag. - w.keplr = { marker: "aggregator-shim" }; - const result = await WalletUtils.getKeplr(); - expect(result).toBeUndefined(); - }); - - it("should return undefined when isKeplr is truthy-but-not-strictly-true", async () => { - // Strict equality `=== true` rejects truthy non-true values like 1 or "yes". - w.keplr = { isKeplr: 1, marker: "loose-keplr" }; - const result = await WalletUtils.getKeplr(); - expect(result).toBeUndefined(); - }); - it("should return the extension when document is complete and extension becomes available", async () => { // No w.keplr set; document.readyState in jsdom is "complete" by default. // The code path then does Promise.resolve(w[prop]) — returns undefined here. @@ -142,7 +127,7 @@ describe("WalletUtils.getKeplr", () => { configurable: true, get: () => "complete" }); - const fakeKeplr = { isKeplr: true, marker: "delayed-keplr" }; + const fakeKeplr = { marker: "delayed-keplr" }; w.keplr = fakeKeplr; document.dispatchEvent(new Event("readystatechange")); diff --git a/src/common/utils/WalletUtils.ts b/src/common/utils/WalletUtils.ts index a2c3ca49..e8727678 100644 --- a/src/common/utils/WalletUtils.ts +++ b/src/common/utils/WalletUtils.ts @@ -5,28 +5,26 @@ import { WalletManager } from "."; import { Wallet, NETWORK_DATA } from "@/networks"; import { fetchEndpoints } from "./EndpointService"; -// Defense against extensions that inject a `window.keplr` shim without identifying as -// Keplr. Real Keplr always sets `isKeplr === true`. Strict-equality guard mirrors the -// `isPhantom`/`isSolflare` checks in `src/networks/sol/wallet.ts` (PR #155). -function isRealKeplr(k?: Keplr): k is Keplr { - return (k as unknown as { isKeplr?: unknown })?.isKeplr === true; -} - +// Note: unlike Phantom/Solflare, Keplr does NOT expose an `isKeplr === true` marker +// on its Cosmos provider (`window.keplr`). The `isKeplr` flag in `@keplr-wallet/types` +// is on the EthereumProvider type — Keplr's EVM bridge — not on the main Keplr +// interface. A strict-equality marker check rejects real Keplr; do not re-add one +// without a verified, Cosmos-side identity signal. See `runbooks/webapp_wallet_network.md`. function getKeplrExtension(): Promise { const w = window as unknown as { keplr?: Keplr }; - if (isRealKeplr(w.keplr)) { + if (w.keplr) { return Promise.resolve(w.keplr); } if (document.readyState === "complete") { - return Promise.resolve(isRealKeplr(w.keplr) ? w.keplr : undefined); + return Promise.resolve(w.keplr); } return new Promise((resolve) => { const documentStateChange = (event: Event) => { if (event.target && (event.target as Document).readyState === "complete") { - resolve(isRealKeplr(w.keplr) ? w.keplr : undefined); + resolve(w.keplr); document.removeEventListener("readystatechange", documentStateChange); } };