Skip to content

Commit 60248f9

Browse files
committed
Add server identity verification to prevent connecting to incorrect web servers
1 parent a71e00d commit 60248f9

File tree

4 files changed

+221
-88
lines changed

4 files changed

+221
-88
lines changed

browser-tools-server/browser-connector.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,16 @@ app.get("/.port", (req, res) => {
418418
res.send(PORT.toString());
419419
});
420420

421+
// Add new identity endpoint with a unique signature
422+
app.get("/.identity", (req, res) => {
423+
res.json({
424+
port: PORT,
425+
name: "browser-tools-server",
426+
version: "1.1.0",
427+
signature: "mcp-browser-connector-24x7",
428+
});
429+
});
430+
421431
// Add function to clear all logs
422432
function clearAllLogs() {
423433
console.log("Wiping all logs...");

chrome-extension/background.js

Lines changed: 132 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,94 +8,150 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
88
serverPort: 3025,
99
};
1010

11-
// Get the inspected window's tab
12-
chrome.tabs.get(message.tabId, (tab) => {
13-
if (chrome.runtime.lastError) {
14-
console.error("Error getting tab:", chrome.runtime.lastError);
11+
// Validate server identity first
12+
validateServerIdentity(settings.serverHost, settings.serverPort)
13+
.then((isValid) => {
14+
if (!isValid) {
15+
console.error(
16+
"Cannot capture screenshot: Not connected to a valid browser tools server"
17+
);
18+
sendResponse({
19+
success: false,
20+
error:
21+
"Not connected to a valid browser tools server. Please check your connection settings.",
22+
});
23+
return;
24+
}
25+
26+
// Continue with screenshot capture
27+
captureAndSendScreenshot(message, settings, sendResponse);
28+
})
29+
.catch((error) => {
30+
console.error("Error validating server:", error);
1531
sendResponse({
1632
success: false,
17-
error: chrome.runtime.lastError.message,
33+
error: "Failed to validate server identity: " + error.message,
1834
});
19-
return;
20-
}
35+
});
36+
});
37+
return true; // Required to use sendResponse asynchronously
38+
}
39+
});
40+
41+
// Validate server identity
42+
async function validateServerIdentity(host, port) {
43+
try {
44+
const response = await fetch(`http://${host}:${port}/.identity`, {
45+
signal: AbortSignal.timeout(3000), // 3 second timeout
46+
});
47+
48+
if (!response.ok) {
49+
console.error(`Invalid server response: ${response.status}`);
50+
return false;
51+
}
52+
53+
const identity = await response.json();
54+
55+
// Validate the server signature
56+
if (identity.signature !== "mcp-browser-connector-24x7") {
57+
console.error("Invalid server signature - not the browser tools server");
58+
return false;
59+
}
2160

22-
// Get all windows to find the one containing our tab
23-
chrome.windows.getAll({ populate: true }, (windows) => {
24-
const targetWindow = windows.find((w) =>
25-
w.tabs.some((t) => t.id === message.tabId)
26-
);
61+
return true;
62+
} catch (error) {
63+
console.error("Error validating server identity:", error);
64+
return false;
65+
}
66+
}
67+
68+
// Function to capture and send screenshot
69+
function captureAndSendScreenshot(message, settings, sendResponse) {
70+
// Get the inspected window's tab
71+
chrome.tabs.get(message.tabId, (tab) => {
72+
if (chrome.runtime.lastError) {
73+
console.error("Error getting tab:", chrome.runtime.lastError);
74+
sendResponse({
75+
success: false,
76+
error: chrome.runtime.lastError.message,
77+
});
78+
return;
79+
}
2780

28-
if (!targetWindow) {
29-
console.error("Could not find window containing the inspected tab");
81+
// Get all windows to find the one containing our tab
82+
chrome.windows.getAll({ populate: true }, (windows) => {
83+
const targetWindow = windows.find((w) =>
84+
w.tabs.some((t) => t.id === message.tabId)
85+
);
86+
87+
if (!targetWindow) {
88+
console.error("Could not find window containing the inspected tab");
89+
sendResponse({
90+
success: false,
91+
error: "Could not find window containing the inspected tab",
92+
});
93+
return;
94+
}
95+
96+
// Capture screenshot of the window containing our tab
97+
chrome.tabs.captureVisibleTab(
98+
targetWindow.id,
99+
{ format: "png" },
100+
(dataUrl) => {
101+
// Ignore DevTools panel capture error if it occurs
102+
if (
103+
chrome.runtime.lastError &&
104+
!chrome.runtime.lastError.message.includes("devtools://")
105+
) {
106+
console.error(
107+
"Error capturing screenshot:",
108+
chrome.runtime.lastError
109+
);
30110
sendResponse({
31111
success: false,
32-
error: "Could not find window containing the inspected tab",
112+
error: chrome.runtime.lastError.message,
33113
});
34114
return;
35115
}
36116

37-
// Capture screenshot of the window containing our tab
38-
chrome.tabs.captureVisibleTab(
39-
targetWindow.id,
40-
{ format: "png" },
41-
(dataUrl) => {
42-
// Ignore DevTools panel capture error if it occurs
43-
if (
44-
chrome.runtime.lastError &&
45-
!chrome.runtime.lastError.message.includes("devtools://")
46-
) {
47-
console.error(
48-
"Error capturing screenshot:",
49-
chrome.runtime.lastError
50-
);
117+
// Send screenshot data to browser connector using configured settings
118+
const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/screenshot`;
119+
console.log(`Sending screenshot to ${serverUrl}`);
120+
121+
fetch(serverUrl, {
122+
method: "POST",
123+
headers: {
124+
"Content-Type": "application/json",
125+
},
126+
body: JSON.stringify({
127+
data: dataUrl,
128+
path: message.screenshotPath,
129+
}),
130+
})
131+
.then((response) => response.json())
132+
.then((result) => {
133+
if (result.error) {
134+
console.error("Error from server:", result.error);
135+
sendResponse({ success: false, error: result.error });
136+
} else {
137+
console.log("Screenshot saved successfully:", result.path);
138+
// Send success response even if DevTools capture failed
51139
sendResponse({
52-
success: false,
53-
error: chrome.runtime.lastError.message,
140+
success: true,
141+
path: result.path,
142+
title: tab.title || "Current Tab",
54143
});
55-
return;
56144
}
57-
58-
// Send screenshot data to browser connector using configured settings
59-
const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/screenshot`;
60-
console.log(`Sending screenshot to ${serverUrl}`);
61-
62-
fetch(serverUrl, {
63-
method: "POST",
64-
headers: {
65-
"Content-Type": "application/json",
66-
},
67-
body: JSON.stringify({
68-
data: dataUrl,
69-
path: message.screenshotPath,
70-
}),
71-
})
72-
.then((response) => response.json())
73-
.then((result) => {
74-
if (result.error) {
75-
console.error("Error from server:", result.error);
76-
sendResponse({ success: false, error: result.error });
77-
} else {
78-
console.log("Screenshot saved successfully:", result.path);
79-
// Send success response even if DevTools capture failed
80-
sendResponse({
81-
success: true,
82-
path: result.path,
83-
title: tab.title || "Current Tab",
84-
});
85-
}
86-
})
87-
.catch((error) => {
88-
console.error("Error sending screenshot data:", error);
89-
sendResponse({
90-
success: false,
91-
error: error.message || "Failed to save screenshot",
92-
});
93-
});
94-
}
95-
);
96-
});
97-
});
145+
})
146+
.catch((error) => {
147+
console.error("Error sending screenshot data:", error);
148+
sendResponse({
149+
success: false,
150+
error: error.message || "Failed to save screenshot",
151+
});
152+
});
153+
}
154+
);
98155
});
99-
return true; // Required to use sendResponse asynchronously
100-
}
101-
});
156+
});
157+
}

