fix(browser): block sensitive header intercept matchers

Squash merge PR #1294 into dev.
This commit is contained in:
Kai (Tam Nhu) Tran
2026-05-19 08:31:17 -04:00
committed by GitHub
parent 1a06ead908
commit a86486497b
2 changed files with 63 additions and 1 deletions
+23 -1
View File
@@ -83,6 +83,16 @@ const TOOL_HOVER = 'browser_hover';
const TOOL_QUERY = 'browser_query';
const TOOL_TAKE_ELEMENT_SCREENSHOT = 'browser_take_element_screenshot';
const TOOL_WAIT_FOR_EVENT = 'browser_wait_for_event';
const SENSITIVE_INTERCEPT_HEADER_NAMES = new Set([
'authorization',
'cookie',
'cookie2',
'proxy-authorization',
'x-api-key',
'x-api-token',
'x-auth-token',
]);
const TOOL_NAMES = [
TOOL_SESSION_INFO,
TOOL_URL_TITLE,
@@ -612,10 +622,15 @@ function getTools() {
urlRegex: { type: 'string' },
headerMatchers: {
type: 'array',
description:
'Match non-sensitive request headers. Cookie, Authorization, and token headers are not allowed.',
items: {
type: 'object',
properties: {
name: { type: 'string' },
name: {
type: 'string',
description: 'Non-sensitive request header name to match.',
},
valueIncludes: { type: 'string' },
valueRegex: { type: 'string' },
},
@@ -1379,6 +1394,10 @@ function parseOptionalPriority(value) {
return value;
}
function isSensitiveInterceptHeaderName(name) {
return SENSITIVE_INTERCEPT_HEADER_NAMES.has(name.toLowerCase());
}
function parseOptionalHeaderMatchers(value) {
if (value === undefined) {
return [];
@@ -1391,6 +1410,9 @@ function parseOptionalHeaderMatchers(value) {
throw new Error('headerMatchers entries must be objects');
}
const name = requireNonEmptyString(entry.name, 'headerMatchers.name');
if (isSensitiveInterceptHeaderName(name)) {
throw new Error(`headerMatchers.name cannot target sensitive request header: ${name}`);
}
const valueIncludes =
entry.valueIncludes === undefined
? ''
@@ -1779,6 +1779,46 @@ describe('ccs-browser MCP server - session and interception', () => {
);
});
it('rejects intercept rules that match sensitive request headers', async () => {
const responses = await runMcpRequests(
[{ id: 'page-1', title: 'Home', currentUrl: 'https://example.com/' }],
[
{
jsonrpc: '2.0',
id: 1003,
method: 'tools/call',
params: {
name: 'browser_add_intercept_rule',
arguments: {
headerMatchers: [{ name: 'Cookie', valueRegex: '^session=' }],
action: 'continue',
},
},
},
{
jsonrpc: '2.0',
id: 1004,
method: 'tools/call',
params: {
name: 'browser_add_intercept_rule',
arguments: {
headerMatchers: [{ name: 'authorization', valueIncludes: 'Bearer ' }],
action: 'continue',
},
},
},
]
);
for (const id of [1003, 1004]) {
const response = responses.find((message) => message.id === id);
expect((response?.result as { isError?: boolean }).isError).toBe(true);
expect(getResponseText(response)).toContain(
'Browser MCP failed: headerMatchers.name cannot target sensitive request header'
);
}
});
it('removes fulfill rules and request summaries after the bound page is closed', async () => {
const responses = await runMcpRequests(
[