Skip to content

Commit 84ce04b

Browse files
authored
test: add E2E tests for maximum privacy security check during onboarding (#38555)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> 1. Adds E2E tests to verify that with maximum privacy settings enabled during onboarding, only allowlisted network calls are made. Covers both import wallet and create new wallet user flows. 2. Add allowlist file `privacy-max-allowlist-onboarding.json` 3. Add code owner for allowlist So we ensure users who enable maximum privacy during onboarding only make expected network calls. example log when test fails: <img width="1078" height="234" alt="Screenshot 2025-12-03 at 15 34 04" src="https://github.com/user-attachments/assets/8f8a2997-c08c-4fc2-a1a2-42b470c365d6" /> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/38555?quickstart=1) ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/MMQA-1154 ## **Manual testing steps** Test pass and stable. Test should fail if new host is called during onboarding which is not in allowlist ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds E2E tests that verify only allowlisted hosts are contacted during and after onboarding, introduces the allowlist JSON, and updates CODEOWNERS. > > - **E2E Tests**: > - Add `test/e2e/tests/privacy/onboarding-maximum-privacy.spec.ts` to enforce maximum-privacy networking during onboarding. > - Covers both `importSRPOnboardingFlow` and `createNewWalletOnboardingFlow`. > - Mocks Infura RPC and feature flags, captures request hosts, and asserts against `duringOnboarding` and `untilOnboardingComplete` allowlists. > - **Test Data**: > - Add `test/e2e/tests/privacy/privacy-max-allowlist-onboarding.json` defining allowlisted hosts for onboarding phases. > - **Repo Ownership**: > - Update `.github/CODEOWNERS` to include QA and extension-privacy-reviewers for the new allowlist and E2E paths. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 163d4b7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent b9b406a commit 84ce04b

File tree

3 files changed

+316
-10
lines changed

3 files changed

+316
-10
lines changed

.github/CODEOWNERS

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,17 @@ app/scripts/lib/smart-transaction @MetaMask/transactions
166166
shared/lib/multichain/addresses/* @MetaMask/new-networks
167167

168168
# QA Team - E2E Framework
169-
test/e2e/page-objects/ @MetaMask/qa
170-
test/e2e/seeder/ @MetaMask/qa
171-
test/e2e/default-fixture.js @MetaMask/qa
172-
test/e2e/fixture-builder.js @MetaMask/qa
173-
test/e2e/fixture-server.js @MetaMask/qa
174-
test/e2e/helpers.js @MetaMask/qa
175-
test/e2e/mock-e2e-allowlist.js @MetaMask/qa
176-
test/e2e/mock-e2e.js @MetaMask/qa
177-
test/e2e/tests/settings/state-logs-helpers.ts @MetaMask/qa
178-
test/e2e/tests/settings/state-logs.json @MetaMask/qa @MetaMask/extension-privacy-reviewers
169+
test/e2e/page-objects/ @MetaMask/qa
170+
test/e2e/seeder/ @MetaMask/qa
171+
test/e2e/default-fixture.js @MetaMask/qa
172+
test/e2e/fixture-builder.js @MetaMask/qa
173+
test/e2e/fixture-server.js @MetaMask/qa
174+
test/e2e/helpers.js @MetaMask/qa
175+
test/e2e/mock-e2e-allowlist.js @MetaMask/qa
176+
test/e2e/mock-e2e.js @MetaMask/qa
177+
test/e2e/tests/settings/state-logs-helpers.ts @MetaMask/qa
178+
test/e2e/tests/settings/state-logs.json @MetaMask/qa @MetaMask/extension-privacy-reviewers
179+
test/e2e/tests/privacy/privacy-max-allowlist-onboarding.json @MetaMask/qa @MetaMask/extension-privacy-reviewers
179180

180181
# Wallet Integrations
181182
app/scripts/lib/rpc-method-middleware @MetaMask/wallet-integrations
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/**
2+
* This test verifies that with maximum privacy settings during onboarding enabled,
3+
* only allowlisted network calls are made during the onboarding process.
4+
*
5+
* It checks two critical phases during onboarding:
6+
* 1. During onboarding (before clicking "Done" on complete onboarding screen)
7+
* 2. After completing onboarding (on homepage without any user action)
8+
*
9+
* The allowlist is defined in privacy-max-allowlist-onboarding.json
10+
*/
11+
import { strict as assert } from 'assert';
12+
import { promises as fs } from 'fs';
13+
import path from 'path';
14+
import { Mockttp, MockedEndpoint } from 'mockttp';
15+
import { withFixtures, veryLargeDelayMs } from '../../helpers';
16+
import { FEATURE_FLAGS_API_MOCK_RESULT } from '../../../data/mock-data';
17+
import FixtureBuilder from '../../fixtures/fixture-builder';
18+
import HomePage from '../../page-objects/pages/home/homepage';
19+
import OnboardingCompletePage from '../../page-objects/pages/onboarding/onboarding-complete-page';
20+
import OnboardingPrivacySettingsPage from '../../page-objects/pages/onboarding/onboarding-privacy-settings-page';
21+
import {
22+
importSRPOnboardingFlow,
23+
createNewWalletOnboardingFlow,
24+
handleSidepanelPostOnboarding,
25+
} from '../../page-objects/flows/onboarding.flow';
26+
27+
const ALLOWLIST_FILE_NAME = 'privacy-max-allowlist-onboarding.json';
28+
29+
async function mockApis(mockServer: Mockttp): Promise<MockedEndpoint[]> {
30+
return [
31+
// Mock Infura RPC endpoints
32+
await mockServer
33+
.forPost(/https:\/\/.*\.infura\.io\/v3\/.*/u)
34+
.thenCallback(() => ({
35+
statusCode: 200,
36+
json: {
37+
jsonrpc: '2.0',
38+
id: '1',
39+
result: '0x1',
40+
},
41+
})),
42+
await mockServer
43+
.forGet('https://bridge.api.cx.metamask.io/featureFlags')
44+
.thenCallback(() => ({
45+
statusCode: 200,
46+
json: FEATURE_FLAGS_API_MOCK_RESULT,
47+
})),
48+
];
49+
}
50+
51+
type OnboardingAllowlist = {
52+
duringOnboarding: string[];
53+
untilOnboardingComplete: string[];
54+
};
55+
56+
async function loadAllowlist(): Promise<OnboardingAllowlist> {
57+
const allowlistPath = path.join(__dirname, ALLOWLIST_FILE_NAME);
58+
const allowlistRaw = await fs.readFile(allowlistPath, 'utf8');
59+
const allowlist: OnboardingAllowlist = JSON.parse(allowlistRaw);
60+
console.log('Privacy maximum onboarding allowlist loaded');
61+
return allowlist;
62+
}
63+
64+
describe('Onboarding with Maximum Privacy Settings', function () {
65+
it('should only make allowlisted network calls during and after import wallet onboarding', async function () {
66+
const allowlist = await loadAllowlist();
67+
const capturedCalls = new Set<string>();
68+
69+
await withFixtures(
70+
{
71+
fixtures: new FixtureBuilder({ onboarding: true }).build(),
72+
title: this.test?.fullTitle(),
73+
testSpecificMock: mockApis,
74+
},
75+
async ({ driver, mockServer }) => {
76+
// Listen to all network requests and capture them
77+
mockServer.on(
78+
'request-initiated',
79+
(request: { headers: { host: string }; url: string }) => {
80+
const { host } = request.headers;
81+
if (host) {
82+
capturedCalls.add(host);
83+
}
84+
},
85+
);
86+
87+
// Complete onboarding up to the complete page
88+
await importSRPOnboardingFlow({ driver });
89+
90+
const onboardingCompletePage = new OnboardingCompletePage(driver);
91+
await onboardingCompletePage.checkPageIsLoaded();
92+
await onboardingCompletePage.checkWalletReadyMessageIsDisplayed();
93+
94+
// Navigate to privacy settings and toggle them OFF (maximum privacy)
95+
await onboardingCompletePage.navigateToDefaultPrivacySettings();
96+
97+
const onboardingPrivacySettingsPage = new OnboardingPrivacySettingsPage(
98+
driver,
99+
);
100+
await onboardingPrivacySettingsPage.checkPageIsLoaded();
101+
await onboardingPrivacySettingsPage.toggleBasicFunctionalitySettings();
102+
await onboardingPrivacySettingsPage.toggleAssetsSettings();
103+
await onboardingPrivacySettingsPage.navigateBackToOnboardingCompletePage();
104+
105+
await onboardingCompletePage.checkPageIsLoaded();
106+
107+
// Intended delay to ensure we cover at least 1 polling loop for network requests
108+
await driver.delay(veryLargeDelayMs);
109+
110+
// Check Phase 1: All calls until now should be in duringOnboarding allowlist
111+
const unexpectedCallsDuringOnboarding = [...capturedCalls].filter(
112+
(host) => !allowlist.duringOnboarding.includes(host),
113+
);
114+
115+
console.log('Checking calls until complete onboarding screen');
116+
console.log('Total calls made during onboarding:', capturedCalls.size);
117+
console.log(
118+
'Calls made during onboarding:',
119+
JSON.stringify([...capturedCalls].sort(), null, 2),
120+
);
121+
122+
// Assert no unexpected hosts during onboarding
123+
assert.equal(
124+
unexpectedCallsDuringOnboarding.length,
125+
0,
126+
`Unexpected network calls during onboarding:\n${unexpectedCallsDuringOnboarding
127+
.map((host) => ` - ${host}`)
128+
.join(
129+
'\n',
130+
)}\n\nThese hosts are NOT in the duringOnboarding allowlist.\nIf these are expected, add them to privacy-max-allowlist-onboarding.json`,
131+
);
132+
133+
// Complete onboarding and go to homepage
134+
await onboardingCompletePage.completeOnboarding();
135+
await handleSidepanelPostOnboarding(driver);
136+
137+
const homePage = new HomePage(driver);
138+
await homePage.checkPageIsLoaded();
139+
140+
// Intended delay to ensure we cover at least 1 polling loop for network requests
141+
await driver.delay(veryLargeDelayMs);
142+
143+
// Check Phase 2: All calls (including previous ones) should be in untilOnboardingComplete allowlist
144+
const unexpectedCallsAfterOnboardingComplete = [
145+
...capturedCalls,
146+
].filter((host) => !allowlist.untilOnboardingComplete.includes(host));
147+
148+
console.log('Checking calls until homepage');
149+
console.log(
150+
'Total calls made until landing on homepage:',
151+
capturedCalls.size,
152+
);
153+
console.log(
154+
'Calls made until landing on homepage:',
155+
JSON.stringify([...capturedCalls].sort(), null, 2),
156+
);
157+
158+
// Assert no unexpected hosts after onboarding complete
159+
assert.equal(
160+
unexpectedCallsAfterOnboardingComplete.length,
161+
0,
162+
`Unexpected network calls after onboarding complete:\n${unexpectedCallsAfterOnboardingComplete
163+
.map((host) => ` - ${host}`)
164+
.join(
165+
'\n',
166+
)}\n\nThese hosts are NOT in the untilOnboardingComplete allowlist.\nIf these are expected, add them to privacy-max-allowlist-onboarding.json`,
167+
);
168+
},
169+
);
170+
});
171+
172+
it('should only make allowlisted network calls during and after create new wallet onboarding', async function () {
173+
const allowlist = await loadAllowlist();
174+
const capturedCalls = new Set<string>();
175+
176+
await withFixtures(
177+
{
178+
fixtures: new FixtureBuilder({ onboarding: true }).build(),
179+
title: this.test?.fullTitle(),
180+
testSpecificMock: mockApis,
181+
},
182+
async ({ driver, mockServer }) => {
183+
// Listen to all network requests and capture them
184+
mockServer.on(
185+
'request-initiated',
186+
(request: { headers: { host: string }; url: string }) => {
187+
const { host } = request.headers;
188+
if (host) {
189+
capturedCalls.add(host);
190+
}
191+
},
192+
);
193+
194+
// Complete onboarding up to the complete page
195+
await createNewWalletOnboardingFlow({ driver });
196+
197+
const onboardingCompletePage = new OnboardingCompletePage(driver);
198+
await onboardingCompletePage.checkPageIsLoaded();
199+
await onboardingCompletePage.checkWalletReadyMessageIsDisplayed();
200+
201+
// Navigate to privacy settings and toggle them OFF (maximum privacy)
202+
await onboardingCompletePage.navigateToDefaultPrivacySettings();
203+
204+
const onboardingPrivacySettingsPage = new OnboardingPrivacySettingsPage(
205+
driver,
206+
);
207+
await onboardingPrivacySettingsPage.checkPageIsLoaded();
208+
await onboardingPrivacySettingsPage.toggleBasicFunctionalitySettings();
209+
await onboardingPrivacySettingsPage.toggleAssetsSettings();
210+
await onboardingPrivacySettingsPage.navigateBackToOnboardingCompletePage();
211+
212+
await onboardingCompletePage.checkPageIsLoaded();
213+
214+
// Intended delay to ensure we cover at least 1 polling loop for network requests
215+
await driver.delay(veryLargeDelayMs);
216+
217+
// Check Phase 1: All calls until now should be in duringOnboarding allowlist
218+
const unexpectedCallsDuringOnboarding = [...capturedCalls].filter(
219+
(host) => !allowlist.duringOnboarding.includes(host),
220+
);
221+
222+
console.log('Checking calls until complete onboarding screen');
223+
console.log('Total calls made during onboarding:', capturedCalls.size);
224+
console.log(
225+
'Calls made during onboarding:',
226+
JSON.stringify([...capturedCalls].sort(), null, 2),
227+
);
228+
229+
// Assert no unexpected hosts during onboarding
230+
assert.equal(
231+
unexpectedCallsDuringOnboarding.length,
232+
0,
233+
`Unexpected network calls during onboarding:\n${unexpectedCallsDuringOnboarding
234+
.map((host) => ` - ${host}`)
235+
.join(
236+
'\n',
237+
)}\n\nThese hosts are NOT in the duringOnboarding allowlist.\nIf these are expected, add them to privacy-max-allowlist-onboarding.json`,
238+
);
239+
240+
// Complete onboarding and go to homepage
241+
await onboardingCompletePage.completeOnboarding();
242+
await handleSidepanelPostOnboarding(driver);
243+
244+
const homePage = new HomePage(driver);
245+
await homePage.checkPageIsLoaded();
246+
247+
// Intended delay to ensure we cover at least 1 polling loop for network requests
248+
await driver.delay(veryLargeDelayMs);
249+
250+
// Check Phase 2: All calls (including previous ones) should be in untilOnboardingComplete allowlist
251+
const unexpectedCallsAfterOnboardingComplete = [
252+
...capturedCalls,
253+
].filter((host) => !allowlist.untilOnboardingComplete.includes(host));
254+
255+
console.log('Checking calls until homepage');
256+
console.log(
257+
'Total calls made until landing on homepage:',
258+
capturedCalls.size,
259+
);
260+
console.log(
261+
'Calls made until landing on homepage:',
262+
JSON.stringify([...capturedCalls].sort(), null, 2),
263+
);
264+
265+
// Assert no unexpected hosts after onboarding complete
266+
assert.equal(
267+
unexpectedCallsAfterOnboardingComplete.length,
268+
0,
269+
`Unexpected network calls after onboarding complete:\n${unexpectedCallsAfterOnboardingComplete
270+
.map((host) => ` - ${host}`)
271+
.join(
272+
'\n',
273+
)}\n\nThese hosts are NOT in the untilOnboardingComplete allowlist.\nIf these are expected, add them to privacy-max-allowlist-onboarding.json`,
274+
);
275+
},
276+
);
277+
});
278+
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"duringOnboarding": [
3+
"accounts.google.com",
4+
"firefox.settings.services.mozilla.com",
5+
"metamask.github.io",
6+
"sentry.io"
7+
],
8+
"toBeRemoved": {
9+
"accounts.google.com": "Should be removed - web3auth team - will only call during social login",
10+
"bridge.api.cx.metamask.io": "Should be removed - STX team - when move to remote feature flag"
11+
},
12+
"untilOnboardingComplete": [
13+
"accounts.google.com",
14+
"arbitrum-mainnet.infura.io",
15+
"base-mainnet.infura.io",
16+
"bridge.api.cx.metamask.io",
17+
"bsc-mainnet.infura.io",
18+
"firefox.settings.services.mozilla.com",
19+
"linea-mainnet.infura.io",
20+
"mainnet.infura.io",
21+
"metamask.github.io",
22+
"optimism-mainnet.infura.io",
23+
"polygon-mainnet.infura.io",
24+
"sei-mainnet.infura.io",
25+
"sentry.io"
26+
]
27+
}

0 commit comments

Comments
 (0)