-
Notifications
You must be signed in to change notification settings - Fork 0
[WIP] Enabling GPT-5 for all clients #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||||||||
| const express = require('express'); | ||||||||||||||||||||||
| const router = express.Router(); | ||||||||||||||||||||||
| const { exec } = require('child_process'); | ||||||||||||||||||||||
| const path = require('path'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // POST /api/mcp/install | ||||||||||||||||||||||
| router.post('/install', (req, res) => { | ||||||||||||||||||||||
| const mcpServerDir = path.resolve(__dirname, '../../mcp-server'); | ||||||||||||||||||||||
| const installScript = path.join(mcpServerDir, 'install.js'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| console.log(`Executing MCP installation script: ${installScript}`); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
||||||||||||||||||||||
| // NOTE: install.js is an ES module. Requires Node.js v14+ and mcp-server/package.json to have "type": "module". | |
| // If you encounter issues, ensure your Node.js version supports ES modules. |
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Command injection vulnerability: the installScript path is constructed using user-controlled input indirectly through __dirname. While path.join mitigates directory traversal, using template string interpolation with exec is unsafe. Use execFile instead with the script path as the first argument to prevent shell injection, or at minimum use an array of arguments with spawn.
Copilot
AI
Nov 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Information disclosure: returning raw stdout and stderr from the installation script in the response could expose sensitive system information (e.g., file paths, environment variables, database connection strings). Consider returning a generic success message or sanitizing the output before sending it to the client.
| return res.status(500).json({ error: `Script execution failed: ${stderr || error.message}` }); | |
| } | |
| console.log(`install.js stdout: ${stdout}`); | |
| res.json({ message: `Installation script finished: ${stdout}` }); | |
| return res.status(500).json({ error: "Script execution failed." }); | |
| } | |
| console.log(`install.js stdout: ${stdout}`); | |
| res.json({ message: "Installation script finished successfully." }); |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,16 +13,99 @@ let extractorState = { | |||||||||
| droppedMessages: 0, | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const DEFAULT_API_URL = 'http://localhost:5000/api'; | ||||||||||
|
|
||||||||||
| // Configuration | ||||||||||
| let config = { | ||||||||||
| apiUrl: 'http://localhost:5000/api', | ||||||||||
| apiUrl: DEFAULT_API_URL, | ||||||||||
| apiKey: '', | ||||||||||
| syncInterval: 60000, // 1 minute | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| function sanitizeApiUrl(url) { | ||||||||||
| if (!url || typeof url !== 'string') { | ||||||||||
| return ''; | ||||||||||
| } | ||||||||||
| return url.trim().replace(/\/+$/, ''); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| function getBatchEndpoint(customUrl) { | ||||||||||
| const base = sanitizeApiUrl(customUrl) || config.apiUrl || DEFAULT_API_URL; | ||||||||||
| return `${base}/messages/batch`; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| async function handleBatchSendRequest(payload = {}) { | ||||||||||
| const { messages, extractionId, metadata, apiUrl, apiKey } = payload; | ||||||||||
|
|
||||||||||
| if (!Array.isArray(messages) || messages.length === 0) { | ||||||||||
| throw new Error('No messages to send'); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (!extractionId) { | ||||||||||
| throw new Error('Missing extractionId for batch'); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const endpoint = getBatchEndpoint(apiUrl); | ||||||||||
| const headers = { | ||||||||||
| 'Content-Type': 'application/json', | ||||||||||
| }; | ||||||||||
| const resolvedApiKey = apiKey || config.apiKey; | ||||||||||
| if (resolvedApiKey) { | ||||||||||
| headers['X-API-Key'] = resolvedApiKey; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| let response; | ||||||||||
| try { | ||||||||||
| response = await fetch(endpoint, { | ||||||||||
| method: 'POST', | ||||||||||
| headers, | ||||||||||
| body: JSON.stringify({ | ||||||||||
| messages, | ||||||||||
| extractionId, | ||||||||||
| metadata: metadata || {}, | ||||||||||
| }), | ||||||||||
| }); | ||||||||||
| } catch (error) { | ||||||||||
| const networkError = new Error(error?.message || 'Failed to reach backend'); | ||||||||||
| networkError.isNetworkError = true; | ||||||||||
| throw networkError; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (!response.ok) { | ||||||||||
| const text = await response.text(); | ||||||||||
| const err = new Error(`HTTP ${response.status}: ${response.statusText}`); | ||||||||||
| err.status = response.status; | ||||||||||
| err.details = text.slice(0, 500); | ||||||||||
| throw err; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| return response.json(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Load config from storage | ||||||||||
| chrome.storage.sync.get(['apiUrl', 'syncInterval'], (result) => { | ||||||||||
| if (result.apiUrl) config.apiUrl = result.apiUrl; | ||||||||||
| if (result.syncInterval) config.syncInterval = result.syncInterval; | ||||||||||
| chrome.storage.sync.get(['apiUrl', 'syncInterval', 'apiKey'], (result) => { | ||||||||||
| if (result.apiUrl) config.apiUrl = sanitizeApiUrl(result.apiUrl); | ||||||||||
| if (result.apiKey) config.apiKey = result.apiKey; | ||||||||||
| if (result.syncInterval) config.syncInterval = Number(result.syncInterval); | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| chrome.storage.onChanged.addListener((changes, area) => { | ||||||||||
| if (area !== 'sync') { | ||||||||||
| return; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (changes.apiUrl) { | ||||||||||
| config.apiUrl = sanitizeApiUrl(changes.apiUrl.newValue) || DEFAULT_API_URL; | ||||||||||
| } | ||||||||||
| if (changes.apiKey) { | ||||||||||
| config.apiKey = changes.apiKey.newValue || ''; | ||||||||||
| } | ||||||||||
| if (changes.syncInterval) { | ||||||||||
| const value = Number(changes.syncInterval.newValue); | ||||||||||
| if (!Number.isNaN(value)) { | ||||||||||
| config.syncInterval = value; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| /** | ||||||||||
|
|
@@ -31,67 +114,98 @@ chrome.storage.sync.get(['apiUrl', 'syncInterval'], (result) => { | |||||||||
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||||||||||
| console.log('Background received message:', message.type); | ||||||||||
|
|
||||||||||
| // Use a single listener with a switch statement | ||||||||||
| switch (message.type) { | ||||||||||
| case 'EXTRACTOR_READY': | ||||||||||
| extractorState.isActive = true; | ||||||||||
| extractorState.lastSync = new Date().toISOString(); | ||||||||||
| updateBadge('✓', 'green'); | ||||||||||
| console.log('Extractor is ready and active'); | ||||||||||
| sendResponse({ success: true }); | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| case 'SEND_BATCH': | ||||||||||
| handleBatchSendRequest(message.payload || {}) | ||||||||||
| .then((result) => { | ||||||||||
| sendResponse({ success: true, result }); | ||||||||||
| }) | ||||||||||
| .catch((error) => { | ||||||||||
| sendResponse({ | ||||||||||
| success: false, | ||||||||||
| error: error.message, | ||||||||||
| status: error.status || null, | ||||||||||
| details: error.details || null, | ||||||||||
| network: Boolean(error.isNetworkError) | ||||||||||
| }); | ||||||||||
| }); | ||||||||||
| return true; // Indicates that the response is sent asynchronously | ||||||||||
|
|
||||||||||
| case 'MESSAGES_SENT': | ||||||||||
| extractorState.totalMessages += message.count; | ||||||||||
| extractorState.lastSync = new Date().toISOString(); | ||||||||||
| extractorState.lastError = null; // Clear error on success | ||||||||||
| extractorState.retrying = false; | ||||||||||
| updateBadge(extractorState.totalMessages.toString(), 'blue'); | ||||||||||
| console.log(`✅ Sent ${message.count} messages. Total: ${extractorState.totalMessages}`); | ||||||||||
| sendResponse({ success: true }); | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| case 'EXTRACTION_ERROR': | ||||||||||
| extractorState.lastError = { | ||||||||||
| timestamp: new Date().toISOString(), | ||||||||||
| error: message.error, | ||||||||||
| retrying: message.retrying || false, | ||||||||||
| retryCount: message.retryCount || 0 | ||||||||||
| }; | ||||||||||
| extractorState.retrying = message.retrying || false; | ||||||||||
|
|
||||||||||
| // Track dropped messages | ||||||||||
| if (message.dropped) { | ||||||||||
| extractorState.droppedMessages += message.dropped; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Only add to errors array if it's a permanent failure | ||||||||||
| if (!message.retrying) { | ||||||||||
| extractorState.errors.push({ | ||||||||||
| timestamp: new Date().toISOString(), | ||||||||||
| error: message.error | ||||||||||
| }); | ||||||||||
| // Keep only last 10 errors | ||||||||||
| if (extractorState.errors.length > 10) { | ||||||||||
| extractorState.errors = extractorState.errors.slice(-10); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // Update badge based on retry status | ||||||||||
| if (message.retrying) { | ||||||||||
| updateBadge('⏰', 'orange'); | ||||||||||
| } else { | ||||||||||
| updateBadge('!', 'red'); | ||||||||||
| } | ||||||||||
| console.error('Extraction error:', message); | ||||||||||
| handleExtractionError(message); | ||||||||||
| sendResponse({ success: true }); | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| case 'GET_STATE': | ||||||||||
| case 'GET_STATUS': | ||||||||||
| sendResponse(extractorState); | ||||||||||
| return true; | ||||||||||
| } | ||||||||||
| break; | ||||||||||
|
|
||||||||||
| sendResponse({ success: true }); | ||||||||||
| return true; | ||||||||||
| default: | ||||||||||
| console.warn('Unknown message type:', message.type); | ||||||||||
| sendResponse({ success: false, error: 'Unknown message type' }); | ||||||||||
| break; | ||||||||||
| } | ||||||||||
| // Return true for async sendResponse, but not for sync cases | ||||||||||
| if (message.type !== 'SEND_BATCH') { | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
Comment on lines
+168
to
+171
|
||||||||||
| // Return true for async sendResponse, but not for sync cases | |
| if (message.type !== 'SEND_BATCH') { | |
| return false; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing authentication/authorization check: the
/api/mcp/installendpoint executes system commands without verifying the requester's identity or permissions. This endpoint should require authentication (e.g., theX-API-Keyheader that's now supported in the Chrome extension) to prevent unauthorized installation attempts.