Skip to content

Commit 87a9c71

Browse files
authored
fix(core): sanitize and remove hardcoded Google CrUX API key in chrome-devtools-mcp (#29158)
1 parent 86b461e commit 87a9c71

5 files changed

Lines changed: 149 additions & 0 deletions

File tree

packages/cli/src/ui/components/__snapshots__/BackgroundTaskDisplay.test.tsx.snap

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ exports[`<BackgroundTaskDisplay /> > keeps exit code status color even when sele
2525
"
2626
`;
2727

28+
exports[`<BackgroundTaskDisplay /> > keeps exit code status color even when selected 2`] = `
29+
"┌──────────────────────────────────────────────────────────────────────────────┐
30+
│ 1: npm sta.. (PID: 1003) Close (Ctrl+B) | Kill (Ctrl+K) | List │
31+
│ (Focused) (Ctrl+L) │
32+
│ │
33+
│ Select Process (Enter to select, Ctrl+K to kill, Esc to cancel): │
34+
│ │
35+
│ 1. npm start (PID: 1001) │
36+
│ 2. tail -f log.txt (PID: 1002) │
37+
│ ● 3. exit 0 (PID: 1003) (Exit Code: 0) │
38+
│ Log: ~/.gemini/tmp/background-processes/background-1003.log │
39+
└──────────────────────────────────────────────────────────────────────────────┘
40+
"
41+
`;
42+
2843
exports[`<BackgroundTaskDisplay /> > renders tabs for multiple shells 1`] = `
2944
"┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
3045
│ 1: npm start 2: tail -f lo... (PID: 1001) Close (Ctrl+B) | Kill (Ctrl+K) | List (Ctrl+L) │

packages/cli/src/ui/components/__snapshots__/InputPrompt.test.tsx.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ exports[`InputPrompt > multiline rendering > should correctly render multiline i
169169
────────────────────────────────────────────────────────────────────────────────────────────────────"
170170
`;
171171

172+
exports[`InputPrompt > should still allow input when shell is not focused 1`] = `
173+
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
174+
> Type your message or @path/to/file
175+
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀"
176+
`;
177+
172178
exports[`InputPrompt > snapshots > should not show inverted cursor when shell is focused 1`] = `
173179
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
174180
> Type your message or @path/to/file

packages/cli/src/ui/components/messages/__snapshots__/DiffRenderer.test.tsx.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlterna
8484
"
8585
`;
8686

87+
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should render a gap indicator for skipped lines 2`] = `
88+
" 1 context line 1
89+
2 - deleted line
90+
2 + added line
91+
════════════════════════════════════════════════════════════════════════════════
92+
10 context line 10
93+
11 context line 11
94+
"
95+
`;
96+
8797
exports[`<OverflowProvider><DiffRenderer /></OverflowProvider> > with useAlternateBuffer = false > should render diff content for existing file (not calling colorizeCode directly for the whole block) 1`] = `
8898
"1 - old line
8999
1 + new line

packages/core/scripts/bundle-browser-mcp.mjs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,44 @@ async function bundle() {
9595
} else {
9696
console.warn(`Warning: third_party assets not found at ${srcThirdParty}`);
9797
}
98+
99+
// Sanitize any hardcoded sensitive API keys in the bundled and copied output files.
100+
// Replace the plaintext Google CrUX API key with dynamic process.env lookup at runtime.
101+
const bundleMcpFile = path.resolve(
102+
__dirname,
103+
'../dist/bundled/chrome-devtools-mcp.mjs',
104+
);
105+
const destThirdPartyIndex = path.resolve(
106+
__dirname,
107+
'../dist/bundled/third_party/index.js',
108+
);
109+
110+
const sanitizeFile = (filePath) => {
111+
if (fs.existsSync(filePath)) {
112+
const content = fs.readFileSync(filePath, 'utf8');
113+
const keyPattern = /AIzaSy[A-Za-z0-9_-]{30,40}/;
114+
if (keyPattern.test(content)) {
115+
console.log(`Sanitizing hardcoded API key in: ${filePath}`);
116+
// Replace single-quoted, double-quoted, and backtick occurrences of any matching Google API key with process.env lookup
117+
const quotedPattern = /([\x27\x22\x60])AIzaSy[A-Za-z0-9_-]{30,40}\1/g;
118+
const sanitizedContent = content.replace(
119+
quotedPattern,
120+
'(process.env.CRUX_API_KEY || "")',
121+
);
122+
123+
if (keyPattern.test(sanitizedContent)) {
124+
throw new Error(
125+
`Failed to fully sanitize API key in ${filePath}. The key pattern was detected but could not be safely replaced.`,
126+
);
127+
}
128+
129+
fs.writeFileSync(filePath, sanitizedContent, 'utf8');
130+
}
131+
}
132+
};
133+
134+
sanitizeFile(bundleMcpFile);
135+
sanitizeFile(destThirdPartyIndex);
98136
} catch (error) {
99137
console.error('Error bundling chrome-devtools-mcp:', error);
100138
process.exit(1);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect } from 'vitest';
8+
import * as fs from 'node:fs';
9+
import * as path from 'node:path';
10+
import { fileURLToPath } from 'node:url';
11+
12+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
13+
14+
describe('Google CrUX API Key Leak Detection', () => {
15+
const LEAKED_KEY = Buffer.from(
16+
'QUl6YVN5Q0NTT3gyNXZyYjV6MHRiZWRDQjNfSlJ6emJWVzZVd2d3',
17+
'base64',
18+
).toString('utf8');
19+
20+
it('should verify the raw node_modules dependency contains the hardcoded Google CrUX API key', () => {
21+
const dependencyPath = path.resolve(
22+
__dirname,
23+
'../../../../node_modules/chrome-devtools-mcp/build/src/third_party/index.js',
24+
);
25+
26+
if (fs.existsSync(dependencyPath)) {
27+
const content = fs.readFileSync(dependencyPath, 'utf8');
28+
expect(content).toContain(LEAKED_KEY);
29+
} else {
30+
throw new Error(
31+
`Expected chrome-devtools-mcp source file to exist at: ${dependencyPath}`,
32+
);
33+
}
34+
});
35+
36+
it('should not contain the hardcoded Google CrUX API key in bundled chrome-devtools-mcp.mjs', () => {
37+
const bundleMcpPath = path.resolve(
38+
__dirname,
39+
'../../dist/bundled/chrome-devtools-mcp.mjs',
40+
);
41+
42+
if (fs.existsSync(bundleMcpPath)) {
43+
const content = fs.readFileSync(bundleMcpPath, 'utf8');
44+
expect(content).not.toContain(LEAKED_KEY);
45+
}
46+
});
47+
48+
it('should not contain the hardcoded Google CrUX API key in bundled third_party assets', () => {
49+
const thirdPartyPath = path.resolve(
50+
__dirname,
51+
'../../dist/bundled/third_party/index.js',
52+
);
53+
54+
if (fs.existsSync(thirdPartyPath)) {
55+
const content = fs.readFileSync(thirdPartyPath, 'utf8');
56+
expect(content).not.toContain(LEAKED_KEY);
57+
}
58+
});
59+
60+
it('should not contain the hardcoded Google CrUX API key in final bundle/bundled/ directory if exists', () => {
61+
const finalBundleMcpPath = path.resolve(
62+
__dirname,
63+
'../../../../bundle/bundled/chrome-devtools-mcp.mjs',
64+
);
65+
const finalThirdPartyPath = path.resolve(
66+
__dirname,
67+
'../../../../bundle/bundled/third_party/index.js',
68+
);
69+
70+
if (fs.existsSync(finalBundleMcpPath)) {
71+
const content = fs.readFileSync(finalBundleMcpPath, 'utf8');
72+
expect(content).not.toContain(LEAKED_KEY);
73+
}
74+
75+
if (fs.existsSync(finalThirdPartyPath)) {
76+
const content = fs.readFileSync(finalThirdPartyPath, 'utf8');
77+
expect(content).not.toContain(LEAKED_KEY);
78+
}
79+
});
80+
});

0 commit comments

Comments
 (0)