From 60248f9fcaf710dba1a15aaae5e8af7918548db4 Mon Sep 17 00:00:00 2001 From: Ted Werbel Date: Sat, 1 Mar 2025 21:11:14 -0500 Subject: [PATCH] Add server identity verification to prevent connecting to incorrect web servers --- browser-tools-server/browser-connector.ts | 10 + chrome-extension/background.js | 216 ++++++++++++++-------- chrome-extension/devtools.js | 53 +++++- chrome-extension/panel.js | 38 +++- 4 files changed, 225 insertions(+), 92 deletions(-) diff --git a/browser-tools-server/browser-connector.ts b/browser-tools-server/browser-connector.ts index 9377c52..eae8a7b 100644 --- a/browser-tools-server/browser-connector.ts +++ b/browser-tools-server/browser-connector.ts @@ -418,6 +418,16 @@ app.get("/.port", (req, res) => { res.send(PORT.toString()); }); +// Add new identity endpoint with a unique signature +app.get("/.identity", (req, res) => { + res.json({ + port: PORT, + name: "browser-tools-server", + version: "1.1.0", + signature: "mcp-browser-connector-24x7", + }); +}); + // Add function to clear all logs function clearAllLogs() { console.log("Wiping all logs..."); diff --git a/chrome-extension/background.js b/chrome-extension/background.js index 3c7ed79..068b09c 100644 --- a/chrome-extension/background.js +++ b/chrome-extension/background.js @@ -8,94 +8,150 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { serverPort: 3025, }; - // Get the inspected window's tab - chrome.tabs.get(message.tabId, (tab) => { - if (chrome.runtime.lastError) { - console.error("Error getting tab:", chrome.runtime.lastError); - sendResponse({ - success: false, - error: chrome.runtime.lastError.message, - }); - return; - } - - // Get all windows to find the one containing our tab - chrome.windows.getAll({ populate: true }, (windows) => { - const targetWindow = windows.find((w) => - w.tabs.some((t) => t.id === message.tabId) - ); - - if (!targetWindow) { - console.error("Could not find window containing the inspected tab"); + // Validate server identity first + validateServerIdentity(settings.serverHost, settings.serverPort) + .then((isValid) => { + if (!isValid) { + console.error( + "Cannot capture screenshot: Not connected to a valid browser tools server" + ); sendResponse({ success: false, - error: "Could not find window containing the inspected tab", + error: + "Not connected to a valid browser tools server. Please check your connection settings.", }); return; } - // Capture screenshot of the window containing our tab - chrome.tabs.captureVisibleTab( - targetWindow.id, - { format: "png" }, - (dataUrl) => { - // Ignore DevTools panel capture error if it occurs - if ( - chrome.runtime.lastError && - !chrome.runtime.lastError.message.includes("devtools://") - ) { - console.error( - "Error capturing screenshot:", - chrome.runtime.lastError - ); - sendResponse({ - success: false, - error: chrome.runtime.lastError.message, - }); - return; - } - - // Send screenshot data to browser connector using configured settings - const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/screenshot`; - console.log(`Sending screenshot to ${serverUrl}`); - - fetch(serverUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - data: dataUrl, - path: message.screenshotPath, - }), - }) - .then((response) => response.json()) - .then((result) => { - if (result.error) { - console.error("Error from server:", result.error); - sendResponse({ success: false, error: result.error }); - } else { - console.log("Screenshot saved successfully:", result.path); - // Send success response even if DevTools capture failed - sendResponse({ - success: true, - path: result.path, - title: tab.title || "Current Tab", - }); - } - }) - .catch((error) => { - console.error("Error sending screenshot data:", error); - sendResponse({ - success: false, - error: error.message || "Failed to save screenshot", - }); - }); - } - ); + // Continue with screenshot capture + captureAndSendScreenshot(message, settings, sendResponse); + }) + .catch((error) => { + console.error("Error validating server:", error); + sendResponse({ + success: false, + error: "Failed to validate server identity: " + error.message, + }); }); - }); }); return true; // Required to use sendResponse asynchronously } }); + +// Validate server identity +async function validateServerIdentity(host, port) { + try { + const response = await fetch(`http://${host}:${port}/.identity`, { + signal: AbortSignal.timeout(3000), // 3 second timeout + }); + + if (!response.ok) { + console.error(`Invalid server response: ${response.status}`); + return false; + } + + const identity = await response.json(); + + // Validate the server signature + if (identity.signature !== "mcp-browser-connector-24x7") { + console.error("Invalid server signature - not the browser tools server"); + return false; + } + + return true; + } catch (error) { + console.error("Error validating server identity:", error); + return false; + } +} + +// Function to capture and send screenshot +function captureAndSendScreenshot(message, settings, sendResponse) { + // Get the inspected window's tab + chrome.tabs.get(message.tabId, (tab) => { + if (chrome.runtime.lastError) { + console.error("Error getting tab:", chrome.runtime.lastError); + sendResponse({ + success: false, + error: chrome.runtime.lastError.message, + }); + return; + } + + // Get all windows to find the one containing our tab + chrome.windows.getAll({ populate: true }, (windows) => { + const targetWindow = windows.find((w) => + w.tabs.some((t) => t.id === message.tabId) + ); + + if (!targetWindow) { + console.error("Could not find window containing the inspected tab"); + sendResponse({ + success: false, + error: "Could not find window containing the inspected tab", + }); + return; + } + + // Capture screenshot of the window containing our tab + chrome.tabs.captureVisibleTab( + targetWindow.id, + { format: "png" }, + (dataUrl) => { + // Ignore DevTools panel capture error if it occurs + if ( + chrome.runtime.lastError && + !chrome.runtime.lastError.message.includes("devtools://") + ) { + console.error( + "Error capturing screenshot:", + chrome.runtime.lastError + ); + sendResponse({ + success: false, + error: chrome.runtime.lastError.message, + }); + return; + } + + // Send screenshot data to browser connector using configured settings + const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/screenshot`; + console.log(`Sending screenshot to ${serverUrl}`); + + fetch(serverUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + data: dataUrl, + path: message.screenshotPath, + }), + }) + .then((response) => response.json()) + .then((result) => { + if (result.error) { + console.error("Error from server:", result.error); + sendResponse({ success: false, error: result.error }); + } else { + console.log("Screenshot saved successfully:", result.path); + // Send success response even if DevTools capture failed + sendResponse({ + success: true, + path: result.path, + title: tab.title || "Current Tab", + }); + } + }) + .catch((error) => { + console.error("Error sending screenshot data:", error); + sendResponse({ + success: false, + error: error.message || "Failed to save screenshot", + }); + }); + } + ); + }); + }); +} diff --git a/chrome-extension/devtools.js b/chrome-extension/devtools.js index 1ff3a02..ef16e64 100644 --- a/chrome-extension/devtools.js +++ b/chrome-extension/devtools.js @@ -173,12 +173,20 @@ function processJsonString(jsonString, maxLength) { } // Helper to send logs to browser-connector -function sendToBrowserConnector(logData) { +async function sendToBrowserConnector(logData) { if (!logData) { console.error("No log data provided to sendToBrowserConnector"); return; } + // First, ensure we're connecting to the right server + if (!(await validateServerIdentity())) { + console.error( + "Cannot send logs: Not connected to a valid browser tools server" + ); + return; + } + console.log("Sending log data to browser connector:", { type: logData.type, timestamp: logData.timestamp, @@ -276,6 +284,37 @@ function sendToBrowserConnector(logData) { }); } +// Validate server identity before connecting +async function validateServerIdentity() { + try { + const serverUrl = `http://${settings.serverHost}:${settings.serverPort}/.identity`; + + // Check if the server is our browser-tools-server + const response = await fetch(serverUrl, { + signal: AbortSignal.timeout(2000), // 2 second timeout + }); + + if (!response.ok) { + console.error(`Invalid server response: ${response.status}`); + return false; + } + + const identity = await response.json(); + + // Validate the server signature + if (identity.signature !== "mcp-browser-connector-24x7") { + console.error("Invalid server signature - not the browser tools server"); + return false; + } + + // If reached here, the server is valid + return true; + } catch (error) { + console.error("Error validating server identity:", error); + return false; + } +} + // Function to clear logs on the server function wipeLogs() { console.log("Wiping all logs..."); @@ -553,11 +592,21 @@ let ws = null; let wsReconnectTimeout = null; const WS_RECONNECT_DELAY = 5000; // 5 seconds -function setupWebSocket() { +async function setupWebSocket() { if (ws) { ws.close(); } + // Validate server identity before connecting + if (!(await validateServerIdentity())) { + console.error( + "Cannot establish WebSocket: Not connected to a valid browser tools server" + ); + // Try again after delay + setTimeout(setupWebSocket, WS_RECONNECT_DELAY); + return; + } + const wsUrl = `ws://${settings.serverHost}:${settings.serverPort}/extension-ws`; console.log(`Connecting to WebSocket at ${wsUrl}`); diff --git a/chrome-extension/panel.js b/chrome-extension/panel.js index 913e480..cc1c935 100644 --- a/chrome-extension/panel.js +++ b/chrome-extension/panel.js @@ -139,19 +139,28 @@ async function testConnection(host, port) { statusText.textContent = "Testing connection..."; try { - const response = await fetch(`http://${host}:${port}/.port`, { + // Use the identity endpoint instead of .port for more reliable validation + const response = await fetch(`http://${host}:${port}/.identity`, { signal: AbortSignal.timeout(5000), // 5 second timeout }); if (response.ok) { - const responsePort = await response.text(); + const identity = await response.json(); + + // Verify this is actually our server by checking the signature + if (identity.signature !== "mcp-browser-connector-24x7") { + statusIcon.className = "status-indicator status-disconnected"; + statusText.textContent = `Connection failed: Found a server at ${host}:${port} but it's not the Browser Tools server`; + return; + } + statusIcon.className = "status-indicator status-connected"; - statusText.textContent = `Connected successfully to server at ${host}:${port}`; + statusText.textContent = `Connected successfully to ${identity.name} v${identity.version} at ${host}:${port}`; // Update settings if different port was discovered - if (parseInt(responsePort, 10) !== port) { - console.log(`Detected different port: ${responsePort}`); - settings.serverPort = parseInt(responsePort, 10); + if (parseInt(identity.port, 10) !== port) { + console.log(`Detected different port: ${identity.port}`); + settings.serverPort = parseInt(identity.port, 10); serverPortInput.value = settings.serverPort; saveSettings(); } @@ -218,24 +227,33 @@ discoverServerButton.addEventListener("click", async () => { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 1000); // 1 second timeout per attempt - const response = await fetch(`http://${host}:${port}/.port`, { + // Use identity endpoint instead of .port for more reliable server validation + const response = await fetch(`http://${host}:${port}/.identity`, { signal: controller.signal, }); clearTimeout(timeoutId); if (response.ok) { - const responsePort = await response.text(); + const identity = await response.json(); + + // Verify this is actually our server by checking the signature + if (identity.signature !== "mcp-browser-connector-24x7") { + console.log( + `Found a server at ${host}:${port} but it's not the Browser Tools server` + ); + continue; + } // Update settings with discovered server settings.serverHost = host; - settings.serverPort = parseInt(responsePort, 10); + settings.serverPort = parseInt(identity.port, 10); serverHostInput.value = settings.serverHost; serverPortInput.value = settings.serverPort; saveSettings(); statusIcon.className = "status-indicator status-connected"; - statusText.textContent = `Discovered server at ${host}:${responsePort}`; + statusText.textContent = `Discovered ${identity.name} v${identity.version} at ${host}:${identity.port}`; // Stop searching once found return;