diff --git a/lib/mcp/ccs-browser-server.cjs b/lib/mcp/ccs-browser-server.cjs index cbc8d07a..dbe56fab 100755 --- a/lib/mcp/ccs-browser-server.cjs +++ b/lib/mcp/ccs-browser-server.cjs @@ -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 ? '' diff --git a/tests/unit/hooks/browser-mcp-session-and-intercepts.test.ts b/tests/unit/hooks/browser-mcp-session-and-intercepts.test.ts index 50954827..0c2f8a33 100644 --- a/tests/unit/hooks/browser-mcp-session-and-intercepts.test.ts +++ b/tests/unit/hooks/browser-mcp-session-and-intercepts.test.ts @@ -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( [