1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 03:12:42 +02:00

Use externally hosted phishing warning page

An externally hosted phishing warning page is now used rather than the
built-in phishing warning page.The phishing page warning URL is set via
configuration file or environment variable. The default URL is either
the expected production URL or `http://localhost:9999/` for e2e testing
environments.

The new external phishing page includes a design change when it is
loaded within an iframe. In that case it now shows a condensed message,
and prompts the user to open the full warning page in a new tab to see
more details or bypass the warning. This is to prevent a clickjacking
attack from safelisting a site without user consent.

The new external phishing page also includes a simple caching service
worker to ensure it continues to work offline (or if our hosting goes
offline), as long as the user has successfully loaded the page at least
once. We also load the page temporarily during the extension startup
process to trigger the service worker installation.

The old phishing page and all related lines have been removed. The
property `web_accessible_resources` has also been removed from the
manifest. The only entry apart from the phishing page was `inpage.js`,
and we don't need that to be web accessible anymore because we inject
the script inline into each page rather than loading the file directly.

New e2e tests have been added to cover more phishing warning page
functionality, including the "safelist" action and the "iframe" case.
This commit is contained in:
Mark Stacey 2022-05-05 19:58:48 -02:30
parent 8a14504b63
commit 7199d9c567
19 changed files with 413 additions and 256 deletions

View File

@ -72,6 +72,5 @@
"*://*.eth/",
"notifications"
],
"short_name": "__MSG_appName__",
"web_accessible_resources": ["inpage.js", "phishing.html"]
"short_name": "__MSG_appName__"
}

View File

@ -1,150 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>MetaMask Phishing Detection</title>
<script
src="./globalthis.js"
type="text/javascript"
charset="utf-8"
></script>
<script
src="./lockdown-install.js"
type="text/javascript"
charset="utf-8"
></script>
<script
src="./lockdown-run.js"
type="text/javascript"
charset="utf-8"
></script>
<script
src="./lockdown-more.js"
type="text/javascript"
charset="utf-8"
></script>
<script src="./phishing-detect.js"></script>
<link rel="stylesheet" type="text/css" href="./index.css" title="ltr" />
<link
rel="stylesheet"
type="text/css"
href="./index-rtl.css"
title="rtl"
disabled
/>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
background-color: var(--color-error-default);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-family: Roboto, Arial, sans-serif;
width: 100vw;
min-height: 100vh;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
width: 80%;
background-color: var(--color-background-default);
box-shadow: 0 0 15px #737373;
}
.content__header {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
color: var(--color-error-default);
border-bottom: 1px solid var(--color-border-default);
padding: 2em;
}
.content__header h1 {
font-size: 24px;
font-weight: normal;
}
.content__header h1 i {
margin-right: 0.25em;
}
.content__header img {
margin-bottom: 3em;
width: 130px;
}
.content__body {
background-color: var(--color-background-alternative);
font-size: 12pt;
color: var(--color-text-default);
}
.content__body p {
margin: 2em;
}
.content__body p a {
text-decoration: underline;
color: var(--color-primary-default);
cursor: pointer;
}
</style>
</head>
<body>
<div class="content">
<div class="content__header">
<img src="./images/logo/metamask-fox.svg" alt="MetaMask Logo" />
<h1>
<i class="fa fa-exclamation-circle" aria-hidden="true"></i>
MetaMask Phishing Detection
</h1>
</div>
<div class="content__body">
<p>
This domain is currently on the MetaMask domain warning list. This
means that based on information available to us, MetaMask believes
this domain could currently compromise your security and, as an added
safety feature, MetaMask has restricted access to the site. To
override this, please read the rest of this warning for instructions
on how to continue at your own risk.
</p>
<p>
There are many reasons sites can appear on our warning list, and our
warning list compiles from other widely used industry lists. Such
reasons can include known fraud or security risks, such as domains
that test positive on the
<a href="https://github.com/metamask/eth-phishing-detect"
>Ethereum Phishing Detector</a
>. Domains on these warning lists may include outright malicious
websites and legitimate websites that have been compromised by a
malicious actor.
</p>
<p>
To read more about this site
<a id="csdbLink" href="https://cryptoscamdb.org/search"
>please search for the domain on CryptoScamDB</a
>.
</p>
<p>
Note that this warning list is compiled on a voluntary basis. This
list may be inaccurate or incomplete. Just because a domain does not
appear on this list is not an implicit guarantee of that domain's
safety. As always, your transactions are your own responsibility. If
you wish to interact with any domain on our warning list, you can do
so by <a id="unsafe-continue">continuing at your own risk</a>.
</p>
<p>
If you think this domain is incorrectly flagged or if a blocked
legitimate website has resolved its security issues,
<a
id="new-issue-link"
href="https://github.com/metamask/eth-phishing-detect/issues/new"
>please file an issue</a
>.
</p>
</div>
</div>
</body>
</html>

