From 85b3f5b6e20dc1d1df46f562af955958f49e32e1 Mon Sep 17 00:00:00 2001 From: Agent Date: Fri, 13 Mar 2026 10:17:45 +0000 Subject: [PATCH] =?UTF-8?q?mcp-hub-007:=20Auth=20hardening=20=E2=80=94=20p?= =?UTF-8?q?er-service=20secrets=20and=20env-based=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ecosystem.config.js | 4 ++-- sample-mcp/index.js | 2 +- src/config.js | 25 ++++++++++++++++++++++++- src/ws-server.js | 11 +++++++++-- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/ecosystem.config.js b/ecosystem.config.js index 4febfd5..87ebd57 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -4,7 +4,7 @@ module.exports = { name: 'mcp-hub', script: 'src/index.js', cwd: '/workspace', - env: { NODE_ENV: 'production', PORT: 3000 }, + env: { NODE_ENV: 'development', PORT: 3000, HUB_AUTH: JSON.stringify({"sample-mcp": "changeme"}) }, max_restarts: 10, restart_delay: 1000, log_date_format: 'YYYY-MM-DD HH:mm:ss Z', @@ -14,7 +14,7 @@ module.exports = { name: 'sample-mcp', script: 'sample-mcp/index.js', cwd: '/workspace', - env: { NODE_ENV: 'production' }, + env: { NODE_ENV: 'development', MCP_SECRET: 'changeme' }, max_restarts: 10, restart_delay: 2000, log_date_format: 'YYYY-MM-DD HH:mm:ss Z', diff --git a/sample-mcp/index.js b/sample-mcp/index.js index 7eb3ef1..41c5aba 100644 --- a/sample-mcp/index.js +++ b/sample-mcp/index.js @@ -5,7 +5,7 @@ const WebSocket = require('ws'); const HUB_URL = process.env.HUB_URL || 'wss://mcp.arik.work/ws/register'; const HUB_URL_FALLBACK = 'ws://mcp.arik.work/ws/register'; const SERVICE_ID = 'sample-mcp'; -const SECRET = 'dev-secret'; +const SECRET = process.env.MCP_SECRET || 'dev-secret'; let reconnectDelay = 1000; let ws = null; diff --git a/src/config.js b/src/config.js index c34c9c5..ed90dc8 100644 --- a/src/config.js +++ b/src/config.js @@ -1,4 +1,27 @@ +const DEV_SECRET = 'dev-secret'; + +let serviceAuthMap = null; +if (process.env.HUB_AUTH) { + try { + serviceAuthMap = JSON.parse(process.env.HUB_AUTH); + } catch (e) { + console.error('[config] Failed to parse HUB_AUTH JSON:', e.message); + process.exit(1); + } +} else if (process.env.NODE_ENV === 'production') { + console.error('[config] HUB_AUTH must be set in production'); + process.exit(1); +} + +function getServiceSecret(serviceId) { + if (serviceAuthMap) { + return serviceAuthMap[serviceId] !== undefined ? serviceAuthMap[serviceId] : null; + } + // Dev fallback: accept dev-secret for any service + return DEV_SECRET; +} + module.exports = { PORT: parseInt(process.env.PORT, 10) || 3000, - HUB_SECRET: process.env.HUB_SECRET || 'dev-secret', + getServiceSecret, }; diff --git a/src/ws-server.js b/src/ws-server.js index e3847ef..ac610d1 100644 --- a/src/ws-server.js +++ b/src/ws-server.js @@ -19,7 +19,7 @@ function setupWsServer(httpServer) { }); }); - wss.on('connection', (ws) => { + wss.on('connection', (ws, req) => { let serviceId = null; let authenticated = false; let missedPongs = 0; @@ -34,7 +34,14 @@ function setupWsServer(httpServer) { return; } - if (msg.type !== 'register' || !msg.serviceId || msg.secret !== config.HUB_SECRET) { + if (msg.type !== 'register' || !msg.serviceId) { + ws.close(4001, 'unauthorized'); + return; + } + + const expectedSecret = config.getServiceSecret(msg.serviceId); + if (expectedSecret === null || msg.secret !== expectedSecret) { + console.log(`[ws] auth failed for serviceId=${msg.serviceId} from ${req.socket.remoteAddress}`); ws.close(4001, 'unauthorized'); return; }