Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

moved to using href only for suspect site #124

Merged
merged 6 commits into from
Nov 22, 2023
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"command": "npm start",
"name": "Run npm start",
"request": "launch",
"type": "node-terminal"
}
]
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
111 changes: 111 additions & 0 deletions build/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const { createWriteStream, promises: fs } = require('fs');
const path = require('path');
const { pipeline } = require('stream/promises');
const browserify = require('browserify');

const exorcist = require('exorcist');
const { copy } = require('fs-extra');
const { generateSW } = require('workbox-build');
const watchify = require('watchify');
const babelConfig = require('../.babelrc.json');

const rootDirectory = path.resolve(__dirname, '..');
const distDirectory = path.join(rootDirectory, 'dist');
const staticDirectory = path.join(rootDirectory, 'static');
const sourceMapPath = path.join(distDirectory, 'bundle.js.map');

// This workaround is needed because the minified lockdown module isn't
// exported by ses, but "require.resolve" only works for exported modules.
const lockdownSource = require.resolve('ses/lockdown');
const minifiedLockdownSource = path.join(
path.dirname(lockdownSource),
'lockdown.umd.min.js',
);

const filesFromPackages = [
{
source: require.resolve(
'@metamask/design-tokens/src/css/design-tokens.css',
),
filename: 'design-tokens.css',
},
{
source: require.resolve('globalthis/dist/browser.js'),
filename: 'globalthis.js',
},
{
source: minifiedLockdownSource,
filename: 'lockdown-install.js',
},
];

/**
* Generate a service worker using Workbox.
*
* Documentation about the `generateSW` function can be found here:
* {@link https://developer.chrome.com/docs/workbox/modules/workbox-build/#generatesw }
*
* This service worker will pre-cache all essential components of the page. It does not perform
* any runtime caching.
*/
async function generateServiceWorker() {
await generateSW({
cleanupOutdatedCaches: true,
babelPresetEnvTargets: babelConfig.presets[0][1].targets.browsers,
cacheId: 'phishing-warning-page',
// Pre-cache CSS, HTML, SVG, and JavaScript files,
// The fonts and the favicon are conditionally fetched and not strictly necessary.
globDirectory: distDirectory,
globPatterns: ['**/*.{css,html,js,svg}'],
mode: 'development',
swDest: path.join(distDirectory, 'service-worker.js'),
});
}

/**
* Build a JavaScript bundle for the phishing warning page.
*/
async function main() {
const bundler = browserify({
debug: true,
entries: [path.join(__dirname, '../src/index.ts')],
extensions: ['.ts'],
plugin: [watchify],
});

/**
* Bundles the JavaScript and copies the static assets to the dist directory.
*/
async function bundle() {
console.log(`>>>>> Building ${new Date().toLocaleTimeString()} <<<<<`);
await fs.rm(distDirectory, { force: true, recursive: true });
await fs.mkdir(distDirectory);
bundler.transform('babelify', { extensions: ['.ts', '.js', '.json'] });

await pipeline(
bundler.bundle(),
exorcist(sourceMapPath),
createWriteStream(path.join(distDirectory, 'bundle.js')),
);

await Promise.all([
...filesFromPackages.map(async ({ source, filename }) => {
await fs.copyFile(source, path.join(distDirectory, filename));
}),
copy(staticDirectory, distDirectory, {
overwrite: true,
errorOnExist: true,
}),
]);

await generateServiceWorker();
}

bundler.on('update', bundle);
bundle();
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -12,12 +12,15 @@
],
"scripts": {
"build": "node ./build/index.js",
"build:dev": "node ./build/dev.js",
"build:prod": "NODE='production' yarn build",
"dev": "yarn build:dev",
"lint": "yarn lint:eslint && yarn lint:misc --check",
"lint:eslint": "eslint . --cache --ext js,ts",
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern",
"prepack": "./scripts/prepack.sh",
"serve": "yarn http-server ./dist",
NicholasEllul marked this conversation as resolved.
Show resolved Hide resolved
"setup": "yarn install && yarn allow-scripts",
"start": "yarn build && yarn http-server ./dist",
"test": "yarn playwright test"
@@ -63,6 +66,7 @@
"prettier-plugin-packagejson": "^2.2.17",
"ts-node": "^10.7.0",
"typescript": "~4.4.4",
"watchify": "^4.0.0",
"workbox-build": "^6.6.0"
},
"packageManager": "yarn@3.4.1",
38 changes: 29 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -138,6 +138,30 @@ async function isBlockedByMetamask(href: string) {
}
}