chrome-extension/devtools.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,20 @@ function processJsonString(jsonString, maxLength) {
173173
}
174174

175175
// Helper to send logs to browser-connector
176-
function sendToBrowserConnector(logData) {
176+
async function sendToBrowserConnector(logData) {
177177
if (!logData) {
178178
console.error("No log data provided to sendToBrowserConnector");
179179
return;
180180
}
181181

182+
// First, ensure we're connecting to the right server
183+
if (!(await validateServerIdentity())) {
184+
console.error(
185+
"Cannot send logs: Not connected to a valid browser tools server"
186+
);
187+
return;
188+
}
189+
182190
console.log("Sending log data to browser connector:", {
183191
type: logData.type,
184192
timestamp: logData.timestamp,
@@ -276,6 +284,37 @@ function sendToBrowserConnector(logData) {
276284
});
277285
}
278286

287+
// Validate server identity before connecting
288+
async function validateServerIdentity() {
289+
try {
290+
const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/.identity`;
291+
292+
// Check if the server is our browser-tools-server
293+
const response = await fetch(serverUrl, {
294+
signal: AbortSignal.timeout(2000), // 2 second timeout
295+
});
296+
297+
if (!response.ok) {
298+
console.error(`Invalid server response: ${response.status}`);
299+
return false;
300+
}
301+
302+
const identity = await response.json();
303+
304+
// Validate the server signature
305+
if (identity.signature !== "mcp-browser-connector-24x7") {
306+
console.error("Invalid server signature - not the browser tools server");
307+
return false;
308+
}
309+
310+
// If reached here, the server is valid
311+
return true;
312+
} catch (error) {
313+
console.error("Error validating server identity:", error);
314+
return false;
315+
}
316+
}
317+
279318
// Function to clear logs on the server
280319
function wipeLogs() {
281320
console.log("Wiping all logs...");
@@ -553,11 +592,21 @@ let ws = null;
553592
let wsReconnectTimeout = null;
554593
const WS_RECONNECT_DELAY = 5000; // 5 seconds
555594

556-
function setupWebSocket() {
595+
async function setupWebSocket() {
557596
if (ws) {
558597
ws.close();
559598
}
560599

600+
// Validate server identity before connecting
601+
if (!(await validateServerIdentity())) {
602+
console.error(
603+
"Cannot establish WebSocket: Not connected to a valid browser tools server"
604+
);
605+
// Try again after delay
606+
setTimeout(setupWebSocket, WS_RECONNECT_DELAY);
607+
return;
608+
}
609+
561610
const wsUrl = `ws://${settings.serverHost}:${settings.serverPort}/extension-ws`;
562611
console.log(`Connecting to WebSocket at ${wsUrl}`);
563612

chrome-extension/panel.js

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,28 @@ async function testConnection(host, port) {
139139
statusText.textContent = "Testing connection...";
140140

141141
try {
142-
const response = await fetch(`http://${host}:${port}/.port`, {
142+
// Use the identity endpoint instead of .port for more reliable validation
143+
const response = await fetch(`http://${host}:${port}/.identity`, {
143144
signal: AbortSignal.timeout(5000), // 5 second timeout
144145
});
145146

146147
if (response.ok) {
147-
const responsePort = await response.text();
148+
const identity = await response.json();
149+
150+
// Verify this is actually our server by checking the signature
151+
if (identity.signature !== "mcp-browser-connector-24x7") {
152+
statusIcon.className = "status-indicator status-disconnected";
153+
statusText.textContent = `Connection failed: Found a server at ${host}:${port} but it's not the Browser Tools server`;
154+
return;
155+
}
156+
148157
statusIcon.className = "status-indicator status-connected";
149-
statusText.textContent = `Connected successfully to server at ${host}:${port}`;
158+
statusText.textContent = `Connected successfully to ${identity.name} v${identity.version} at ${host}:${port}`;
150159

151160
// Update settings if different port was discovered
152-
if (parseInt(responsePort, 10) !== port) {
153-
console.log(`Detected different port: ${responsePort}`);
154-
settings.serverPort = parseInt(responsePort, 10);
161+
if (parseInt(identity.port, 10) !== port) {
162+
console.log(`Detected different port: ${identity.port}`);
163+
settings.serverPort = parseInt(identity.port, 10);
155164
serverPortInput.value = settings.serverPort;
156165
saveSettings();
157166
}
@@ -218,24 +227,33 @@ discoverServerButton.addEventListener("click", async () => {
218227
const controller = new AbortController();
219228
const timeoutId = setTimeout(() => controller.abort(), 1000); // 1 second timeout per attempt
220229

221-
const response = await fetch(`http://${host}:${port}/.port`, {
230+
// Use identity endpoint instead of .port for more reliable server validation
231+
const response = await fetch(`http://${host}:${port}/.identity`, {
222232
signal: controller.signal,
223233
});
224234

225235
clearTimeout(timeoutId);
226236

227237
if (response.ok) {
228-
const responsePort = await response.text();
238+
const identity = await response.json();
239+
240+
// Verify this is actually our server by checking the signature
241+
if (identity.signature !== "mcp-browser-connector-24x7") {
242+
console.log(
243+
`Found a server at ${host}:${port} but it's not the Browser Tools server`
244+
);
245+
continue;
246+
}
229247

230248
// Update settings with discovered server
231249
settings.serverHost = host;
232-
settings.serverPort = parseInt(responsePort, 10);
250+
settings.serverPort = parseInt(identity.port, 10);
233251
serverHostInput.value = settings.serverHost;
234252
serverPortInput.value = settings.serverPort;
235253
saveSettings();
236254

237255
statusIcon.className = "status-indicator status-connected";
238-
statusText.textContent = `Discovered server at ${host}:${responsePort}`;
256+
statusText.textContent = `Discovered ${identity.name} v${identity.version} at ${host}:${identity.port}`;
239257

240258
// Stop searching once found
241259
return;

0 commit comments

Comments
 (0)