View File

@ -67,6 +67,12 @@ if (inTest || process.env.METAMASK_DEBUG) {
global.metamaskGetState = localStore.get.bind(localStore);
}
const phishingPageUrl = new URL(process.env.PHISHING_PAGE_URL);
const ONE_SECOND_IN_MILLISECONDS = 1_000;
// Timeout for initializing phishing warning page.
const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS;
// initialization flow
initialize().catch(log.error);
@ -134,9 +140,76 @@ async function initialize() {
const initState = await loadStateFromPersistence();
const initLangCode = await getFirstPreferredLangCode();
await setupController(initState, initLangCode);
await loadPhishingWarningPage();
log.info('MetaMask initialization complete.');
}
/**
* An error thrown if the phishing warning page takes too long to load.
*/
class PhishingWarningPageTimeoutError extends Error {
constructor() {
super('Timeout failed');
}
}
/**
* Load the phishing warning page temporarily to ensure the service
* worker has been registered, so that the warning page works offline.
*/
async function loadPhishingWarningPage() {
let iframe;
try {
const extensionStartupPhishingPageUrl = new URL(
process.env.PHISHING_PAGE_URL,
);
// The `extensionStartup` hash signals to the phishing warning page that it should not bother
// setting up streams for user interaction. Otherwise this page load would cause a console
// error.
extensionStartupPhishingPageUrl.hash = '#extensionStartup';
iframe = window.document.createElement('iframe');
iframe.setAttribute('src', extensionStartupPhishingPageUrl.href);
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
// Create "deferred Promise" to allow passing resolve/reject to event handlers
let deferredResolve;
let deferredReject;
const loadComplete = new Promise((resolve, reject) => {
deferredResolve = resolve;
deferredReject = reject;
});
// The load event is emitted once loading has completed, even if the loading failed.
// If loading failed we can't do anything about it, so we don't need to check.
iframe.addEventListener('load', deferredResolve);
// This step initiates the page loading.
window.document.body.appendChild(iframe);
// This timeout ensures that this iframe gets cleaned up in a reasonable
// timeframe, and ensures that the "initialization complete" message
// doesn't get delayed too long.
setTimeout(
() => deferredReject(new PhishingWarningPageTimeoutError()),
PHISHING_WARNING_PAGE_TIMEOUT,
);
await loadComplete;
} catch (error) {
if (error instanceof PhishingWarningPageTimeoutError) {
console.warn(
'Phishing warning page timeout; page not guaraneteed to work offline.',
);
} else {
console.error('Failed to initialize phishing warning page', error);
}
} finally {
if (iframe) {
iframe.remove();
}
}
}
//
// State and Persistence
//
@ -362,6 +435,10 @@ function setupController(initState, initLangCode) {
remotePort.sender.origin === `chrome-extension://${browser.runtime.id}`;
}
const senderUrl = remotePort.sender?.url
? new URL(remotePort.sender.url)
: null;
if (isMetaMaskInternalProcess) {
const portStream = new PortStream(remotePort);
// communication with popup
@ -406,6 +483,15 @@ function setupController(initState, initLangCode) {
);
});
}
} else if (
senderUrl &&
senderUrl.origin === phishingPageUrl.origin &&
senderUrl.pathname === phishingPageUrl.pathname
) {
const portStream = new PortStream(remotePort);
controller.setupPhishingCommunication({
connectionStream: portStream,
});
} else {
if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) {
const tabId = remotePort.sender.tab.id;

View File

@ -17,8 +17,13 @@ const inpageContent = fs.readFileSync(
const inpageSuffix = `//# sourceURL=${browser.runtime.getURL('inpage.js')}\n`;
const inpageBundle = inpageContent + inpageSuffix;
// contexts
const CONTENT_SCRIPT = 'metamask-contentscript';
const INPAGE = 'metamask-inpage';
const PHISHING_WARNING_PAGE = 'metamask-phishing-warning-page';
// stream channels
const PHISHING_SAFELIST = 'metamask-phishing-safelist';
const PROVIDER = 'metamask-provider';
// TODO:LegacyProvider: Delete
@ -27,7 +32,14 @@ const LEGACY_INPAGE = 'inpage';
const LEGACY_PROVIDER = 'provider';
const LEGACY_PUBLIC_CONFIG = 'publicConfig';
if (shouldInjectProvider()) {
const phishingPageUrl = new URL(process.env.PHISHING_PAGE_URL);
if (
window.location.origin === phishingPageUrl.origin &&
window.location.pathname === phishingPageUrl.pathname
) {
setupPhishingStream();
} else if (shouldInjectProvider()) {
injectScript(inpageBundle);
setupStreams();
}
@ -50,6 +62,47 @@ function injectScript(content) {
}
}
async function setupPhishingStream() {
// the transport-specific streams for communication between inpage and background
const pageStream = new WindowPostMessageStream({
name: CONTENT_SCRIPT,
target: PHISHING_WARNING_PAGE,
});
const extensionPort = browser.runtime.connect({ name: CONTENT_SCRIPT });
const extensionStream = new PortStream(extensionPort);
// create and connect channel muxers
// so we can handle the channels individually
const pageMux = new ObjectMultiplex();
pageMux.setMaxListeners(25);
const extensionMux = new ObjectMultiplex();
extensionMux.setMaxListeners(25);
pump(pageMux, pageStream, pageMux, (err) =>
logStreamDisconnectWarning('MetaMask Inpage Multiplex', err),
);
pump(extensionMux, extensionStream, extensionMux, (err) => {
logStreamDisconnectWarning('MetaMask Background Multiplex', err);
window.postMessage(
{
target: PHISHING_WARNING_PAGE, // the post-message-stream "target"
data: {
// this object gets passed to obj-multiplex
name: PHISHING_SAFELIST, // the obj-multiplex channel name
data: {
jsonrpc: '2.0',
method: 'METAMASK_STREAM_FAILURE',
},
},
},
window.location.origin,
);
});
// forward communication across inpage-background for these channels only
forwardTrafficBetweenMuxes(PHISHING_SAFELIST, pageMux, extensionMux);
}
/**
* Sets up two-way communication streams between the
* browser extension and local per-page browser context.
@ -300,9 +353,9 @@ function blockedDomainCheck() {
* Redirects the current page to a phishing information page
*/
function redirectToPhishingWarning() {
console.debug('MetaMask: Routing to Phishing Warning component.');
const extensionURL = browser.runtime.getURL('phishing.html');
window.location.href = `${extensionURL}#${querystring.stringify({
console.debug('MetaMask: Routing to Phishing Warning page.');
const baseUrl = process.env.PHISHING_PAGE_URL;
window.location.href = `${baseUrl}#${querystring.stringify({
hostname: window.location.hostname,
href: window.location.href,
})}`;

View File

@ -27,7 +27,7 @@ const getEnvironmentTypeMemo = memoize((url) => {
const parsedUrl = new URL(url);
if (parsedUrl.pathname === '/popup.html') {
return ENVIRONMENT_TYPE_POPUP;
} else if (['/home.html', '/phishing.html'].includes(parsedUrl.pathname)) {
} else if (['/home.html'].includes(parsedUrl.pathname)) {
return ENVIRONMENT_TYPE_FULLSCREEN;
} else if (parsedUrl.pathname === '/notification.html') {
return ENVIRONMENT_TYPE_NOTIFICATION;

View File

@ -34,13 +34,6 @@ describe('app utils', () => {
expect(environmentType).toStrictEqual(ENVIRONMENT_TYPE_FULLSCREEN);
});
it('should return fullscreen type for phishing.html', () => {
const environmentType = getEnvironmentType(
'http://extension-id/phishing.html',
);
expect(environmentType).toStrictEqual(ENVIRONMENT_TYPE_FULLSCREEN);
});
it('should return background type', () => {
const environmentType = getEnvironmentType(
'http://extension-id/_generated_background_page.html',

View File

@ -145,6 +145,9 @@ export const METAMASK_CONTROLLER_EVENTS = {
APPROVAL_STATE_CHANGE: 'ApprovalController:stateChange',
};
// stream channels
const PHISHING_SAFELIST = 'metamask-phishing-safelist';
export default class MetamaskController extends EventEmitter {
/**
* @param {Object} opts
@ -1371,7 +1374,6 @@ export default class MetamaskController extends EventEmitter {
),
markPasswordForgotten: this.markPasswordForgotten.bind(this),
unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
safelistPhishingDomain: this.safelistPhishingDomain.bind(this),
getRequestAccountTabIds: this.getRequestAccountTabIds,
getOpenMetamaskTabsIds: this.getOpenMetamaskTabsIds,
markNotificationPopupAsAutomaticallyClosed: () =>
@ -3152,6 +3154,33 @@ export default class MetamaskController extends EventEmitter {
);
}
/**
* Used to create a multiplexed stream for connecting to the phishing warning page.
*
* @param options - Options bag.
* @param {ReadableStream} options.connectionStream - The Duplex stream to connect to.
*/
setupPhishingCommunication({ connectionStream }) {
const { usePhishDetect } = this.preferencesController.store.getState();
if (!usePhishDetect) {
return;
}
// setup multiplexing
const mux = setupMultiplex(connectionStream);
const phishingStream = mux.createStream(PHISHING_SAFELIST);
// set up postStream transport
phishingStream.on(
'data',
createMetaRPCHandler(
{ safelistPhishingDomain: this.safelistPhishingDomain.bind(this) },
phishingStream,
),
);
}
/**
* Called when we detect a suspicious domain. Requests the browser redirects
* to our anti-phishing page.

View File

@ -1,52 +0,0 @@
import querystring from 'querystring';
import PortStream from 'extension-port-stream';
import browser from 'webextension-polyfill';
import createRandomId from '../../shared/modules/random-id';
import { setupMultiplex } from './lib/stream-utils';
import { getEnvironmentType } from './lib/util';
import ExtensionPlatform from './platforms/extension';
document.addEventListener('DOMContentLoaded', start);
function start() {
const hash = window.location.hash.substring(1);
const suspect = querystring.parse(hash);
const newIssueLink = document.getElementById('new-issue-link');
const newIssueUrl = `https://github.com/MetaMask/eth-phishing-detect/issues/new`;
const newIssueParams = `?title=[Legitimate%20Site%20Blocked]%20${encodeURIComponent(
suspect.hostname,
)}&body=${encodeURIComponent(suspect.href)}`;
newIssueLink.href = `${newIssueUrl}${newIssueParams}`;
global.platform = new ExtensionPlatform();
const extensionPort = browser.runtime.connect({
name: getEnvironmentType(),
});
const connectionStream = new PortStream(extensionPort);
const mx = setupMultiplex(connectionStream);
const backgroundConnection = mx.createStream('controller');
const continueLink = document.getElementById('unsafe-continue');
continueLink.addEventListener('click', () => {
backgroundConnection.write({
jsonrpc: '2.0',
method: 'safelistPhishingDomain',
params: [suspect.hostname],
id: createRandomId(),
});
const redirectTarget = new URL(suspect.href, window.location.href);
// validate redirect url
const invalidProtocol = !['https:', 'http:'].includes(
redirectTarget.protocol,
);
// if in valid, show warning and abort
if (invalidProtocol) {
// we intentionally dont display to the user any potential attacker-written content here
console.error(`Invalid redirect url.`);
return;
}
// use the validated url instance
window.location.href = redirectTarget.href;
});
}

