diff --git a/.metamaskrc.dist b/.metamaskrc.dist index 0bd4d399549c..151c29808c86 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -13,3 +13,4 @@ INFURA_PROJECT_ID=00000000000 ; Set this to test changes to the phishing warning page. ;PHISHING_WARNING_PAGE_URL= BLOCKAID_FILE_CDN= +BLOCKAID_PUBLIC_KEY= diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 84847b645f9a..c2eeaa0f6138 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -2415,13 +2415,22 @@ export default class TransactionController extends EventEmitter { let uiCustomizations; - if (securityProviderResponse?.flagAsDangerous === 1) { - uiCustomizations = ['flagged_as_malicious']; - } else if (securityProviderResponse?.flagAsDangerous === 2) { - uiCustomizations = ['flagged_as_safety_unknown']; + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + if (securityAlertResponse?.result_type === BlockaidResultType.Failed) { + uiCustomizations = ['security_alert_failed']; } else { - uiCustomizations = null; + ///: END:ONLY_INCLUDE_IN + // eslint-disable-next-line no-lonely-if + if (securityProviderResponse?.flagAsDangerous === 1) { + uiCustomizations = ['flagged_as_malicious']; + } else if (securityProviderResponse?.flagAsDangerous === 2) { + uiCustomizations = ['flagged_as_safety_unknown']; + } else { + uiCustomizations = null; + } + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) } + ///: END:ONLY_INCLUDE_IN /** The transaction status property is not considered sensitive and is now included in the non-anonymous event */ let properties = { diff --git a/app/scripts/controllers/transactions/index.test.js b/app/scripts/controllers/transactions/index.test.js index fb759f1afc1c..ee1af91f45fc 100644 --- a/app/scripts/controllers/transactions/index.test.js +++ b/app/scripts/controllers/transactions/index.test.js @@ -2652,6 +2652,83 @@ describe('Transaction Controller', function () { ); }); + it('should call _trackMetaMetricsEvent with the correct payload when blockaid verification fails', async function () { + const txMeta = { + id: 1, + status: TransactionStatus.unapproved, + txParams: { + from: fromAccount.address, + to: '0x1678a085c290ebd122dc42cba69373b5953b831d', + gasPrice: '0x77359400', + gas: '0x7b0d', + nonce: '0x4b', + }, + type: TransactionType.simpleSend, + origin: 'other', + chainId: currentChainId, + time: 1624408066355, + metamaskNetworkId: currentNetworkId, + securityAlertResponse: { + result_type: BlockaidResultType.Failed, + reason: 'some error', + }, + }; + const expectedPayload = { + actionId, + initialEvent: 'Transaction Added', + successEvent: 'Transaction Approved', + failureEvent: 'Transaction Rejected', + uniqueIdentifier: 'transaction-added-1', + persist: true, + category: MetaMetricsEventCategory.Transactions, + properties: { + network: '5', + referrer: 'other', + source: MetaMetricsTransactionEventSource.Dapp, + status: 'unapproved', + transaction_type: TransactionType.simpleSend, + chain_id: '0x5', + eip_1559_version: '0', + gas_edit_attempted: 'none', + gas_edit_type: 'none', + account_type: 'MetaMask', + asset_type: AssetType.native, + token_standard: TokenStandard.none, + device_model: 'N/A', + transaction_speed_up: false, + ui_customizations: ['security_alert_failed'], + security_alert_reason: 'some error', + security_alert_response: BlockaidResultType.Failed, + }, + sensitiveProperties: { + baz: 3.0, + foo: 'bar', + gas_price: '2', + gas_limit: '0x7b0d', + transaction_contract_method: undefined, + transaction_replaced: undefined, + first_seen: 1624408066355, + transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY, + }, + }; + + await txController._trackTransactionMetricsEvent( + txMeta, + TransactionMetaMetricsEvent.added, + actionId, + { + baz: 3.0, + foo: 'bar', + }, + ); + assert.equal(createEventFragmentSpy.callCount, 1); + assert.equal(finalizeEventFragmentSpy.callCount, 0); + assert.deepEqual( + createEventFragmentSpy.getCall(0).args[0], + expectedPayload, + ); + }); + it('should call _trackMetaMetricsEvent with the correct payload (extra params) when flagAsDangerous is malicious', async function () { const txMeta = { id: 1, diff --git a/app/scripts/lib/ppom/ppom-middleware.test.ts b/app/scripts/lib/ppom/ppom-middleware.test.ts index be7b5558d1ca..8276f8361df2 100644 --- a/app/scripts/lib/ppom/ppom-middleware.test.ts +++ b/app/scripts/lib/ppom/ppom-middleware.test.ts @@ -1,3 +1,7 @@ +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../shared/constants/security-provider'; import { createPPOMMiddleware } from './ppom-middleware'; Object.defineProperty(globalThis, 'fetch', { @@ -13,10 +17,16 @@ Object.defineProperty(globalThis, 'performance', { describe('PPOMMiddleware', () => { it('should call ppomController.usePPOM for requests of type confirmation', async () => { const useMock = jest.fn(); - const controller = { + const ppomController = { usePPOM: useMock, }; - const middlewareFunction = createPPOMMiddleware(controller as any); + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: true }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); await middlewareFunction( { method: 'eth_sendTransaction' }, undefined, @@ -26,25 +36,85 @@ describe('PPOMMiddleware', () => { }); it('should add validation response on confirmation requests', async () => { - const controller = { + const ppomController = { + usePPOM: async () => Promise.resolve('VALIDATION_RESULT'), + }; + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: true }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); + const req = { + method: 'eth_sendTransaction', + securityAlertResponse: undefined, + }; + await middlewareFunction(req, undefined, () => undefined); + expect(req.securityAlertResponse).toBeDefined(); + }); + + it('should not do validation if user has not enabled preference', async () => { + const ppomController = { usePPOM: async () => Promise.resolve('VALIDATION_RESULT'), }; - const middlewareFunction = createPPOMMiddleware(controller as any); - const req = { method: 'eth_sendTransaction', ppomResponse: undefined }; + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: false }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); + const req = { + method: 'eth_sendTransaction', + securityAlertResponse: undefined, + }; await middlewareFunction(req, undefined, () => undefined); - expect(req.ppomResponse).toBeDefined(); + expect(req.securityAlertResponse).toBeUndefined(); + }); + + it('should set Failed type in response if usePPOM throw error', async () => { + const ppomController = { + usePPOM: async () => { + throw new Error('some error'); + }, + }; + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: true }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); + const req = { + method: 'eth_sendTransaction', + securityAlertResponse: undefined, + }; + await middlewareFunction(req, undefined, () => undefined); + expect((req.securityAlertResponse as any)?.result_type).toBe( + BlockaidResultType.Failed, + ); + expect((req.securityAlertResponse as any)?.reason).toBe( + BlockaidReason.failed, + ); }); it('should call next method when ppomController.usePPOM completes', async () => { const ppom = { validateJsonRpc: () => undefined, }; - const controller = { + const ppomController = { usePPOM: async (callback: any) => { callback(ppom); }, }; - const middlewareFunction = createPPOMMiddleware(controller as any); + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: true }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); const nextMock = jest.fn(); await middlewareFunction( { method: 'eth_sendTransaction' }, @@ -55,12 +125,18 @@ describe('PPOMMiddleware', () => { }); it('should call next method when ppomController.usePPOM throws error', async () => { - const controller = { + const ppomController = { usePPOM: async (_callback: any) => { throw Error('Some error'); }, }; - const middlewareFunction = createPPOMMiddleware(controller as any); + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: true }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); const nextMock = jest.fn(); await middlewareFunction( { method: 'eth_sendTransaction' }, @@ -75,12 +151,18 @@ describe('PPOMMiddleware', () => { const ppom = { validateJsonRpc: validateMock, }; - const controller = { + const ppomController = { usePPOM: async (callback: any) => { callback(ppom); }, }; - const middlewareFunction = createPPOMMiddleware(controller as any); + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: true }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); await middlewareFunction( { method: 'eth_sendTransaction' }, undefined, @@ -94,12 +176,18 @@ describe('PPOMMiddleware', () => { const ppom = { validateJsonRpc: validateMock, }; - const controller = { + const ppomController = { usePPOM: async (callback: any) => { callback(ppom); }, }; - const middlewareFunction = createPPOMMiddleware(controller as any); + const preferenceController = { + store: { getState: () => ({ securityAlertsEnabled: true }) }, + }; + const middlewareFunction = createPPOMMiddleware( + ppomController as any, + preferenceController as any, + ); await middlewareFunction( { method: 'eth_someRequest' }, undefined, diff --git a/app/scripts/lib/ppom/ppom-middleware.ts b/app/scripts/lib/ppom/ppom-middleware.ts index c58f74d60c4e..d283043672d2 100644 --- a/app/scripts/lib/ppom/ppom-middleware.ts +++ b/app/scripts/lib/ppom/ppom-middleware.ts @@ -1,7 +1,14 @@ import { PPOM } from '@blockaid/ppom'; - import { PPOMController } from '@metamask/ppom-validator'; +import { + BlockaidReason, + BlockaidResultType, +} from '../../../../shared/constants/security-provider'; +import PreferencesController from 'app/scripts/controllers/preferences'; + +const { sentry } = global as any; + const ConfirmationMethods = Object.freeze([ 'eth_sendRawTransaction', 'eth_sendTransaction', @@ -23,19 +30,33 @@ const ConfirmationMethods = Object.freeze([ * the request will be forwarded to the next middleware, together with the PPOM response. * * @param ppomController - Instance of PPOMController. + * @param preferencesController - Instance of PreferenceController. * @returns PPOMMiddleware function. */ -export function createPPOMMiddleware(ppomController: PPOMController) { +export function createPPOMMiddleware( + ppomController: PPOMController, + preferencesController: PreferencesController, +) { return async (req: any, _res: any, next: () => void) => { try { - if (ConfirmationMethods.includes(req.method)) { + const securityAlertsEnabled = + preferencesController.store.getState()?.securityAlertsEnabled; + if (securityAlertsEnabled && ConfirmationMethods.includes(req.method)) { // eslint-disable-next-line require-atomic-updates - req.ppomResponse = await ppomController.usePPOM(async (ppom: PPOM) => { - return ppom.validateJsonRpc(req); - }); + req.securityAlertResponse = await ppomController.usePPOM( + async (ppom: PPOM) => { + return ppom.validateJsonRpc(req); + }, + ); } - } catch (error: unknown) { + } catch (error: any) { + sentry?.captureException(error); console.error('Error validating JSON RPC using PPOM: ', error); + req.securityAlertResponse = { + result_type: BlockaidResultType.Failed, + reason: BlockaidReason.failed, + description: 'Validating the confirmation failed by throwing error.', + }; } finally { next(); } diff --git a/app/scripts/lib/ppom/ppom.js b/app/scripts/lib/ppom/ppom.js index 39915b3a4b6e..0d1960502446 100644 --- a/app/scripts/lib/ppom/ppom.js +++ b/app/scripts/lib/ppom/ppom.js @@ -1,4 +1,5 @@ /* eslint-disable */ +// The contents of this file were provided by the Blockaid team and pasted verbatim let wasm; @@ -6,568 +7,539 @@ const heap = new Array(128).fill(undefined); heap.push(undefined, null, true, false); -function getObject(idx) { - return heap[idx]; -} +function getObject(idx) { return heap[idx]; } let heap_next = heap.length; function dropObject(idx) { - if (idx < 132) return; - heap[idx] = heap_next; - heap_next = idx; + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; } function takeObject(idx) { - const ret = getObject(idx); - dropObject(idx); - return ret; + const ret = getObject(idx); + dropObject(idx); + return ret; } -let WASM_VECTOR_LEN = 0; +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; let cachedUint8Memory0 = null; function getUint8Memory0() { - if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { - cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); - } - return cachedUint8Memory0; + if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; } -const cachedTextEncoder = - typeof TextEncoder !== 'undefined' - ? new TextEncoder('utf-8') - : { - encode: () => { - throw Error('TextEncoder not available'); - }, - }; - -const encodeString = - typeof cachedTextEncoder.encodeInto === 'function' +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' ? function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); - } + return cachedTextEncoder.encodeInto(arg, view); +} : function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length, - }; - }; + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); function passStringToWasm0(arg, malloc, realloc) { - if (realloc === undefined) { - const buf = cachedTextEncoder.encode(arg); - const ptr = malloc(buf.length, 1) >>> 0; - getUint8Memory0() - .subarray(ptr, ptr + buf.length) - .set(buf); - WASM_VECTOR_LEN = buf.length; - return ptr; - } - let len = arg.length; - let ptr = malloc(len, 1) >>> 0; + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } - const mem = getUint8Memory0(); + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; - let offset = 0; + const mem = getUint8Memory0(); - for (; offset < len; offset++) { - const code = arg.charCodeAt(offset); - if (code > 0x7f) break; - mem[ptr + offset] = code; - } + let offset = 0; - if (offset !== len) { - if (offset !== 0) { - arg = arg.slice(offset); + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; } - ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; - const view = getUint8Memory0().subarray(ptr + offset, ptr + len); - const ret = encodeString(arg, view); - offset += ret.written; - } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } - WASM_VECTOR_LEN = offset; - return ptr; + WASM_VECTOR_LEN = offset; + return ptr; } function isLikeNone(x) { - return x === undefined || x === null; + return x === undefined || x === null; } let cachedInt32Memory0 = null; function getInt32Memory0() { - if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { - cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); - } - return cachedInt32Memory0; -} - -const cachedTextDecoder = - typeof TextDecoder !== 'undefined' - ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) - : { - decode: () => { - throw Error('TextDecoder not available'); - }, - }; - -if (typeof TextDecoder !== 'undefined') { - cachedTextDecoder.decode(); -} - -function getStringFromWasm0(ptr, len) { - ptr = ptr >>> 0; - return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); -} - -function addHeapObject(obj) { - if (heap_next === heap.length) heap.push(heap.length + 1); - const idx = heap_next; - heap_next = heap[idx]; - - heap[idx] = obj; - return idx; + if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; } function debugString(val) { - // primitive types - const type = typeof val; - if (type == 'number' || type == 'boolean' || val == null) { - return `${val}`; - } - if (type == 'string') { - return `"${val}"`; - } - if (type == 'symbol') { - const description = val.description; - if (description == null) { - return 'Symbol'; - } else { - return `Symbol(${description})`; + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; } - } - if (type == 'function') { - const name = val.name; - if (typeof name == 'string' && name.length > 0) { - return `Function(${name})`; - } else { - return 'Function'; + if (type == 'string') { + return `"${val}"`; } - } - // objects - if (Array.isArray(val)) { - const length = val.length; - let debug = '['; - if (length > 0) { - debug += debugString(val[0]); + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } } - for (let i = 1; i < length; i++) { - debug += ', ' + debugString(val[i]); + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } } - debug += ']'; - return debug; - } - // Test for built-in - const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); - let className; - if (builtInMatches.length > 1) { - className = builtInMatches[1]; - } else { - // Failed to match the standard '[object ClassName]' - return toString.call(val); - } - if (className == 'Object') { - // we're a user defined class or Object - // JSON.stringify avoids problems with cycles, and is generally much - // easier than looping through ownProperties of `val`. - try { - return 'Object(' + JSON.stringify(val) + ')'; - } catch (_) { - return 'Object'; + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; } - } - // errors - if (val instanceof Error) { - return `${val.name}: ${val.message}\n${val.stack}`; - } - // TODO we could test for more things here, like `Set`s and `Map`s. - return className; + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; } function makeMutClosure(arg0, arg1, dtor, f) { - const state = { a: arg0, b: arg1, cnt: 1 }; - const real = (...args) => { - // First up with a closure we increment the internal reference - // count. This ensures that the Rust closure environment won't - // be deallocated while we're invoking it. - state.cnt++; - const a = state.a; - state.a = 0; - try { - return f(a, state.b, ...args); - } finally { - if (--state.cnt === 0) { - dtor(a, state.b); - } else { - state.a = a; - } - } - }; - real.original = state; + const state = { a: arg0, b: arg1, cnt: 1 }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + dtor(a, state.b); - return real; + } else { + state.a = a; + } + } + }; + real.original = state; + + return real; +} +function __wbg_adapter_20(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h71774d49975c327c(arg0, arg1); } -function __wbg_adapter_20(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke( - arg0, - arg1, - addHeapObject(arg2), - ); + +function __wbg_adapter_23(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h3f3246d02d2f05cb(arg0, arg1, addHeapObject(arg2)); } -function __wbg_adapter_21(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__destroy( - arg0, - arg1, - ); +function __wbg_adapter_24(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__destroy__h2b1bef6683dbac4f(arg0, arg1); } /** - */ +*/ export function main() { - wasm.main(); + wasm.main(); } let cachedUint32Memory0 = null; function getUint32Memory0() { - if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) { - cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); - } - return cachedUint32Memory0; + if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) { + cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32Memory0; } function passArrayJsValueToWasm0(array, malloc) { - const ptr = malloc(array.length * 4, 4) >>> 0; - const mem = getUint32Memory0(); - for (let i = 0; i < array.length; i++) { - mem[ptr / 4 + i] = addHeapObject(array[i]); - } - WASM_VECTOR_LEN = array.length; - return ptr; + const ptr = malloc(array.length * 4, 4) >>> 0; + const mem = getUint32Memory0(); + for (let i = 0; i < array.length; i++) { + mem[ptr / 4 + i] = addHeapObject(array[i]); + } + WASM_VECTOR_LEN = array.length; + return ptr; } function handleError(f, args) { - try { - return f.apply(this, args); - } catch (e) { - wasm.__wbindgen_exn_store(addHeapObject(e)); - } + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } } -function __wbg_adapter_39(arg0, arg1, arg2, arg3) { - wasm.wasm_bindgen__convert__closures__invoke2_mut( - arg0, - arg1, - addHeapObject(arg2), - addHeapObject(arg3), - ); +function __wbg_adapter_52(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures__invoke2_mut__h724d112298dfe4d5(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3)); } /** - * JavaScript wrapper for [`PPOM`] - */ +* JavaScript wrapper for [`PPOM`] +*/ export class PPOM { - static __wrap(ptr) { - ptr = ptr >>> 0; - const obj = Object.create(PPOM.prototype); - obj.__wbg_ptr = ptr; - return obj; - } + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(PPOM.prototype); + obj.__wbg_ptr = ptr; - __destroy_into_raw() { - const ptr = this.__wbg_ptr; - this.__wbg_ptr = 0; + return obj; + } - return ptr; - } - - free() { - const ptr = this.__destroy_into_raw(); - wasm.__wbg_ppom_free(ptr); - } - /** - * @param {Function} json_rpc_callback - * @param {any[]} files - * @returns {Promise} - */ - static new(json_rpc_callback, files) { - const ptr0 = passArrayJsValueToWasm0(files, wasm.__wbindgen_malloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.ppom_new(addHeapObject(json_rpc_callback), ptr0, len0); - return takeObject(ret); - } - /** - * @param {any} request - * @returns {Promise} - */ - validateJsonRpc(request) { - const ret = wasm.ppom_validateJsonRpc( - this.__wbg_ptr, - addHeapObject(request), - ); - return takeObject(ret); - } + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_ppom_free(ptr); + } + /** + * @param {Function} json_rpc_callback + * @param {any[]} files + * @returns {Promise} + */ + static new(json_rpc_callback, files) { + const ptr0 = passArrayJsValueToWasm0(files, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.ppom_new(addHeapObject(json_rpc_callback), ptr0, len0); + return takeObject(ret); + } + /** + * @param {any} request + * @returns {Promise} + */ + validateJsonRpc(request) { + const ret = wasm.ppom_validateJsonRpc(this.__wbg_ptr, addHeapObject(request)); + return takeObject(ret); + } } async function __wbg_load(module, imports) { - if (typeof Response === 'function' && module instanceof Response) { - if (typeof WebAssembly.instantiateStreaming === 'function') { - try { - return await WebAssembly.instantiateStreaming(module, imports); - } catch (e) { - if (module.headers.get('Content-Type') != 'application/wasm') { - console.warn( - '`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n', - e, - ); - } else { - throw e; + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } } - } - } - const bytes = await module.arrayBuffer(); - return await WebAssembly.instantiate(bytes, imports); - } else { - const instance = await WebAssembly.instantiate(module, imports); + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); - if (instance instanceof WebAssembly.Instance) { - return { instance, module }; } else { - return instance; + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } } - } } function __wbg_get_imports() { - const imports = {}; - imports.wbg = {}; - imports.wbg.__wbg_buffer_085ec1f694018c4f = function (arg0) { - const ret = getObject(arg0).buffer; - return addHeapObject(ret); - }; - imports.wbg.__wbg_call_01734de55d61e11d = function () { - return handleError(function (arg0, arg1, arg2) { - const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); - return addHeapObject(ret); - }, arguments); - }; - imports.wbg.__wbg_call_4c92f6aec1e1d6e6 = function () { - return handleError(function (arg0, arg1, arg2, arg3) { - const ret = getObject(arg0).call( - getObject(arg1), - getObject(arg2), - getObject(arg3), - ); - return addHeapObject(ret); - }, arguments); - }; - imports.wbg.__wbg_from_d7c216d4616bb368 = function (arg0) { - const ret = Array.from(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_get_44be0491f933a435 = function (arg0, arg1) { - const ret = getObject(arg0)[arg1 >>> 0]; - return addHeapObject(ret); - }; - imports.wbg.__wbg_length_72e2208bbc0efc61 = function (arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_length_d813e535247d427e = function (arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_length_fff51ee6522a1a18 = function (arg0) { - const ret = getObject(arg0).length; - return ret; - }; - imports.wbg.__wbg_new_43f1b47c28813cbd = function (arg0, arg1) { - try { - var state0 = { a: arg0, b: arg1 }; - var cb0 = (arg0, arg1) => { - const a = state0.a; - state0.a = 0; + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_buffer_085ec1f694018c4f = function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_call_01734de55d61e11d = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_call_4c92f6aec1e1d6e6 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2), getObject(arg3)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_clearTimeout_76877dbc010e786d = function(arg0) { + const ret = clearTimeout(takeObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_from_d7c216d4616bb368 = function(arg0) { + const ret = Array.from(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getTime_5e2054f832d82ec9 = function(arg0) { + const ret = getObject(arg0).getTime(); + return ret; + }; + imports.wbg.__wbg_getTimezoneOffset_8aee3445f323973e = function(arg0) { + const ret = getObject(arg0).getTimezoneOffset(); + return ret; + }; + imports.wbg.__wbg_get_44be0491f933a435 = function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }; + imports.wbg.__wbg_length_72e2208bbc0efc61 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_d813e535247d427e = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_length_fff51ee6522a1a18 = function(arg0) { + const ret = getObject(arg0).length; + return ret; + }; + imports.wbg.__wbg_new0_c0be7df4b6bd481f = function() { + const ret = new Date(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_43f1b47c28813cbd = function(arg0, arg1) { try { - return __wbg_adapter_39(a, state0.b, arg0, arg1); + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_52(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return addHeapObject(ret); } finally { - state0.a = a; + state0.a = state0.b = 0; } - }; - const ret = new Promise(cb0); - return addHeapObject(ret); - } finally { - state0.a = state0.b = 0; - } - }; - imports.wbg.__wbg_new_8125e318e6245eed = function (arg0) { - const ret = new Uint8Array(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_parse_670c19d4e984792e = function () { - return handleError(function (arg0, arg1) { - const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }, arguments); - }; - imports.wbg.__wbg_ppom_new = function (arg0) { - const ret = PPOM.__wrap(arg0); - return addHeapObject(ret); - }; - imports.wbg.__wbg_resolve_53698b95aaf7fcf8 = function (arg0) { - const ret = Promise.resolve(getObject(arg0)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_set_5cf90238115182c3 = function (arg0, arg1, arg2) { - getObject(arg0).set(getObject(arg1), arg2 >>> 0); - }; - imports.wbg.__wbg_stringify_e25465938f3f611f = function () { - return handleError(function (arg0) { - const ret = JSON.stringify(getObject(arg0)); - return addHeapObject(ret); - }, arguments); - }; - imports.wbg.__wbg_then_b2267541e2a73865 = function (arg0, arg1, arg2) { - const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); - return addHeapObject(ret); - }; - imports.wbg.__wbg_then_f7e06ee3c11698eb = function (arg0, arg1) { - const ret = getObject(arg0).then(getObject(arg1)); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_cb_drop = function (arg0) { - const obj = takeObject(arg0).original; - if (obj.cnt-- == 1) { - obj.a = 0; - return true; - } - const ret = false; - return ret; - }; - imports.wbg.__wbindgen_closure_wrapper_wasm_bindgen__closure__Closure_T___wrap__breaks_if_inlined = - function (arg0, arg1, arg2) { - const ret = makeMutClosure( - arg0, - arg1, - __wbg_adapter_21, - __wbg_adapter_20, - ); - return addHeapObject(ret); }; - imports.wbg.__wbindgen_debug_string = function (arg0, arg1) { - const ret = debugString(getObject(arg1)); - const ptr1 = passStringToWasm0( - ret, - wasm.__wbindgen_malloc, - wasm.__wbindgen_realloc, - ); - const len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }; - imports.wbg.__wbindgen_error_new = function (arg0, arg1) { - const ret = new Error(getStringFromWasm0(arg0, arg1)); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_is_undefined = function (arg0) { - const ret = getObject(arg0) === undefined; - return ret; - }; - imports.wbg.__wbindgen_memory = function () { - const ret = wasm.memory; - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_object_drop_ref = function (arg0) { - takeObject(arg0); - }; - imports.wbg.__wbindgen_string_get = function (arg0, arg1) { - const obj = getObject(arg1); - const ret = typeof obj === 'string' ? obj : undefined; - var ptr1 = isLikeNone(ret) - ? 0 - : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - var len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }; - imports.wbg.__wbindgen_string_new = function (arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_throw = function (arg0, arg1) { - throw new Error(getStringFromWasm0(arg0, arg1)); - }; - - return imports; + imports.wbg.__wbg_new_8125e318e6245eed = function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_parse_670c19d4e984792e = function() { return handleError(function (arg0, arg1) { + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_ppom_new = function(arg0) { + const ret = PPOM.__wrap(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_resolve_53698b95aaf7fcf8 = function(arg0) { + const ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_setTimeout_75cb9b6991a4031d = function() { return handleError(function (arg0, arg1) { + const ret = setTimeout(getObject(arg0), arg1); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_set_5cf90238115182c3 = function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }; + imports.wbg.__wbg_stringify_e25465938f3f611f = function() { return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)); + return addHeapObject(ret); + }, arguments) }; + imports.wbg.__wbg_then_b2267541e2a73865 = function(arg0, arg1, arg2) { + const ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_f7e06ee3c11698eb = function(arg0, arg1) { + const ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = takeObject(arg0).original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper_wasm_bindgen__closure__Closure_T___wrap__breaks_if_inlined__h1d7bf0f00ff7214d = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, __wbg_adapter_20, __wbg_adapter_20); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper_wasm_bindgen__closure__Closure_T___wrap__breaks_if_inlined__hc2986dfcd9d6621f = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, __wbg_adapter_24, __wbg_adapter_23); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbindgen_error_new = function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_drop_ref = function(arg0) { + takeObject(arg0); + }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + + return imports; } -function __wbg_init_memory(imports, maybe_memory) {} +function __wbg_init_memory(imports, maybe_memory) { + +} function __wbg_finalize_init(instance, module) { - wasm = instance.exports; - __wbg_init.__wbindgen_wasm_module = module; - cachedInt32Memory0 = null; - cachedUint32Memory0 = null; - cachedUint8Memory0 = null; - - wasm.__wbindgen_start(); - return wasm; + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedInt32Memory0 = null; + cachedUint32Memory0 = null; + cachedUint8Memory0 = null; + + wasm.__wbindgen_start(); + return wasm; } function initSync(module) { - if (wasm !== undefined) return wasm; + if (wasm !== undefined) return wasm; - const imports = __wbg_get_imports(); + const imports = __wbg_get_imports(); - __wbg_init_memory(imports); + __wbg_init_memory(imports); - if (!(module instanceof WebAssembly.Module)) { - module = new WebAssembly.Module(module); - } + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } - const instance = new WebAssembly.Instance(module, imports); + const instance = new WebAssembly.Instance(module, imports); - return __wbg_finalize_init(instance, module); + return __wbg_finalize_init(instance, module); } async function __wbg_init(input) { - if (wasm !== undefined) return wasm; + if (wasm !== undefined) return wasm; + - const imports = __wbg_get_imports(); + const imports = __wbg_get_imports(); - if ( - typeof input === 'string' || - (typeof Request === 'function' && input instanceof Request) || - (typeof URL === 'function' && input instanceof URL) - ) { - input = fetch(input); - } + if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { + input = fetch(input); + } - __wbg_init_memory(imports); + __wbg_init_memory(imports); - const { instance, module } = await __wbg_load(await input, imports); + const { instance, module } = await __wbg_load(await input, imports); - return __wbg_finalize_init(instance, module); + return __wbg_finalize_init(instance, module); } -export { initSync }; +export { initSync } export default __wbg_init; diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f0e87fd6c2ea..2487aab05908 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -656,6 +656,7 @@ export default class MetamaskController extends EventEmitter { this.preferencesController.store, ), cdnBaseUrl: process.env.BLOCKAID_FILE_CDN, + blockaidPublicKey: process.env.BLOCKAID_PUBLIC_KEY, }); ///: END:ONLY_INCLUDE_IN @@ -4054,7 +4055,9 @@ export default class MetamaskController extends EventEmitter { engine.push(this.permissionLogController.createMiddleware()); ///: BEGIN:ONLY_INCLUDE_IN(blockaid) - engine.push(createPPOMMiddleware(this.ppomController)); + engine.push( + createPPOMMiddleware(this.ppomController, this.preferencesController), + ); ///: END:ONLY_INCLUDE_IN engine.push( diff --git a/builds.yml b/builds.yml index d11fcf08b7d1..178abc9fd0ec 100644 --- a/builds.yml +++ b/builds.yml @@ -47,7 +47,7 @@ buildTypes: - desktop - build-flask - keyring-snaps - # - blockaid + # - blockaid env: - INFURA_FLASK_PROJECT_ID - SEGMENT_FLASK_WRITE_KEY @@ -120,6 +120,7 @@ features: blockaid: env: - BLOCKAID_FILE_CDN: null + - BLOCKAID_PUBLIC_KEY: null ### # Build Type code extensions. Things like different support links, warning pages, banners @@ -230,6 +231,8 @@ env: - EDITOR_URL: '' # CDN for blockaid files - BLOCKAID_FILE_CDN + # Blockaid public key for verifying signatures of data files downloaded from CDN + - BLOCKAID_PUBLIC_KEY ### # Meta variables diff --git a/shared/constants/security-provider.ts b/shared/constants/security-provider.ts index 5dba77daede3..8f916832114b 100644 --- a/shared/constants/security-provider.ts +++ b/shared/constants/security-provider.ts @@ -50,6 +50,7 @@ export enum BlockaidReason { // Locally defined notApplicable = 'NotApplicable', + failed = 'Failed', } export enum BlockaidResultType { @@ -57,6 +58,7 @@ export enum BlockaidResultType { Warning = 'Warning', Benign = 'Benign', // Locally defined + Failed = 'Failed', NotApplicable = 'NotApplicable', } diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js index b100cdb6bb62..a1656c5c1212 100644 --- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js +++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js @@ -13,6 +13,9 @@ import Typography from '../../../ui/typography'; import { TypographyVariant } from '../../../../helpers/constants/design-system'; import { isSuspiciousResponse } from '../../../../../shared/modules/security-provider.utils'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import BlockaidBannerAlert from '../../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; +///: END:ONLY_INCLUDE_IN import SecurityProviderBannerMessage from '../../security-provider-banner-message/security-provider-banner-message'; import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.'; @@ -222,6 +225,13 @@ export default class ConfirmPageContainerContent extends Component { {ethGasPriceWarning && ( )} + { + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + + ///: END:ONLY_INCLUDE_IN + } {isSuspiciousResponse(txData?.securityProviderResponse) && ( { ); expect(getByRole('button', { name: 'Buy' })).toBeInTheDocument(); }); + + it('should display security alert if present', () => { + const { getByText } = renderWithProvider( + , + store, + ); + expect(getByText('This is a deceptive request')).toBeInTheDocument(); + }); }); diff --git a/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap index db26f9e91ce9..7cf86eeb7a10 100644 --- a/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap +++ b/ui/components/app/security-provider-banner-alert/__snapshots__/security-provider-banner-alert.test.js.snap @@ -3,7 +3,7 @@ exports[`Security Provider Banner Alert should match snapshot 1`] = `
`; -exports[`Blockaid Banner Alert should render 'warning' UI when ppomResponse.resultType is 'Warning 1`] = ` +exports[`Blockaid Banner Alert should render 'warning' UI when securityAlertResponse.result_type is 'Warning 1`] = `
( - + ); DefaultStory.storyName = 'Default'; diff --git a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js index 7e71886455e9..7710be5313a0 100644 --- a/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js +++ b/ui/components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert.test.js @@ -7,8 +7,8 @@ import { } from '../../../../../shared/constants/security-provider'; import BlockaidBannerAlert from '.'; -const mockPpomResponse = { - resultType: BlockaidResultType.Warning, +const mockSecurityAlertResponse = { + result_type: BlockaidResultType.Warning, reason: BlockaidReason.setApprovalForAll, description: 'A SetApprovalForAll request was made on {contract}. We found the operator {operator} to be malicious', @@ -19,12 +19,33 @@ const mockPpomResponse = { }; describe('Blockaid Banner Alert', () => { - it(`should not render when ppomResponse.resultType is '${BlockaidResultType.Benign}'`, () => { + it('should not render when securityAlertResponse is not present', () => { + const { container } = renderWithLocalization( + , + ); + + expect(container.querySelector('.mm-banner-alert')).toBeNull(); + }); + + it(`should not render when securityAlertResponse.result_type is '${BlockaidResultType.Benign}'`, () => { + const { container } = renderWithLocalization( + , + ); + + expect(container.querySelector('.mm-banner-alert')).toBeNull(); + }); + + it(`should not render when securityAlertResponse.result_type is '${BlockaidResultType.Failed}'`, () => { const { container } = renderWithLocalization( , ); @@ -32,12 +53,12 @@ describe('Blockaid Banner Alert', () => { expect(container.querySelector('.mm-banner-alert')).toBeNull(); }); - it(`should render '${Severity.Danger}' UI when ppomResponse.resultType is '${BlockaidResultType.Malicious}`, () => { + it(`should render '${Severity.Danger}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Malicious}`, () => { const { container } = renderWithLocalization( , ); @@ -49,9 +70,9 @@ describe('Blockaid Banner Alert', () => { expect(dangerBannerAlert).toMatchSnapshot(); }); - it(`should render '${Severity.Warning}' UI when ppomResponse.resultType is '${BlockaidResultType.Warning}`, () => { + it(`should render '${Severity.Warning}' UI when securityAlertResponse.result_type is '${BlockaidResultType.Warning}`, () => { const { container } = renderWithLocalization( - , + , ); const warningBannerAlert = container.querySelector( '.mm-banner-alert--severity-warning', @@ -63,7 +84,7 @@ describe('Blockaid Banner Alert', () => { it('should render title, "This is a deceptive request"', () => { const { getByText } = renderWithLocalization( - , + , ); expect(getByText('This is a deceptive request')).toBeInTheDocument(); @@ -72,8 +93,8 @@ describe('Blockaid Banner Alert', () => { it('should render title, "This is a suspicious request", when the reason is "raw_signature_farming"', () => { const { getByText } = renderWithLocalization( , @@ -90,7 +111,10 @@ describe('Blockaid Banner Alert', () => { const { container, getByText } = renderWithLocalization( , ); @@ -133,7 +157,7 @@ describe('Blockaid Banner Alert', () => { it(`should render for '${reason}' correctly`, () => { const { getByText } = renderWithLocalization( , ); diff --git a/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js index f6046d4e9ed1..e3b777b8b9f5 100644 --- a/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js +++ b/ui/components/app/security-provider-banner-alert/security-provider-banner-alert.js @@ -36,13 +36,7 @@ function SecurityProviderBannerAlert({ const t = useContext(I18nContext); return ( - + {description} {details && ( diff --git a/ui/components/app/signature-request-original/signature-request-original.component.js b/ui/components/app/signature-request-original/signature-request-original.component.js index fbd13dba9708..6ef2850d4138 100644 --- a/ui/components/app/signature-request-original/signature-request-original.component.js +++ b/ui/components/app/signature-request-original/signature-request-original.component.js @@ -42,6 +42,9 @@ import { Text, ///: END:ONLY_INCLUDE_IN } from '../../component-library'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; +///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) import Box from '../../ui/box/box'; ///: END:ONLY_INCLUDE_IN @@ -150,12 +153,18 @@ export default class SignatureRequestOriginal extends Component { return (
+ { + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + + ///: END:ONLY_INCLUDE_IN + } {isSuspiciousResponse(txData?.securityProviderResponse) && ( )} - { ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.props.selectedAccount.address === @@ -183,7 +192,6 @@ export default class SignatureRequestOriginal extends Component { ) ///: END:ONLY_INCLUDE_IN } -
{ // Use legacy authorship header for snaps @@ -211,7 +219,6 @@ export default class SignatureRequestOriginal extends Component { ///: END:ONLY_INCLUDE_IN }
- {this.context.t('signatureRequestGuidance')} -
{notice}
{rows.map(({ name, value }, index) => { diff --git a/ui/components/app/signature-request-original/signature-request-original.test.js b/ui/components/app/signature-request-original/signature-request-original.test.js index 030451514629..cfc7f8d60a13 100644 --- a/ui/components/app/signature-request-original/signature-request-original.test.js +++ b/ui/components/app/signature-request-original/signature-request-original.test.js @@ -180,4 +180,20 @@ describe('SignatureRequestOriginal', () => { ).toBeNull(); expect(screen.queryByText('OpenSea')).toBeNull(); }); + + it('should display security alert if present', () => { + props.txData.securityAlertResponse = { + resultType: 'Malicious', + reason: 'blur_farming', + description: + 'A SetApprovalForAll request was made on {contract}. We found the operator {operator} to be malicious', + args: { + contract: '0xa7206d878c5c3871826dfdb42191c49b1d11f466', + operator: '0x92a3b9773b1763efa556f55ccbeb20441962d9b2', + }, + }; + + render(); + expect(screen.getByText('This is a deceptive request')).toBeInTheDocument(); + }); }); diff --git a/ui/components/app/signature-request-siwe/signature-request-siwe.js b/ui/components/app/signature-request-siwe/signature-request-siwe.js index e036cc414b35..4332abbd3451 100644 --- a/ui/components/app/signature-request-siwe/signature-request-siwe.js +++ b/ui/components/app/signature-request-siwe/signature-request-siwe.js @@ -38,6 +38,9 @@ import { import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message'; import ConfirmPageContainerNavigation from '../confirm-page-container/confirm-page-container-navigation'; import { getMostRecentOverviewPage } from '../../../ducks/history/history'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; +///: END:ONLY_INCLUDE_IN import LedgerInstructionField from '../ledger-instruction-field'; import SignatureRequestHeader from '../signature-request-header'; @@ -133,13 +136,18 @@ export default function SignatureRequestSIWE({ txData }) { isSIWEDomainValid={isSIWEDomainValid} subjectMetadata={targetSubjectMetadata} /> - + { + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + + ///: END:ONLY_INCLUDE_IN + } {showSecurityProviderBanner && ( )} - {!isMatchingAddress && ( )} - {isLedgerWallet && (
)} - {!isSIWEDomainValid && ( { expect(mockShowModal).toHaveBeenCalled(); }); }); + + it('should display security alert if present', () => { + const store = configureStore(mockStoreInitialState); + const txData = cloneDeep(mockProps.txData); + + const { getByText } = renderWithProvider( + , + store, + ); + + expect(getByText('This is a deceptive request')).toBeInTheDocument(); + }); }); diff --git a/ui/components/app/signature-request/signature-request.js b/ui/components/app/signature-request/signature-request.js index 6838f42540b1..25c64e75919b 100644 --- a/ui/components/app/signature-request/signature-request.js +++ b/ui/components/app/signature-request/signature-request.js @@ -90,6 +90,9 @@ import { mmiActionsFactory } from '../../../store/institutional/institution-back import { showCustodyConfirmLink } from '../../../store/institutional/institution-actions'; import { useMMICustodySignMessage } from '../../../hooks/useMMICustodySignMessage'; ///: END:ONLY_INCLUDE_IN +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import BlockaidBannerAlert from '../security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; +///: END:ONLY_INCLUDE_IN import Message from './signature-request-message'; import Footer from './signature-request-footer'; @@ -245,6 +248,13 @@ const SignatureRequest = ({ txData }) => {
+ { + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + + ///: END:ONLY_INCLUDE_IN + } {(txData?.securityProviderResponse?.flagAsDangerous !== undefined && txData?.securityProviderResponse?.flagAsDangerous !== SECURITY_PROVIDER_MESSAGE_SEVERITY.NOT_MALICIOUS) || diff --git a/ui/components/app/signature-request/signature-request.test.js b/ui/components/app/signature-request/signature-request.test.js index 4c500d3fdb3e..f7acfbab225f 100644 --- a/ui/components/app/signature-request/signature-request.test.js +++ b/ui/components/app/signature-request/signature-request.test.js @@ -447,5 +447,38 @@ describe('Signature Request Component', () => { container.querySelector('.request-signature__mismatch-info'), ).toBeInTheDocument(); }); + + it('should display security alert if present', () => { + const msgParams = { + from: '0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5', + data: JSON.stringify(messageData), + version: 'V4', + origin: 'test', + }; + + const { getByText } = renderWithProvider( + , + store, + ); + + expect(getByText('This is a deceptive request')).toBeInTheDocument(); + }); }); }); diff --git a/ui/pages/token-allowance/token-allowance.js b/ui/pages/token-allowance/token-allowance.js index d5c90a3a52bc..8a24c639f10a 100644 --- a/ui/pages/token-allowance/token-allowance.js +++ b/ui/pages/token-allowance/token-allowance.js @@ -60,6 +60,9 @@ import { NUM_W_OPT_DECIMAL_COMMA_OR_DOT_REGEX, } from '../../../shared/constants/tokens'; import { isSuspiciousResponse } from '../../../shared/modules/security-provider.utils'; +///: BEGIN:ONLY_INCLUDE_IN(blockaid) +import BlockaidBannerAlert from '../../components/app/security-provider-banner-alert/blockaid-banner-alert/blockaid-banner-alert'; +///: END:ONLY_INCLUDE_IN import { ConfirmPageContainerNavigation } from '../../components/app/confirm-page-container'; import { useSimulationFailureWarning } from '../../hooks/useSimulationFailureWarning'; import SimulationErrorMessage from '../../components/ui/simulation-error-message'; @@ -311,6 +314,13 @@ export default function TokenAllowance({ + { + ///: BEGIN:ONLY_INCLUDE_IN(blockaid) + + ///: END:ONLY_INCLUDE_IN + } {isSuspiciousResponse(txData?.securityProviderResponse) && ( { expect(queryByText('Account 1')).toBeInTheDocument(); expect(queryByText('Account 2')).not.toBeInTheDocument(); }); + + it('should display security alert if present', () => { + const { getByText } = renderWithProvider( + , + store, + ); + + expect(getByText('This is a deceptive request')).toBeInTheDocument(); + }); });