diff --git a/LICENSE b/LICENSE index f288702..f8a0f60 100644 --- a/LICENSE +++ b/LICENSE @@ -672,3 +672,110 @@ may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . + +=========================================================================== +THIRD-PARTY FILES +=========================================================================== + +The following files are not original to this project and are distributed +under their own licenses. They are NOT covered by the GPL-3.0 license above. + +--------------------------------------------------------------------------- +File: src/shared/phishingBlocklist.json +Source: https://github.com/AugurProject/eth-phishing-detect (config.json) +Copyright: Copyright (c) 2018 kumavis +License: Don't Be a Dick Public License (DBAD), Version 1.2 +--------------------------------------------------------------------------- + +DON'T BE A DICK PUBLIC LICENSE + +Version 1.2, February 2021 + +Copyright (C) 2018 kumavis + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document. + +DON'T BE A DICK PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 1. Do whatever you like with the original work, just don't be a dick. + + Being a dick includes - but is not limited to - the following instances: + + 1a. Outright copyright infringement - Don't just copy the original + work/works and change the name. + 1b. Selling the unmodified original with no work done what-so-ever, + that's REALLY being a dick. + 1c. Modifying the original work to contain hidden harmful content. + That would make you a PROPER dick. + + 2. If you become rich through modifications, related works/services, or + supporting the original work, share the love. Only a dick would make + loads off this work and not buy the original work's creator(s) a pint. + + 3. Code is provided with no warranty. Using somebody else's code and + bitching when it goes wrong makes you a DONKEY dick. Fix the problem + yourself. A non-dick would submit the fix back or submit a bug report. + + 4. If you use code, calling it your own would make you a ROYAL dick. + Alternatively, even just a comment giving attribution to where you found + the code would be OK. + +--------------------------------------------------------------------------- +File: src/shared/scamlist.js (address data from MyEtherWallet ethereum-lists) +Source: https://github.com/MyEtherWallet/ethereum-lists (addresses-darklist.json) +Copyright: Copyright (c) 2020 MyEtherWallet +License: MIT License +--------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2020 MyEtherWallet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--------------------------------------------------------------------------- +File: src/shared/scamlist.js (address data from EtherScamDB) +Source: https://github.com/MrLuit/EtherScamDB (scams.yaml) +Copyright: Copyright (c) 2018 Luit Hollander +License: MIT License +--------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2018 Luit Hollander + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 42ea566..1a141ee 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,12 @@ Hence, a minimally viable ERC20 browser wallet/signer that works cross-platform. Everything you need, nothing you don't. We import as few libraries as possible, don't implement any crypto, and don't send user-specific data anywhere but a (user-configurable) Ethereum RPC endpoint (which defaults to a public node). The -extension contacts exactly three external services: the configured RPC node for +extension contacts three user-configurable services: the configured RPC node for blockchain interactions, a public CoinDesk API (no API key) for realtime price information, and a Blockscout block-explorer API for transaction history and -token balances. All three endpoints are user-configurable. +token balances. It also fetches a community-maintained phishing domain blocklist +periodically and performs best-effort Etherscan address label lookups during +transaction confirmation. In the extension is a hardcoded list of the top ERC20 contract addresses. You can add any ERC20 contract by contract address if you wish, but the hardcoded @@ -435,25 +437,29 @@ transitions. #### TransactionDetail - **When**: User tapped a transaction row from AddressDetail or AddressToken. -- **Elements**: +- **Elements** (grouped into logical blocks using light well containers; field + labels are self-explanatory so groups have no headings): - "Transaction" heading, "Back" button + - Transaction hash: full hash (tap to copy) + etherscan link - Type: transaction classification — one of: Native ETH Transfer, ERC-20 Token Transfer, Swap, Token Approval, Contract Call, Contract Creation + - Status: "Success" or "Failed" + - From: blockie + color dot + full address (tap to copy) + etherscan link; + ENS name if available + - To: blockie + color dot + full address (tap to copy) + etherscan link; ENS + name if available + - Time: ISO datetime + relative age in parentheses + - Block: block number (tap to copy) + etherscan block link + - Amount: value + symbol (bold) + - Native quantity: raw integer + unit (shown when available) - Token contract: shown for ERC-20 transfers — color dot + full contract address (tap to copy) + etherscan token link - - Status: "Success" or "Failed" - - Time: ISO datetime + relative age in parentheses - - Amount: value + symbol (bold) - - From: blockie + color dot + full address (tap to copy) + etherscan link - - ENS name if available - - To: blockie + color dot + full address (tap to copy) + etherscan link - - ENS name if available - - Transaction hash: full hash (tap to copy) + etherscan link - - Block: block number (tap to copy) + etherscan block link - - Nonce: transaction nonce (tap to copy) - - Transaction fee: ETH amount (tap to copy) - - Gas price: value in Gwei (tap to copy) - - Gas used: integer (tap to copy) + - Decoded details (shown for contract calls): action name, decoded + parameters, token details, swap steps + - Network details (shown when on-chain data is available): nonce, gas price, + gas used, transaction fee (all tap to copy) + - Raw data (shown when calldata is present): full calldata in monospace + dashed border - **Transitions**: - "Back" → **AddressToken** (if `selectedToken` set) or **AddressDetail** @@ -576,14 +582,25 @@ What the extension does NOT do: - No analytics or telemetry services - No token list APIs (user adds tokens manually by contract address) -- No phishing/blocklist APIs - No Infura/Alchemy dependency (any JSON-RPC endpoint works) - No backend servers operated by the developer -These three services (RPC endpoint, CoinDesk price API, and Blockscout API) are -the only external services. All three endpoints are user-configurable. Users who -want maximum privacy can point the RPC and Blockscout URLs at their own -self-hosted instances (price fetching can be disabled in a future version). +In addition to the three user-configurable services above (RPC endpoint, +CoinDesk price API, and Blockscout API), AutistMask also contacts: + +- **Phishing domain blocklist**: A community-maintained phishing domain + blocklist is vendored into the extension at build time. At runtime, the + extension fetches the live list once every 24 hours to detect newly added + domains. Only the delta (domains not already in the vendored list) is kept in + memory, keeping runtime memory usage small. The delta is persisted to + localStorage if it is under 256 KiB. +- **Etherscan address labels**: When confirming a transaction, the extension + performs a best-effort lookup of the recipient address on Etherscan to check + for phishing/scam labels. This is a direct page fetch with no API key; the + user's browser makes the request. + +Users who want maximum privacy can point the RPC and Blockscout URLs at their +own self-hosted instances (price fetching can be disabled in a future version). ### Dependencies @@ -773,6 +790,21 @@ indexes it as a real token transfer. designed as a sharp tool — users who understand the risks can configure the wallet to show everything unfiltered, unix-style. +#### Phishing Domain Protection + +AutistMask protects users from known phishing sites when they connect their +wallet or approve transactions/signatures. A community-maintained domain +blocklist is vendored into the extension at build time, providing immediate +protection without any network requests. At runtime, the extension fetches the +live list once every 24 hours and keeps only the delta (newly added domains not +in the vendored list) in memory. This architecture keeps runtime memory usage +small while ensuring fresh coverage of new phishing domains. + +When a dApp on a blocklisted domain requests a wallet connection, transaction +approval, or signature, the approval popup displays a prominent red warning +banner alerting the user. The domain checker matches exact hostnames and all +parent domains (subdomain matching). + #### Transaction Decoding When a dApp asks the user to approve a transaction, AutistMask attempts to @@ -855,6 +887,21 @@ Currently supported: GPL-3.0. See [LICENSE](LICENSE). +### Third-Party Data Files + +This repository includes data files from third-party projects that are not +covered by the GPL-3.0 license above. These files, their copyright holders, and +their licenses are: + +| File | Source | Copyright | License | +| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------- | +| `src/shared/phishingBlocklist.json` | [eth-phishing-detect](https://github.com/AugurProject/eth-phishing-detect) community-maintained phishing domain blocklist | Copyright (c) 2018 kumavis | [DBAD (Don't Be a Dick)](https://github.com/philsturgeon/dbad) | +| `src/shared/scamlist.js` (address data from MyEtherWallet) | [ethereum-lists](https://github.com/MyEtherWallet/ethereum-lists) `addresses-darklist.json` | Copyright (c) 2020 MyEtherWallet | MIT | +| `src/shared/scamlist.js` (address data from EtherScamDB) | [EtherScamDB](https://github.com/MrLuit/EtherScamDB) `scams.yaml` | Copyright (c) 2018 Luit Hollander | MIT | + +The full license texts for these third-party files are included in the +[LICENSE](LICENSE) file. + ## Author [@sneak](https://sneak.berlin) diff --git a/src/background/index.js b/src/background/index.js index 1060fcf..9a4879f 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -2,16 +2,25 @@ // Handles EIP-1193 RPC requests from content scripts and proxies // non-sensitive calls to the configured Ethereum JSON-RPC endpoint. -const { - ETHEREUM_MAINNET_CHAIN_ID, - DEFAULT_RPC_URL, -} = require("../shared/constants"); +const { DEFAULT_RPC_URL } = require("../shared/constants"); +const { SUPPORTED_CHAIN_IDS, networkByChainId } = require("../shared/networks"); +const { onChainSwitch } = require("../shared/chainSwitch"); const { getBytes } = require("ethers"); -const { state, loadState, saveState } = require("../shared/state"); +const { + state, + loadState, + saveState, + currentNetwork, +} = require("../shared/state"); const { refreshBalances, getProvider } = require("../shared/balances"); const { debugFetch } = require("../shared/log"); const { decryptWithPassword } = require("../shared/vault"); const { getSignerForAddress } = require("../shared/wallet"); +const { + isPhishingDomain, + updatePhishingList, + startPeriodicRefresh, +} = require("../shared/phishingDomains"); const storageApi = typeof browser !== "undefined" @@ -324,31 +333,43 @@ async function handleRpc(method, params, origin) { } if (method === "eth_chainId") { - return { result: ETHEREUM_MAINNET_CHAIN_ID }; + return { result: currentNetwork().chainId }; } if (method === "net_version") { - return { result: "1" }; + return { result: currentNetwork().networkVersion }; } if (method === "wallet_switchEthereumChain") { const chainId = params?.[0]?.chainId; - if (chainId === ETHEREUM_MAINNET_CHAIN_ID) { + if (chainId === currentNetwork().chainId) { + return { result: null }; + } + if (SUPPORTED_CHAIN_IDS.has(chainId)) { + const target = networkByChainId(chainId); + await onChainSwitch(target.id); + broadcastChainChanged(target.chainId); return { result: null }; } return { error: { code: 4902, - message: "AutistMask only supports Ethereum mainnet.", + message: + "AutistMask supports Ethereum Mainnet and Sepolia Testnet only.", }, }; } if (method === "wallet_addEthereumChain") { + const chainId = params?.[0]?.chainId; + if (SUPPORTED_CHAIN_IDS.has(chainId)) { + return { result: null }; + } return { error: { code: 4902, - message: "AutistMask only supports Ethereum mainnet.", + message: + "AutistMask supports Ethereum Mainnet and Sepolia Testnet only.", }, }; } @@ -494,6 +515,27 @@ async function handleRpc(method, params, origin) { return { error: { message: "Unsupported method: " + method } }; } +// Broadcast chainChanged to all tabs when the network is switched. +function broadcastChainChanged(chainId) { + tabsApi.query({}, (tabs) => { + for (const tab of tabs) { + tabsApi.sendMessage( + tab.id, + { + type: "AUTISTMASK_EVENT", + eventName: "chainChanged", + data: chainId, + }, + () => { + if (runtime.lastError) { + // expected for tabs without our content script + } + }, + ); + } + }); +} + // Broadcast accountsChanged to all tabs, respecting per-address permissions async function broadcastAccountsChanged() { // Clear non-remembered approvals on address switch @@ -571,6 +613,11 @@ async function backgroundRefresh() { setInterval(backgroundRefresh, BACKGROUND_REFRESH_INTERVAL); +// Fetch the phishing domain blocklist delta on startup and refresh every 24h. +// The vendored blocklist is bundled at build time; this fetches only new entries. +updatePhishingList(); +startPeriodicRefresh(); + // When approval window is closed without a response, treat as rejection if (windowsApi && windowsApi.onRemoved) { windowsApi.onRemoved.addListener((windowId) => { @@ -643,6 +690,8 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => { resp.type = "sign"; resp.signParams = approval.signParams; } + // Flag if the requesting domain is on the phishing blocklist. + resp.isPhishingDomain = isPhishingDomain(approval.hostname); sendResponse(resp); } else { sendResponse(null); diff --git a/src/content/inpage.js b/src/content/inpage.js index 9a95012..8147f49 100644 --- a/src/content/inpage.js +++ b/src/content/inpage.js @@ -2,7 +2,10 @@ // Creates window.ethereum (EIP-1193 provider) and announces via EIP-6963. (function () { - const CHAIN_ID = "0x1"; // Ethereum mainnet + // Defaults to mainnet; updated dynamically via eth_chainId on init and + // chainChanged events from the extension. + let currentChainId = "0x1"; + let currentNetworkVersion = "1"; const listeners = {}; let nextId = 1; @@ -28,6 +31,12 @@ if (event.source !== window) return; if (event.data?.type !== "AUTISTMASK_EVENT") return; const { eventName, data } = event.data; + if (eventName === "chainChanged") { + currentChainId = data; + currentNetworkVersion = String(parseInt(data, 16)); + provider.chainId = currentChainId; + provider.networkVersion = currentNetworkVersion; + } emit(eventName, data); }); @@ -57,8 +66,8 @@ const provider = { isAutistMask: true, isMetaMask: true, // compatibility — many dApps check this - chainId: CHAIN_ID, - networkVersion: "1", + chainId: currentChainId, + networkVersion: currentNetworkVersion, selectedAddress: null, async request(args) { @@ -75,6 +84,12 @@ ? result[0] : null; } + if (args.method === "eth_chainId" && result) { + currentChainId = result; + currentNetworkVersion = String(parseInt(result, 16)); + provider.chainId = currentChainId; + provider.networkVersion = currentNetworkVersion; + } return result; }, @@ -189,4 +204,19 @@ window.addEventListener("eip6963:requestProvider", announceProvider); announceProvider(); + + // Fetch the current chain ID from the extension on load so the provider + // reflects the selected network immediately (covers Sepolia etc.). + sendRequest({ method: "eth_chainId", params: [] }) + .then((chainId) => { + if (chainId) { + currentChainId = chainId; + currentNetworkVersion = String(parseInt(chainId, 16)); + provider.chainId = currentChainId; + provider.networkVersion = currentNetworkVersion; + } + }) + .catch(() => { + // Best-effort — keep defaults. + }); })(); diff --git a/src/popup/index.html b/src/popup/index.html index 336431c..90fb615 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -605,6 +605,43 @@ Double-check the address before sending. + + +
+
+

Network

+

+ Select the Ethereum network. Switching networks will + update the RPC and Blockscout endpoints to their + defaults. +

+
+ +
+
+

Ethereum RPC

@@ -1064,93 +1119,149 @@

Transaction

- -
-
Status
-
-
-
-
Time
-
-
-
-
Amount
-
-
- -
-
From
-
-
-
-
To
-
-
- -