Skip to content

Commit d696987

Browse files
Natallia HarshunovaOrKoN
authored andcommitted
Implement url allowlist and blocklist
1 parent 32dc50d commit d696987

4 files changed

Lines changed: 116 additions & 0 deletions

File tree

src/bin/chrome-devtools-mcp-cli-options.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,17 @@ export const cliOptions = {
204204
describe:
205205
'Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.',
206206
},
207+
blocklist: {
208+
type: 'array',
209+
describe:
210+
'URL patterns to block access to. Uses standard URLPattern API. Cannot be used with --blocklist',
211+
},
212+
allowlist: {
213+
type: 'array',
214+
describe:
215+
'URL patterns to allow access to (blocks everything else). Uses standard URLPattern API. Cannot be used with --blocklist.',
216+
conflicts: ['blocklist'],
217+
},
207218
ignoreDefaultChromeArg: {
208219
type: 'array',
209220
describe:

src/browser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export async function ensureBrowserConnected(options: {
5151
channel?: Channel;
5252
userDataDir?: string;
5353
enableExtensions?: boolean;
54+
blocklist?: string[];
55+
allowlist?: string[];
5456
}) {
5557
const {channel, enableExtensions} = options;
5658
if (browser?.connected) {
@@ -61,6 +63,8 @@ export async function ensureBrowserConnected(options: {
6163
targetFilter: makeTargetFilter(enableExtensions),
6264
defaultViewport: null,
6365
handleDevToolsAsPage: true,
66+
blocklist: options.blocklist,
67+
allowlist: options.allowlist,
6468
};
6569

6670
let autoConnect = false;
@@ -150,6 +154,8 @@ interface McpLaunchOptions {
150154
devtools: boolean;
151155
enableExtensions?: boolean;
152156
viaCli?: boolean;
157+
blocklist?: string[];
158+
allowlist?: string[];
153159
}
154160

155161
export function detectDisplay(): void {
@@ -229,6 +235,8 @@ export async function launch(options: McpLaunchOptions): Promise<Browser> {
229235
acceptInsecureCerts: options.acceptInsecureCerts,
230236
handleDevToolsAsPage: true,
231237
enableExtensions: options.enableExtensions,
238+
blocklist: options.blocklist,
239+
allowlist: options.allowlist,
232240
});
233241
if (options.logFile) {
234242
// FIXME: we are probably subscribing too late to catch startup logs. We

src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ export async function createMcpServer(
9999
chromeArgs.push(`--proxy-server=${serverArgs.proxyServer}`);
100100
}
101101
const devtools = serverArgs.experimentalDevtools ?? false;
102+
const blocklist = serverArgs.blocklist
103+
? serverArgs.blocklist.map(String)
104+
: undefined;
105+
const allowlist = serverArgs.allowlist
106+
? serverArgs.allowlist.map(String)
107+
: undefined;
108+
102109
const browser =
103110
serverArgs.browserUrl || serverArgs.wsEndpoint || serverArgs.autoConnect
104111
? await ensureBrowserConnected({
@@ -111,6 +118,8 @@ export async function createMcpServer(
111118
: undefined,
112119
userDataDir: serverArgs.userDataDir,
113120
devtools,
121+
blocklist,
122+
allowlist,
114123
})
115124
: await ensureBrowserLaunched({
116125
headless: serverArgs.headless,
@@ -126,6 +135,8 @@ export async function createMcpServer(
126135
devtools,
127136
enableExtensions: serverArgs.categoryExtensions,
128137
viaCli: serverArgs.viaCli,
138+
blocklist,
139+
allowlist,
129140
});
130141

131142
if (context?.browser !== browser) {

tests/browser.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {executablePath} from 'puppeteer';
1313

1414
import {detectDisplay, ensureBrowserConnected, launch} from '../src/browser.js';
1515

16+
import {serverHooks} from './server.js';
17+
1618
describe('browser', () => {
1719
it('detects display does not crash', () => {
1820
detectDisplay();
@@ -100,4 +102,88 @@ describe('browser', () => {
100102
await browser.close();
101103
}
102104
});
105+
106+
describe('Blocking', () => {
107+
const server = serverHooks();
108+
109+
it('blocks URLs in blocklist', async () => {
110+
server.addHtmlRoute('/allowed.html', '<html><body>Allowed</body></html>');
111+
server.addHtmlRoute('/blocked.html', '<html><body>Blocked</body></html>');
112+
113+
const browser = await launch({
114+
headless: true,
115+
isolated: true,
116+
executablePath: executablePath(),
117+
devtools: false,
118+
blocklist: ['*://*:*/blocked.html'],
119+
});
120+
try {
121+
const page = await browser.newPage();
122+
123+
// Access allowed URL
124+
await page.goto(server.getRoute('/allowed.html'));
125+
const content = await page.evaluate(() => document.body.textContent);
126+
assert.strictEqual(content, 'Allowed');
127+
128+
// Fetch of blocked URL from the page
129+
const fetchResult = await page.evaluate(async url => {
130+
try {
131+
await fetch(url);
132+
return 'SUCCESS';
133+
} catch (err) {
134+
return err instanceof Error ? err.message : String(err);
135+
}
136+
}, server.getRoute('/blocked.html'));
137+
138+
assert.strictEqual(fetchResult, 'Failed to fetch');
139+
} finally {
140+
await browser.close();
141+
}
142+
});
143+
144+
it(
145+
'blocks URLs not in allowlist',
146+
{skip: 'Requires Chrome 149 or greater'},
147+
async () => {
148+
server.addHtmlRoute(
149+
'/allowed.html',
150+
'<html><body>Allowed</body></html>',
151+
);
152+
server.addHtmlRoute(
153+
'/blocked.html',
154+
'<html><body>Blocked</body></html>',
155+
);
156+
157+
const browser = await launch({
158+
headless: true,
159+
isolated: true,
160+
executablePath: executablePath(),
161+
devtools: false,
162+
allowlist: ['*://*/allowed.html'],
163+
});
164+
try {
165+
const page = await browser.newPage();
166+
167+
// Access allowed URL
168+
await page.goto(server.getRoute('/allowed.html'));
169+
const content = await page.evaluate(() => document.body.textContent);
170+
assert.strictEqual(content, 'Allowed');
171+
172+
// Fetch of blocked URL from the page
173+
const fetchResult = await page.evaluate(async url => {
174+
try {
175+
await fetch(url);
176+
return 'SUCCESS';
177+
} catch (err) {
178+
return err instanceof Error ? err.message : String(err);
179+
}
180+
}, server.getRoute('/blocked.html'));
181+
182+
assert.strictEqual(fetchResult, 'Failed to fetch');
183+
} finally {
184+
await browser.close();
185+
}
186+
},
187+
);
188+
});
103189
});

0 commit comments

Comments
 (0)