mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'master' into transactionControllerRefractor
This commit is contained in:
commit
25cffd21f8
@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
|
- Continuously update blacklist for known phishing sites in background.
|
||||||
|
- Automatically detect suspicious URLs too similar to common phishing targets, and blacklist them.
|
||||||
|
|
||||||
|
## 3.9.2 2017-7-26
|
||||||
|
|
||||||
|
- Fix bugs that could sometimes result in failed transactions after switching networks.
|
||||||
- Include stack traces in txMeta's to better understand the life cycle of transactions
|
- Include stack traces in txMeta's to better understand the life cycle of transactions
|
||||||
|
- Enhance blacklister functionality to include levenshtein logic. (credit to @sogoiii and @409H for their help!)
|
||||||
|
|
||||||
## 3.9.1 2017-7-19
|
## 3.9.1 2017-7-19
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "MetaMask",
|
"name": "MetaMask",
|
||||||
"short_name": "Metamask",
|
"short_name": "Metamask",
|
||||||
"version": "3.9.1",
|
"version": "3.9.2",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "https://metamask.io",
|
"author": "https://metamask.io",
|
||||||
"description": "Ethereum Browser Extension",
|
"description": "Ethereum Browser Extension",
|
||||||
@ -55,8 +55,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"matches": ["http://*/*", "https://*/*"],
|
"matches": [
|
||||||
"js": ["scripts/blacklister.js"]
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
],
|
||||||
|
"js": ["scripts/blacklister.js"],
|
||||||
|
"all_frames": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
@ -11,6 +11,7 @@ const NotificationManager = require('./lib/notification-manager.js')
|
|||||||
const MetamaskController = require('./metamask-controller')
|
const MetamaskController = require('./metamask-controller')
|
||||||
const extension = require('extensionizer')
|
const extension = require('extensionizer')
|
||||||
const firstTimeState = require('./first-time-state')
|
const firstTimeState = require('./first-time-state')
|
||||||
|
const isPhish = require('./lib/is-phish')
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config'
|
const STORAGE_KEY = 'metamask-config'
|
||||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||||
@ -90,6 +91,10 @@ function setupController (initState) {
|
|||||||
|
|
||||||
extension.runtime.onConnect.addListener(connectRemote)
|
extension.runtime.onConnect.addListener(connectRemote)
|
||||||
function connectRemote (remotePort) {
|
function connectRemote (remotePort) {
|
||||||
|
if (remotePort.name === 'blacklister') {
|
||||||
|
return checkBlacklist(remotePort)
|
||||||
|
}
|
||||||
|
|
||||||
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
|
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
|
||||||
var portStream = new PortStream(remotePort)
|
var portStream = new PortStream(remotePort)
|
||||||
if (isMetaMaskInternalProcess) {
|
if (isMetaMaskInternalProcess) {
|
||||||
@ -135,6 +140,27 @@ function setupController (initState) {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for new pages and return if blacklisted:
|
||||||
|
function checkBlacklist (port) {
|
||||||
|
const handler = handleNewPageLoad.bind(null, port)
|
||||||
|
port.onMessage.addListener(handler)
|
||||||
|
setTimeout(() => {
|
||||||
|
port.onMessage.removeListener(handler)
|
||||||
|
}, 30000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNewPageLoad (port, message) {
|
||||||
|
const { pageLoaded } = message
|
||||||
|
if (!pageLoaded || !global.metamaskController) return
|
||||||
|
|
||||||
|
const state = global.metamaskController.getState()
|
||||||
|
const updatedBlacklist = state.blacklist
|
||||||
|
|
||||||
|
if (isPhish({ updatedBlacklist, hostname: pageLoaded })) {
|
||||||
|
port.postMessage({ 'blacklist': pageLoaded })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Etc...
|
// Etc...
|
||||||
//
|
//
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
const blacklistedDomains = require('etheraddresslookup/blacklists/domains.json')
|
const extension = require('extensionizer')
|
||||||
|
|
||||||
function detectBlacklistedDomain() {
|
var port = extension.runtime.connect({name: 'blacklister'})
|
||||||
var strCurrentTab = window.location.hostname
|
port.postMessage({ 'pageLoaded': window.location.hostname })
|
||||||
if (blacklistedDomains && blacklistedDomains.includes(strCurrentTab)) {
|
port.onMessage.addListener(redirectIfBlacklisted)
|
||||||
|
|
||||||
|
function redirectIfBlacklisted (response) {
|
||||||
|
const { blacklist } = response
|
||||||
|
const host = window.location.hostname
|
||||||
|
if (blacklist && blacklist === host) {
|
||||||
window.location.href = 'https://metamask.io/phishing.html'
|
window.location.href = 'https://metamask.io/phishing.html'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', function() {
|
|
||||||
detectBlacklistedDomain()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const ObservableStore = require('obs-store')
|
const ObservableStore = require('obs-store')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
|
const recentBlacklist = require('etheraddresslookup/blacklists/domains.json')
|
||||||
|
|
||||||
// every ten minutes
|
// every ten minutes
|
||||||
const POLLING_INTERVAL = 300000
|
const POLLING_INTERVAL = 300000
|
||||||
@ -9,6 +10,7 @@ class InfuraController {
|
|||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
const initState = extend({
|
const initState = extend({
|
||||||
infuraNetworkStatus: {},
|
infuraNetworkStatus: {},
|
||||||
|
blacklist: recentBlacklist,
|
||||||
}, opts.initState)
|
}, opts.initState)
|
||||||
this.store = new ObservableStore(initState)
|
this.store = new ObservableStore(initState)
|
||||||
}
|
}
|
||||||
@ -30,12 +32,24 @@ class InfuraController {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateLocalBlacklist () {
|
||||||
|
return fetch('https://api.infura.io/v1/blacklist')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((parsedResponse) => {
|
||||||
|
this.store.updateState({
|
||||||
|
blacklist: parsedResponse,
|
||||||
|
})
|
||||||
|
return parsedResponse
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
scheduleInfuraNetworkCheck () {
|
scheduleInfuraNetworkCheck () {
|
||||||
if (this.conversionInterval) {
|
if (this.conversionInterval) {
|
||||||
clearInterval(this.conversionInterval)
|
clearInterval(this.conversionInterval)
|
||||||
}
|
}
|
||||||
this.conversionInterval = setInterval(() => {
|
this.conversionInterval = setInterval(() => {
|
||||||
this.checkInfuraNetworkStatus()
|
this.checkInfuraNetworkStatus()
|
||||||
|
this.updateLocalBlacklist()
|
||||||
}, POLLING_INTERVAL)
|
}, POLLING_INTERVAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ module.exports = class TransactionController extends EventEmitter {
|
|||||||
this.blockTracker = opts.blockTracker
|
this.blockTracker = opts.blockTracker
|
||||||
this.nonceTracker = new NonceTracker({
|
this.nonceTracker = new NonceTracker({
|
||||||
provider: this.provider,
|
provider: this.provider,
|
||||||
blockTracker: this.provider._blockTracker,
|
|
||||||
getPendingTransactions: (address) => {
|
getPendingTransactions: (address) => {
|
||||||
return this.getFilteredTxList({
|
return this.getFilteredTxList({
|
||||||
from: address,
|
from: address,
|
||||||
@ -104,8 +103,16 @@ module.exports = class TransactionController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTx (txMeta) {
|
updateTx (txMeta) {
|
||||||
|
// create txMeta snapshot for history
|
||||||
const txMetaForHistory = clone(txMeta)
|
const txMetaForHistory = clone(txMeta)
|
||||||
|
// dont include previous history in this snapshot
|
||||||
|
delete txMetaForHistory.history
|
||||||
|
// add stack to help understand why tx was updated
|
||||||
txMetaForHistory.stack = getStack()
|
txMetaForHistory.stack = getStack()
|
||||||
|
// add snapshot to tx history
|
||||||
|
if (!txMeta.history) txMeta.history = []
|
||||||
|
txMeta.history.push(txMetaForHistory)
|
||||||
|
|
||||||
const txId = txMeta.id
|
const txId = txMeta.id
|
||||||
const txList = this.getFullTxList()
|
const txList = this.getFullTxList()
|
||||||
const index = txList.findIndex(txData => txData.id === txId)
|
const index = txList.findIndex(txData => txData.id === txId)
|
||||||
@ -192,8 +199,12 @@ module.exports = class TransactionController extends EventEmitter {
|
|||||||
// get next nonce
|
// get next nonce
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
const fromAddress = txMeta.txParams.from
|
const fromAddress = txMeta.txParams.from
|
||||||
|
// wait for a nonce
|
||||||
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
|
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
|
||||||
|
// add nonce to txParams
|
||||||
txMeta.txParams.nonce = nonceLock.nextNonce
|
txMeta.txParams.nonce = nonceLock.nextNonce
|
||||||
|
// add nonce debugging information to txMeta
|
||||||
|
txMeta.nonceDetails = nonceLock.nonceDetails
|
||||||
this.updateTx(txMeta)
|
this.updateTx(txMeta)
|
||||||
// sign transaction
|
// sign transaction
|
||||||
const rawTx = await this.signTransaction(txId)
|
const rawTx = await this.signTransaction(txId)
|
||||||
|
@ -65,3 +65,4 @@ function restoreContextAfterImports () {
|
|||||||
console.warn('MetaMask - global.define could not be overwritten.')
|
console.warn('MetaMask - global.define could not be overwritten.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
38
app/scripts/lib/is-phish.js
Normal file
38
app/scripts/lib/is-phish.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const levenshtein = require('fast-levenshtein')
|
||||||
|
const blacklistedMetaMaskDomains = ['metamask.com']
|
||||||
|
let blacklistedDomains = require('etheraddresslookup/blacklists/domains.json').concat(blacklistedMetaMaskDomains)
|
||||||
|
const whitelistedMetaMaskDomains = ['metamask.io', 'www.metamask.io']
|
||||||
|
const whitelistedDomains = require('etheraddresslookup/whitelists/domains.json').concat(whitelistedMetaMaskDomains)
|
||||||
|
const LEVENSHTEIN_TOLERANCE = 4
|
||||||
|
const LEVENSHTEIN_CHECKS = ['myetherwallet', 'myetheroll', 'ledgerwallet', 'metamask']
|
||||||
|
|
||||||
|
|
||||||
|
// credit to @sogoiii and @409H for their help!
|
||||||
|
// Return a boolean on whether or not a phish is detected.
|
||||||
|
function isPhish({ hostname, updatedBlacklist = null }) {
|
||||||
|
var strCurrentTab = hostname
|
||||||
|
|
||||||
|
// check if the domain is part of the whitelist.
|
||||||
|
if (whitelistedDomains && whitelistedDomains.includes(strCurrentTab)) { return false }
|
||||||
|
|
||||||
|
// Allow updating of blacklist:
|
||||||
|
if (updatedBlacklist) {
|
||||||
|
blacklistedDomains = blacklistedDomains.concat(updatedBlacklist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the domain is part of the blacklist.
|
||||||
|
const isBlacklisted = blacklistedDomains && blacklistedDomains.includes(strCurrentTab)
|
||||||
|
|
||||||
|
// check for similar values.
|
||||||
|
let levenshteinMatched = false
|
||||||
|
var levenshteinForm = strCurrentTab.replace(/\./g, '')
|
||||||
|
LEVENSHTEIN_CHECKS.forEach((element) => {
|
||||||
|
if (levenshtein.get(element, levenshteinForm) <= LEVENSHTEIN_TOLERANCE) {
|
||||||
|
levenshteinMatched = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return isBlacklisted || levenshteinMatched
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = isPhish
|
@ -4,8 +4,8 @@ const Mutex = require('await-semaphore').Mutex
|
|||||||
|
|
||||||
class NonceTracker {
|
class NonceTracker {
|
||||||
|
|
||||||
constructor ({ blockTracker, provider, getPendingTransactions }) {
|
constructor ({ provider, getPendingTransactions }) {
|
||||||
this.blockTracker = blockTracker
|
this.provider = provider
|
||||||
this.ethQuery = new EthQuery(provider)
|
this.ethQuery = new EthQuery(provider)
|
||||||
this.getPendingTransactions = getPendingTransactions
|
this.getPendingTransactions = getPendingTransactions
|
||||||
this.lockMap = {}
|
this.lockMap = {}
|
||||||
@ -31,21 +31,25 @@ class NonceTracker {
|
|||||||
const currentBlock = await this._getCurrentBlock()
|
const currentBlock = await this._getCurrentBlock()
|
||||||
const pendingTransactions = this.getPendingTransactions(address)
|
const pendingTransactions = this.getPendingTransactions(address)
|
||||||
const pendingCount = pendingTransactions.length
|
const pendingCount = pendingTransactions.length
|
||||||
assert(Number.isInteger(pendingCount), 'nonce-tracker - pendingCount is an integer')
|
assert(Number.isInteger(pendingCount), `nonce-tracker - pendingCount is not an integer - got: (${typeof pendingCount}) "${pendingCount}"`)
|
||||||
const baseCountHex = await this._getTxCount(address, currentBlock)
|
const baseCountHex = await this._getTxCount(address, currentBlock)
|
||||||
const baseCount = parseInt(baseCountHex, 16)
|
const baseCount = parseInt(baseCountHex, 16)
|
||||||
assert(Number.isInteger(baseCount), 'nonce-tracker - baseCount is an integer')
|
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
|
||||||
const nextNonce = baseCount + pendingCount
|
const nextNonce = baseCount + pendingCount
|
||||||
assert(Number.isInteger(nextNonce), 'nonce-tracker - nextNonce is an integer')
|
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
|
||||||
// return next nonce and release cb
|
// collect the numbers used to calculate the nonce for debugging
|
||||||
return { nextNonce, releaseLock }
|
const blockNumber = currentBlock.number
|
||||||
|
const nonceDetails = { blockNumber, baseCount, baseCountHex, pendingCount }
|
||||||
|
// return nonce and release cb
|
||||||
|
return { nextNonce, nonceDetails, releaseLock }
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getCurrentBlock () {
|
async _getCurrentBlock () {
|
||||||
const currentBlock = this.blockTracker.getCurrentBlock()
|
const blockTracker = this._getBlockTracker()
|
||||||
|
const currentBlock = blockTracker.getCurrentBlock()
|
||||||
if (currentBlock) return currentBlock
|
if (currentBlock) return currentBlock
|
||||||
return await Promise((reject, resolve) => {
|
return await Promise((reject, resolve) => {
|
||||||
this.blockTracker.once('latest', resolve)
|
blockTracker.once('latest', resolve)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +83,12 @@ class NonceTracker {
|
|||||||
return mutex
|
return mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is a hotfix for the fact that the blockTracker will
|
||||||
|
// change when the network changes
|
||||||
|
_getBlockTracker () {
|
||||||
|
return this.provider._blockTracker
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = NonceTracker
|
module.exports = NonceTracker
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"extension-link-enabler": "^1.0.0",
|
"extension-link-enabler": "^1.0.0",
|
||||||
"extensionizer": "^1.0.0",
|
"extensionizer": "^1.0.0",
|
||||||
|
"fast-levenshtein": "^2.0.6",
|
||||||
"gulp-eslint": "^2.0.0",
|
"gulp-eslint": "^2.0.0",
|
||||||
"hat": "0.0.3",
|
"hat": "0.0.3",
|
||||||
"idb-global": "^1.0.0",
|
"idb-global": "^1.0.0",
|
||||||
|
24
test/unit/blacklister-test.js
Normal file
24
test/unit/blacklister-test.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const assert = require('assert')
|
||||||
|
const isPhish = require('../../app/scripts/lib/is-phish')
|
||||||
|
|
||||||
|
describe('blacklister', function () {
|
||||||
|
describe('#isPhish', function () {
|
||||||
|
it('should not flag whitelisted values', function () {
|
||||||
|
var result = isPhish({ hostname: 'www.metamask.io' })
|
||||||
|
assert(!result)
|
||||||
|
})
|
||||||
|
it('should flag explicit values', function () {
|
||||||
|
var result = isPhish({ hostname: 'metamask.com' })
|
||||||
|
assert(result)
|
||||||
|
})
|
||||||
|
it('should flag levenshtein values', function () {
|
||||||
|
var result = isPhish({ hostname: 'metmask.com' })
|
||||||
|
assert(result)
|
||||||
|
})
|
||||||
|
it('should not flag not-even-close values', function () {
|
||||||
|
var result = isPhish({ hostname: 'example.com' })
|
||||||
|
assert(!result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -18,11 +18,13 @@ describe('Nonce Tracker', function () {
|
|||||||
|
|
||||||
|
|
||||||
getPendingTransactions = () => pendingTxs
|
getPendingTransactions = () => pendingTxs
|
||||||
provider = { sendAsync: (_, cb) => { cb(undefined, {result: '0x0'}) } }
|
provider = {
|
||||||
nonceTracker = new NonceTracker({
|
sendAsync: (_, cb) => { cb(undefined, {result: '0x0'}) },
|
||||||
blockTracker: {
|
_blockTracker: {
|
||||||
getCurrentBlock: () => '0x11b568',
|
getCurrentBlock: () => '0x11b568',
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
nonceTracker = new NonceTracker({
|
||||||
provider,
|
provider,
|
||||||
getPendingTransactions,
|
getPendingTransactions,
|
||||||
})
|
})
|
||||||
|
@ -43,7 +43,6 @@ function rootReducer (state, action) {
|
|||||||
|
|
||||||
window.logState = function () {
|
window.logState = function () {
|
||||||
var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2)
|
var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2)
|
||||||
console.log(stateString)
|
|
||||||
return stateString
|
return stateString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user