Skip to content

Commit ea6bb6e

Browse files
committed
feat: apply DevTools header redactions by default
This PR adds a CLI flag to enable redacting network headers in the same way they are redacted in DevTools. Note that sometimes it might prevent the agent from properly analysing network issues. Pass `--redact-headers=false` to revert to the previous behavior.
1 parent 49eed51 commit ea6bb6e

File tree

11 files changed

+126
-22
lines changed

11 files changed

+126
-22
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,11 @@ The Chrome DevTools MCP server supports the following configuration option:
620620
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
621621
- **Type:** boolean
622622

623+
- **`--redactNetworkHeaders`/ `--redact-network-headers`**
624+
If true, redacts some of the network headers considered senstive before returning to the client.
625+
- **Type:** boolean
626+
- **Default:** `true`
627+
623628
<!-- END AUTO GENERATED OPTIONS -->
624629

625630
Pass them via the `args` property in the JSON configuration. For example:

src/McpResponse.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ export class McpResponse implements Response {
185185
#tabId?: string;
186186
#args: ParsedArguments;
187187
#page?: McpPage;
188+
#redactNetworkHeaders = true;
188189

189190
constructor(args: ParsedArguments) {
190191
this.#args = args;
@@ -194,6 +195,10 @@ export class McpResponse implements Response {
194195
this.#page = page;
195196
}
196197

198+
setRedactNetworkHeaders(value: boolean): void {
199+
this.#redactNetworkHeaders = value;
200+
}
201+
197202
attachDevToolsData(data: DevToolsData): void {
198203
this.#devToolsData = data;
199204
}
@@ -425,6 +430,7 @@ export class McpResponse implements Response {
425430
requestFilePath: this.#attachedNetworkRequestOptions?.requestFilePath,
426431
responseFilePath: this.#attachedNetworkRequestOptions?.responseFilePath,
427432
saveFile: (data, filename) => context.saveFile(data, filename),
433+
redactNetworkHeaders: this.#redactNetworkHeaders,
428434
});
429435
detailedNetworkRequest = formatter;
430436
}
@@ -568,6 +574,7 @@ export class McpResponse implements Response {
568574
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI,
569575
fetchData: false,
570576
saveFile: (data, filename) => context.saveFile(data, filename),
577+
redactNetworkHeaders: this.#redactNetworkHeaders,
571578
}),
572579
),
573580
);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ export const cliOptions = {
261261
'Set by Chrome DevTools CLI if the MCP server is started via the CLI client (this arg exists for usage stats)',
262262
hidden: true,
263263
},
264+
redactNetworkHeaders: {
265+
type: 'boolean',
266+
describe:
267+
'If true, redacts some of the network headers considered senstive before returning to the client.',
268+
default: false,
269+
},
264270
} satisfies Record<string, YargsOptions>;
265271

266272
export type ParsedArguments = ReturnType<typeof parseArguments>;

src/formatters/NetworkFormatter.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
import {isUtf8} from 'node:buffer';
88

9-
import type {HTTPRequest, HTTPResponse} from '../third_party/index.js';
9+
import {
10+
DevTools,
11+
type HTTPRequest,
12+
type HTTPResponse,
13+
} from '../third_party/index.js';
1014

1115
const BODY_CONTEXT_SIZE_LIMIT = 10000;
1216

@@ -21,6 +25,7 @@ export interface NetworkFormatterOptions {
2125
data: Uint8Array<ArrayBufferLike>,
2226
filename: string,
2327
) => Promise<{filename: string}>;
28+
redactNetworkHeaders: boolean;
2429
}
2530

2631
interface NetworkRequestConcise {
@@ -150,6 +155,20 @@ export class NetworkFormatter {
150155
};
151156
}
152157