View File

@ -34,6 +34,7 @@ const metamaskrc = require('rc')('metamask', {
INFURA_PROD_PROJECT_ID: process.env.INFURA_PROD_PROJECT_ID,
ONBOARDING_V2: process.env.ONBOARDING_V2,
COLLECTIBLES_V1: process.env.COLLECTIBLES_V1,
PHISHING_PAGE_URL: process.env.PHISHING_PAGE_URL,
TOKEN_DETECTION_V2: process.env.TOKEN_DETECTION_V2,
SEGMENT_HOST: process.env.SEGMENT_HOST,
SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
@ -133,6 +134,48 @@ function getSegmentWriteKey({ buildType, environment }) {
throw new Error(`Invalid build type: '${buildType}'`);
}
/**
* Get the URL for the phishing warning page, if it has been set.
*
* @param options0
* @param options0.testing
* @returns {string} The URL for the phishing warning page, or `undefined` if no URL is set.
*/
function getPhishingWarningPageUrl({ testing }) {
let phishingWarningPageUrl = metamaskrc.PHISHING_PAGE_URL;
if (!phishingWarningPageUrl) {
phishingWarningPageUrl = testing
? 'http://localhost:9999/'
: 'https://metamask.github.io/phishing-warning/v1.1.0/';
}
// We add a hash/fragment to the URL dynamically, so we need to ensure it
// has a valid pathname to append a hash to.
const normalizedUrl = phishingWarningPageUrl.endsWith('/')
? phishingWarningPageUrl
: `${phishingWarningPageUrl}/`;
let phishingWarningPageUrlObject;
try {
// eslint-disable-next-line no-new
phishingWarningPageUrlObject = new URL(normalizedUrl);
} catch (error) {
throw new Error(
`Invalid phishing warning page URL: '${normalizedUrl}'`,
error,
);
}
if (phishingWarningPageUrlObject.hash) {
// The URL fragment must be set dynamically
throw new Error(
`URL fragment not allowed in phishing warning page URL: '${normalizedUrl}'`,
);
}
return normalizedUrl;
}
const noopWriteStream = through.obj((_file, _fileEncoding, callback) =>
callback(),
);
@ -216,11 +259,6 @@ function createScriptTasks({
createTaskForBundleSentry({ devMode, testing }),
);
const phishingDetectSubtask = createTask(
`${taskPrefix}:phishing-detect`,
createTaskForBundlePhishingDetect({ devMode, testing }),
);
// task for initiating browser livereload
const initiateLiveReload = async () => {
if (devMode) {
@ -243,7 +281,6 @@ function createScriptTasks({
contentscriptSubtask,
disableConsoleSubtask,
installSentrySubtask,
phishingDetectSubtask,
].map((subtask) =>
runInChildProcess(subtask, {
buildType,
@ -290,23 +327,6 @@ function createScriptTasks({
});
}
function createTaskForBundlePhishingDetect({ devMode, testing }) {
const label = 'phishing-detect';
return createNormalBundle({
buildType,
browserPlatforms,
destFilepath: `${label}.js`,
devMode,
entryFilepath: `./app/scripts/${label}.js`,
ignoredFiles,
label,
testing,
policyOnly,
shouldLintFenceFiles,
version,
});
}
// the "contentscript" bundle contains the "inpage" bundle
function createTaskForBundleContentscript({ devMode, testing }) {
const inpage = 'inpage';
@ -818,6 +838,7 @@ function getEnvironmentVariables({ buildType, devMode, testing, version }) {
METAMASK_BUILD_TYPE: buildType,
NODE_ENV: devMode ? ENVIRONMENT.DEVELOPMENT : ENVIRONMENT.PRODUCTION,
IN_TEST: testing,
PHISHING_PAGE_URL: getPhishingWarningPageUrl({ testing }),
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
CONF: devMode ? metamaskrc : {},

View File

@ -174,10 +174,6 @@ function getCopyTargets(shouldIncludeLockdown) {
src: require.resolve('@lavamoat/lavapack/src/runtime.js'),
dest: `runtime-lavamoat.js`,
},
{
src: `./app/phishing.html`,
dest: `phishing.html`,
},
];
const languageTags = new Set();

View File

@ -24,7 +24,6 @@ async function start() {
`common-0.js`,
`background-0.js`,
`ui-0.js`,
'phishing-detect.js',
// `contentscript.js`, skipped because the validator is erroneously sampling the inlined `inpage.js` script
`inpage.js`,
];

View File

@ -252,6 +252,7 @@
"@metamask/eslint-config-nodejs": "^9.0.0",
"@metamask/eslint-config-typescript": "^9.0.1",
"@metamask/forwarder": "^1.1.0",
"@metamask/phishing-warning": "^1.0.0",
"@metamask/test-dapp": "^5.0.0",
"@sentry/cli": "^1.58.0",
"@storybook/addon-a11y": "^6.3.12",

View File

@ -6,6 +6,7 @@ const enLocaleMessages = require('../../app/_locales/en/messages.json');
const { setupMocking } = require('./mock-e2e');
const Ganache = require('./ganache');
const FixtureServer = require('./fixture-server');
const PhishingWarningPageServer = require('./phishing-warning-page-server');
const { buildWebDriver } = require('./webdriver');
const { ensureXServerIsRunning } = require('./x-server');
@ -27,6 +28,7 @@ async function withFixtures(options, testSuite) {
title,
failOnConsoleError = true,
dappPath = undefined,
dappPaths,
testSpecificMock = function () {
// do nothing.
},
@ -38,6 +40,7 @@ async function withFixtures(options, testSuite) {
let secondaryGanacheServer;
let numberOfDapps = dapp ? 1 : 0;
const dappServer = [];
const phishingPageServer = new PhishingWarningPageServer();
let webDriver;
let failed = false;
@ -55,14 +58,15 @@ async function withFixtures(options, testSuite) {
}
await fixtureServer.start();
await fixtureServer.loadState(path.join(__dirname, 'fixtures', fixtures));
await phishingPageServer.start();
if (dapp) {
if (dappOptions?.numberOfDapps) {
numberOfDapps = dappOptions.numberOfDapps;
}
for (let i = 0; i < numberOfDapps; i++) {
let dappDirectory;
if (dappPath) {
dappDirectory = path.resolve(__dirname, dappPath);
if (dappPath || (dappPaths && dappPaths[i])) {
dappDirectory = path.resolve(__dirname, dappPath || dappPaths[i]);
} else {
dappDirectory = path.resolve(
__dirname,
@ -146,6 +150,9 @@ async function withFixtures(options, testSuite) {
}
}
}
if (phishingPageServer.isRunning()) {
await phishingPageServer.quit();
}
await mockServer.stop();
}
}

View File

@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<title>Mock E2E Phishing Page</title>
</head>
<script type="text/javascript">
function setIframeSource() {
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
document.getElementById('frame').src = params.extensionUrl;
}
window.onload = setIframeSource;
</script>
<body>
<div>Hello</div>
<iframe id="frame" width=900 height=900>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<title>Mock E2E Phishing Page</title>
</head>
<body>
<div>Hello</div>
<iframe src="http://127.0.0.1:8081" width=900 height=900>
</body>
</html>

View File

@ -0,0 +1,58 @@
const path = require('path');
const createStaticServer = require('../../development/create-static-server');
const phishingWarningDirectory = path.resolve(
__dirname,
'..',
'..',
'node_modules',
'@metamask',
'phishing-warning',
'dist',
);
class PhishingWarningPageServer {
constructor() {
this._server = createStaticServer(phishingWarningDirectory);
}
async start({ port = 9999 } = {}) {
this._server.listen(port);
let resolveStart;
let rejectStart;
const result = new Promise((resolve, reject) => {
resolveStart = resolve;
rejectStart = reject;
});
this._server.once('listening', resolveStart);
this._server.once('error', rejectStart);
try {
await result;
// clean up listener to ensure later errors properly bubble up
this._server.removeListener('error', rejectStart);
} catch (error) {
this._server.removeListener('listening', resolveStart);
throw error;
}
}
isRunning() {
return this._server.listening;
}
async quit() {
await new Promise((resolve, reject) =>
this._server.close((error) => {
if (error) {
reject(error);
} else {
resolve();
}
}),
);
}
}
module.exports = PhishingWarningPageServer;

View File

@ -1,4 +1,3 @@
/* eslint-disable mocha/no-skipped-tests */
const { strict: assert } = require('assert');
const { convertToHexValue, withFixtures } = require('../helpers');
@ -16,7 +15,7 @@ describe('Phishing Detection', function () {
tolerance: 2,
fuzzylist: [],
whitelist: [],
blacklist: ['example.com'],
blacklist: ['127.0.0.1'],
},
};
});
@ -30,23 +29,90 @@ describe('Phishing Detection', function () {
},
],
};
it.skip('should display the MetaMask Phishing Detection page', async function () {
it('should display the MetaMask Phishing Detection page and take the user to the blocked page if they continue', async function () {
await withFixtures(
{
fixtures: 'imported-account',
ganacheOptions,
title: this.test.title,
testSpecificMock: mockPhishingDetection,
dapp: true,
failOnConsoleError: false,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
await driver.navigate();
await driver.openNewPage('http://example.com');
await driver.waitForSelector({ text: 'continuing at your own risk' });
await driver.openNewPage('http://127.0.0.1:8080');
await driver.clickElement({
text: 'continuing at your own risk',
});
const header = await driver.findElement('h1');
assert.equal(await header.getText(), 'MetaMask Phishing Detection');
assert.equal(await header.getText(), 'E2E Test Dapp');
},
);
});
it('should display the MetaMask Phishing Detection page in an iframe and take the user to the blocked page if they continue', async function () {
await withFixtures(
{
fixtures: 'imported-account',
ganacheOptions,
title: this.test.title,
testSpecificMock: mockPhishingDetection,
dapp: true,
dappPaths: ['mock-page-with-iframe'],
dappOptions: {
numberOfDapps: 2,
},
failOnConsoleError: false,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
await driver.openNewPage('http://localhost:8080/');
const iframe = await driver.findElement('iframe');
await driver.switchToFrame(iframe);
await driver.clickElement({
text: 'continuing at your own risk',
});
const header = await driver.findElement('h1');
assert.equal(await header.getText(), 'E2E Test Dapp');
},
);
});
it('should display the MetaMask Phishing Detection page in an iframe but should NOT take the user to the blocked page if it is not an accessible resource', async function () {
await withFixtures(
{
fixtures: 'imported-account',
ganacheOptions,
title: this.test.title,
testSpecificMock: mockPhishingDetection,
dapp: true,
dappPaths: ['mock-page-with-disallowed-iframe'],
dappOptions: {
numberOfDapps: 2,
},
failOnConsoleError: false,
},
async ({ driver }) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
await driver.press('#password', driver.Key.ENTER);
await driver.openNewPage(
`http://localhost:8080?extensionUrl=${driver.extensionUrl}`,
);
const iframe = await driver.findElement('iframe');
await driver.switchToFrame(iframe);
await driver.assertElementNotPresent({
text: 'continuing at your own risk',
});
},
);
});

View File

@ -303,6 +303,10 @@ class Driver {
await this.driver.switchTo().window(handle);
}
async switchToFrame(element) {
await this.driver.switchTo().frame(element);
}
async getAllWindowHandles() {
return await this.driver.getAllWindowHandles();
}

View File

@ -2809,6 +2809,11 @@
resolved "https://registry.yarnpkg.com/@metamask/design-tokens/-/design-tokens-1.4.4.tgz#85a80f0ba5ff34a595bd1d879dbf4219c0fbfe7a"
integrity sha512-OzlUv3GSBbmVlO4EcFrkK2/InxaBMH31O2ncVabJvySc/HbhpXMEm7ZtowPqxlBI05V0WdVxSv/0MZtRkbIyXA==
"@metamask/design-tokens@^1.6.0":
version "1.6.5"
resolved "https://registry.yarnpkg.com/@metamask/design-tokens/-/design-tokens-1.6.5.tgz#e585b67f73ce301e0218d98ba89e079f7e81c412"
integrity sha512-5eCrUHXrIivXX1xx6kwNtM9s/ejhrPYSATSniFc7YKS9z+TkCK4/n52owOBnDIbrL8W3XxQIiaaqQAM+NQad4w==
"@metamask/eslint-config-jest@^9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@metamask/eslint-config-jest/-/eslint-config-jest-9.0.0.tgz#516fdf1f03f6f006b26ca790bf748e2189d19d17"
@ -2967,6 +2972,18 @@
"@metamask/safe-event-emitter" "^2.0.0"
through2 "^2.0.3"
"@metamask/phishing-warning@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@metamask/phishing-warning/-/phishing-warning-1.0.0.tgz#e40ddecbaff4ae5af038eb8f5607b1f72cf97ea4"
integrity sha512-bRdd16NonDTsWI5Vwaac4wvTS61XRW2phc0FBXrdkj9WHKTo747MKOZht4bWmgCHtlZMYrT4wIQGt+NeyU6rag==
dependencies:
"@metamask/design-tokens" "^1.6.0"
"@metamask/post-message-stream" "^4.0.0"
globalthis "1.0.1"
obj-multiplex "^1.0.0"
pump "^3.0.0"
ses "0.12.4"
"@metamask/post-message-stream@4.0.0", "@metamask/post-message-stream@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-4.0.0.tgz#72f120e562346ca86ccc9b3684023ad44265f0df"
@ -13440,7 +13457,7 @@ globals@^9.14.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
globalthis@^1.0.0, globalthis@^1.0.1:
globalthis@1.0.1, globalthis@^1.0.0, globalthis@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9"
integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw==
@ -24633,7 +24650,7 @@ serve-static@1.14.1:
parseurl "~1.3.3"
send "0.17.1"
ses@^0.12.4:
ses@0.12.4, ses@^0.12.4:
version "0.12.4"
resolved "https://registry.yarnpkg.com/ses/-/ses-0.12.4.tgz#f466f7199292b5c4454949c7d497f5569ade5805"
integrity sha512-qbtkhuuAXNXb390yiaNUdNvDg/QmX7W2cO+srIUJllINMYADc/8m0vt7DNBmq+rqOBRrjVRPPeyQq8ZTLK3Rmw==