/**
* Extract hostname and href from the query string.
*
* @returns The suspect hostname and href from the query string.
* @param href - The href value to check.
*/
function getSuspect(href: string | null): {
suspectHostname: string;
suspectHref: string;
suspectHrefPlain: string;
} {
try {
const hrefStr = href || '';
const url = new URL(hrefStr);
witmicko marked this conversation as resolved.
Show resolved Hide resolved
return {
suspectHostname: url.hostname,
suspectHref: url.href,
suspectHrefPlain: hrefStr,
};
} catch (error) {
throw new Error(`Invalid 'href' query parameter`);
}
Comment on lines +160 to +161
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing an error here is a good call so that we reject untrusted inputs. However, this will break us setting up the back to safety button on the page.

Can we restructure this so that we ensure that the back to safety button is set up correctly before we process this input? That way, if we do raise an error on the page, at least the button will still let the user navigate back to their original page.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch here @NicholasEllul

}

/**
* Initialize the phishing warning page streams.
*/
@@ -157,14 +181,10 @@ function start() {
const { hash } = new URL(window.location.href);
const hashContents = hash.slice(1); // drop leading '#' from hash
const hashQueryString = new URLSearchParams(hashContents);
const suspectHostname = hashQueryString.get('hostname');
const suspectHref = hashQueryString.get('href');

if (!suspectHostname) {
throw new Error("Missing 'hostname' query parameter");
} else if (!suspectHref) {
throw new Error("Missing 'href' query parameter");
}
const { suspectHostname, suspectHref, suspectHrefPlain } = getSuspect(
hashQueryString.get('href'),
);

const suspectLink = document.getElementById('suspect-link');
if (!suspectLink) {
@@ -178,8 +198,8 @@ function start() {
}

const newIssueParams = `?title=[Legitimate%20Site%20Blocked]%20${encodeURIComponent(
suspectHostname,
)}&body=${encodeURIComponent(suspectHref)}`;
suspectHrefPlain,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we change from using the hostname in the issue title to the href. This can potentially result in long issue title names if the href if very long. I don't have strong opinions, but just want to verify that this was intentional

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was't certain about this one, would switching to suspectHostnamePlain be better?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking abou it a bit more, since the issue would be regarding unblocking a domain (the path is not relevant), I think adding only the hostname makes the most sense

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this was kept as the full href, rather than the hostname. Was this a mistake?

Not a huge issue either way, but I am inclined to agree with @NicholasEllul that it would be better to just reference the hostname. The extra information in the href is effectively ignored. Including that in the eth-phishing-detect issue might be confusing, and it certainly wouldn't be helpful.

)}&body=${encodeURIComponent(suspectHrefPlain)}`;

newIssueLink.addEventListener('click', async () => {
const listName = (await isBlockedByMetamask(suspectHref))
2 changes: 1 addition & 1 deletion tests/failed-lookup.spec.ts
Original file line number Diff line number Diff line change
@@ -17,6 +17,6 @@ test('directs users to eth-phishing-detect to dispute a block, including issue t
await page.waitForLoadState('networkidle');

await expect(page).toHaveURL(
'https://github.com/MetaMask/eth-phishing-detect/issues/new?title=[Legitimate%20Site%20Blocked]%20test.com&body=https%3A%2F%2Ftest.com',
'https://github.com/MetaMask/eth-phishing-detect/issues/new?title=[Legitimate%20Site%20Blocked]%20https%3A%2F%2Ftest.com&body=https%3A%2F%2Ftest.com',
);
});
26 changes: 2 additions & 24 deletions tests/invalid-inputs.spec.ts
Original file line number Diff line number Diff line change
@@ -6,29 +6,7 @@ test.beforeEach(async ({ context }) => {
await setupDefaultMocks(context);
});

test('throws an error about the hostname query parameter being missing', async ({
context,
page,
}) => {
const errorLogs: string[] = [];
page.on('console', (message) => {
if (message.type() === 'error') {
errorLogs.push(message.text());
}
});

await page.goto('/');

expect(errorLogs.length).toBe(1);
const browserName = context.browser()?.browserType().name();
expect(errorLogs[0]).toMatch(
browserName === 'firefox'
? 'Error' // for some reason the message is missing on Firefox
: `Error: Missing 'hostname' query parameter`,
);
});

test('throws an error about the href query parameter being missing', async ({
test('throws an error about the href query parameter being invalid', async ({
context,
page,
}) => {
@@ -47,7 +25,7 @@ test('throws an error about the href query parameter being missing', async ({
expect(errorLogs[0]).toMatch(
browserName === 'firefox'
? 'Error' // for some reason the message is missing on Firefox
: `Error: Missing 'href' query parameter`,
: `Error: Invalid 'href' query parameter`,
);
});

10 changes: 6 additions & 4 deletions tests/metamask-block.spec.ts
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ test('directs users to eth-phishing-detect to dispute a block, including issue t
await page.waitForLoadState('networkidle');

await expect(page).toHaveURL(
'https://github.com/MetaMask/eth-phishing-detect/issues/new?title=[Legitimate%20Site%20Blocked]%20test.com&body=https%3A%2F%2Ftest.com',
'https://github.com/MetaMask/eth-phishing-detect/issues/new?title=[Legitimate%20Site%20Blocked]%20https%3A%2F%2Ftest.com&body=https%3A%2F%2Ftest.com',
);
});

@@ -33,17 +33,19 @@ test('correctly matches unicode domains', async ({ context, page }) => {
blacklist: ['xn--metamsk-en4c.io'],
},
});
const url = 'https://metamạsk.io';
const querystring = new URLSearchParams({
hostname: 'test.com',
href: 'https://metamạsk.io',
hostname: url,
witmicko marked this conversation as resolved.
Show resolved Hide resolved
href: url,
});
const encoded = encodeURIComponent(url);
await page.goto(`/#${querystring}`);

await page.getByRole('link', { name: 'report a detection problem' }).click();
// Wait for dynamic configuration check
await page.waitForLoadState('networkidle');

await expect(page).toHaveURL(
'https://github.com/MetaMask/eth-phishing-detect/issues/new?title=[Legitimate%20Site%20Blocked]%20test.com&body=https%3A%2F%2Fmetamạsk.io',
`https://github.com/MetaMask/eth-phishing-detect/issues/new?title=[Legitimate%20Site%20Blocked]%20${encoded}&body=${encoded}`,
);
});
2 changes: 1 addition & 1 deletion tests/phishfort-block.spec.ts
Original file line number Diff line number Diff line change
@@ -19,6 +19,6 @@ test('directs users to PhishFort to dispute a block, including issue template pa
await page.waitForLoadState('networkidle');

await expect(page).toHaveURL(
'https://github.com/phishfort/phishfort-lists/issues/new?title=[Legitimate%20Site%20Blocked]%20test.com&body=https%3A%2F%2Ftest.com',
'https://github.com/phishfort/phishfort-lists/issues/new?title=[Legitimate%20Site%20Blocked]%20https%3A%2F%2Ftest.com&body=https%3A%2F%2Ftest.com',
);
});
Loading