158+
#redactNetworkHeaders(
159+
headers: Record<string, string>,
160+
): Record<string, string> {
161+
const headersList = Object.entries(headers).map(item => {
162+
return {name: item[0], value: item[1]};
163+
});
164+
const redacted =
165+
DevTools.NetworkRequestFormatter.sanitizeHeaders(headersList);
166+
return redacted.reduce<Record<string, string>>((acc, item) => {
167+
acc[item.name] = item.value;
168+
return acc;
169+
}, {});
170+
}
171+
153172
toJSONDetailed(): NetworkRequestDetailed {
154173
const redirectChain = this.#request.redirectChain();
155174
const formattedRedirectChain = redirectChain.reverse().map(request => {
@@ -159,16 +178,24 @@ export class NetworkFormatter {
159178
const formatter = new NetworkFormatter(request, {
160179
requestId: id,
161180
saveFile: this.#options.saveFile,
181+
redactNetworkHeaders: this.#options.redactNetworkHeaders,
162182
});
163183
return formatter.toJSON();
164184
});
165185

186+
const responseHeaders = this.#request.response()?.headers();
187+
166188
return {
167189
...this.toJSON(),
168-
requestHeaders: this.#request.headers(),
190+
requestHeaders: this.#options.redactNetworkHeaders
191+
? this.#redactNetworkHeaders(this.#request.headers())
192+
: this.#request.headers(),
169193
requestBody: this.#requestBody,
170194
requestBodyFilePath: this.#requestBodyFilePath,
171-
responseHeaders: this.#request.response()?.headers(),
195+
responseHeaders:
196+
this.#options.redactNetworkHeaders && responseHeaders
197+
? this.#redactNetworkHeaders(responseHeaders)
198+
: this.#request.response()?.headers(),
172199
responseBody: this.#responseBody,
173200
responseBodyFilePath: this.#responseBodyFilePath,
174201
failure: this.#request.failure()?.errorText,

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ export async function createMcpServer(
191191
const response = serverArgs.slim
192192
? new SlimMcpResponse(serverArgs)
193193
: new McpResponse(serverArgs);
194+
195+
response.setRedactNetworkHeaders(serverArgs.redactNetworkHeaders);
194196
if ('pageScoped' in tool && tool.pageScoped) {
195197
const page =
196198
serverArgs.experimentalPageIdRouting &&

tests/McpContext.test.js.snapshot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ exports[`McpContext > should include detailed network request in structured cont
66
"url": "http://example.com/detail",
77
"status": "pending",
88
"requestHeaders": {
9-
"content-size": "10"
9+
"content-size": "<redacted>"
1010
}
1111
}
1212
}

tests/McpResponse.test.js.snapshot

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ exports[`McpResponse > add network request when attached 1`] = `
22
## Request http://example.com
33
Status: pending
44
### Request Headers
5-
- content-size:10
5+
- content-size:<redacted>
66
## Network requests
77
Showing 1-1 of 1 (Page 1 of 1).
88
reqid=1 GET http://example.com [pending]
@@ -16,7 +16,7 @@ exports[`McpResponse > add network request when attached 2`] = `
1616
"url": "http://example.com",
1717
"status": "pending",
1818
"requestHeaders": {
19-
"content-size": "10"
19+
"content-size": "<redacted>"
2020
}
2121
},
2222
"pagination": {
@@ -44,7 +44,7 @@ exports[`McpResponse > add network request when attached with POST data 1`] = `
4444
## Request http://example.com
4545
Status: 200
4646
### Request Headers
47-
- content-size:10
47+
- content-size:<redacted>
4848
### Request Body
4949
{"request":"body"}
5050
### Response Headers
@@ -64,7 +64,7 @@ exports[`McpResponse > add network request when attached with POST data 2`] = `
6464
"url": "http://example.com",
6565
"status": "200",
6666
"requestHeaders": {
67-
"content-size": "10"
67+
"content-size": "<redacted>"
6868
},
6969
"requestBody": "{\\"request\\":\\"body\\"}",
7070
"responseHeaders": {

tests/cli.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ describe('cli args parsing', () => {
2323
performanceCrux: true,
2424
'usage-statistics': true,
2525
usageStatistics: true,
26+
'redact-network-headers': false,
27+
redactNetworkHeaders: false,
2628
};
2729

2830
it('parses with default args', async () => {

tests/formatters/NetworkFormatter.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('NetworkFormatter', () => {
3131
const formatter = await NetworkFormatter.from(request, {
3232
requestId: 1,
3333
saveFile: async () => ({filename: ''}),
34+
redactNetworkHeaders: false,
3435
});
3536

3637
assert.equal(
@@ -43,6 +44,7 @@ describe('NetworkFormatter', () => {
4344
const formatter = await NetworkFormatter.from(request, {
4445
requestId: 1,
4546
saveFile: async () => ({filename: ''}),
47+
redactNetworkHeaders: false,
4648
});
4749

4850
assert.equal(
@@ -56,6 +58,7 @@ describe('NetworkFormatter', () => {
5658
const formatter = await NetworkFormatter.from(request, {
5759
requestId: 1,
5860
saveFile: async () => ({filename: ''}),
61+
redactNetworkHeaders: false,
5962
});
6063

6164
assert.equal(
@@ -71,6 +74,7 @@ describe('NetworkFormatter', () => {
7174
const formatter = await NetworkFormatter.from(request, {
7275
requestId: 1,
7376
saveFile: async () => ({filename: ''}),
77+
redactNetworkHeaders: false,
7478
});
7579

7680
assert.equal(
@@ -86,6 +90,7 @@ describe('NetworkFormatter', () => {
8690
const formatter = await NetworkFormatter.from(request, {
8791
requestId: 1,
8892
saveFile: async () => ({filename: ''}),
93+
redactNetworkHeaders: false,
8994
});
9095

9196
assert.equal(
@@ -104,6 +109,7 @@ describe('NetworkFormatter', () => {
104109
const formatter = await NetworkFormatter.from(request, {
105110
requestId: 1,
106111
saveFile: async () => ({filename: ''}),
112+
redactNetworkHeaders: false,
107113
});
108114

109115
assert.equal(
@@ -118,6 +124,7 @@ describe('NetworkFormatter', () => {
118124
requestId: 1,
119125
selectedInDevToolsUI: true,
120126
saveFile: async () => ({filename: ''}),
127+
redactNetworkHeaders: false,
121128
});
122129

123130
assert.equal(
@@ -138,6 +145,7 @@ describe('NetworkFormatter', () => {
138145
requestId: 200,
139146
fetchData: true,
140147
saveFile: async () => ({filename: ''}),
148+
redactNetworkHeaders: false,
141149
});
142150
const result = formatter.toStringDetailed();
143151
assert.match(result, /test/);
@@ -154,6 +162,7 @@ describe('NetworkFormatter', () => {
154162
requestId: 200,
155163
fetchData: true,
156164
saveFile: async () => ({filename: ''}),
165+
redactNetworkHeaders: false,
157166
});
158167
const result = formatter.toStringDetailed();
159168

@@ -176,6 +185,7 @@ describe('NetworkFormatter', () => {
176185
requestId: 20,
177186
fetchData: true,
178187
saveFile: async () => ({filename: ''}),
188+
redactNetworkHeaders: false,
179189
});
180190
const result = formatter.toStringDetailed();
181191
assert.match(result, /some text/);
@@ -209,6 +219,7 @@ describe('NetworkFormatter', () => {
209219
await writeFile(filename, data);
210220
return {filename};
211221
},
222+
redactNetworkHeaders: false,
212223
});
213224

214225
const json = formatter.toJSONDetailed() as {
@@ -252,6 +263,7 @@ describe('NetworkFormatter', () => {
252263
await writeFile(filename, data);
253264
return {filename};
254265
},
266+
redactNetworkHeaders: false,
255267
});
256268

257269
const reqContent = await readFile(reqPath, 'utf8');
@@ -272,6 +284,7 @@ describe('NetworkFormatter', () => {
272284
requestId: 200,
273285
fetchData: true,
274286
saveFile: async () => ({filename: ''}),
287+
redactNetworkHeaders: false,
275288
});
276289
const result = formatter.toStringDetailed();
277290

@@ -289,6 +302,7 @@ describe('NetworkFormatter', () => {
289302
requestId: 1,
290303
requestIdResolver: () => 2,
291304
saveFile: async () => ({filename: ''}),
305+
redactNetworkHeaders: false,
292306
});
293307
const result = formatter.toStringDetailed();
294308
assert.match(result, /Redirect chain/);
@@ -322,6 +336,7 @@ describe('NetworkFormatter', () => {
322336
await writeFile(filename, data);
323337
return {filename};
324338
},
339+
redactNetworkHeaders: false,
325340
});
326341

327342
const result = formatter.toStringDetailed();
@@ -361,6 +376,7 @@ describe('NetworkFormatter', () => {
361376
await writeFile(filename, data);
362377
return {filename};
363378
},
379+
redactNetworkHeaders: false,
364380
});
365381

366382
const result = formatter.toStringDetailed();
@@ -379,6 +395,7 @@ describe('NetworkFormatter', () => {
379395
requestId: 1,
380396
selectedInDevToolsUI: true,
381397
saveFile: async () => ({filename: ''}),
398+
redactNetworkHeaders: false,
382399
});
383400
const result = formatter.toJSON();
384401
assert.deepEqual(result, {
@@ -404,6 +421,7 @@ describe('NetworkFormatter', () => {
404421
requestId: 1,
405422
fetchData: true,
406423
saveFile: async () => ({filename: ''}),
424+
redactNetworkHeaders: false,
407425
});
408426
const result = formatter.toJSONDetailed();
409427
assert.deepEqual(result, {
@@ -425,6 +443,38 @@ describe('NetworkFormatter', () => {
425443
});
426444
});
427445

446+
it('redacts headers', async () => {
447+
const response = getMockResponse({
448+
headers: {
449+
'set-cookie': 'secret=123',
450+
'content-type': 'text/plain',
451+
},
452+
});
453+
response.buffer = () => Promise.resolve(Buffer.from('response'));
454+
const request = getMockRequest({
455+
response,
456+
headers: {
457+
cookie: 'secret=123',
458+
'user-agent': 'test',
459+
},
460+
});
461+
const formatter = await NetworkFormatter.from(request, {
462+
requestId: 1,
463+
fetchData: true,
464+
saveFile: async () => ({filename: ''}),
465+
redactNetworkHeaders: true,
466+
});
467+
const result = formatter.toJSONDetailed();
468+
assert.deepEqual(result.requestHeaders, {
469+
cookie: '<redacted>',
470+
'user-agent': 'test',
471+
});
472+
assert.deepEqual(result.responseHeaders, {
473+
'set-cookie': '<redacted>',
474+
'content-type': 'text/plain',
475+
});
476+
});
477+
428478
it('returns file paths in structured detailed data', async () => {
429479
const request = {
430480
method: () => 'POST',
@@ -453,6 +503,7 @@ describe('NetworkFormatter', () => {
453503
await writeFile(filename, data);
454504
return {filename};
455505
},
506+
redactNetworkHeaders: false,
456507
});
457508

458509
const result = formatter.toJSONDetailed() as {

0 commit comments

Comments
 (0)