From 0956ea7d15b131185466de04f87e36a3ca73636e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 11 Apr 2026 07:57:11 +0000 Subject: [PATCH] Add Task #112: Trinity Core Security Hardening Gemini identified 5 concerns: 1. Command injection (CRITICAL) - Fix: use spawn instead of exec 2. Race conditions (MEDIUM) - Future: server locking 3. SD card wear (HIGH) - FIXED: cron log rotation 4. Cloudflare Access (MEDIUM) - Future enhancement 5. System prompts (LOW) - Document for Catalyst/Orb Updated MCP implementation spec with secure spawn-based SSH execution. Task #112 is a BLOCKER for Task #111 (web MCP deployment). Chronicler #76 --- ...emini-mcp-web-implementation-2026-04-11.md | 81 ++++++-- .../task-112-trinity-core-security.md | 186 ++++++++++++++++++ 2 files changed, 252 insertions(+), 15 deletions(-) create mode 100644 docs/tasks-index/task-112-trinity-core-security.md diff --git a/docs/consultations/gemini-mcp-web-implementation-2026-04-11.md b/docs/consultations/gemini-mcp-web-implementation-2026-04-11.md index 7ff9c5a..59e576e 100644 --- a/docs/consultations/gemini-mcp-web-implementation-2026-04-11.md +++ b/docs/consultations/gemini-mcp-web-implementation-2026-04-11.md @@ -313,9 +313,67 @@ mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({ })); ``` -#### Block E: Execution Logic & SSH Wrapper +#### Block E: Execution Logic & SSH Wrapper (SECURE VERSION) + +**IMPORTANT:** Uses `spawn` instead of `exec` to prevent command injection attacks. ```javascript +import { spawn } from 'child_process'; + +function executeSSH(server, command) { + return new Promise((resolve) => { + const target = SERVERS[server]; + + log(`EXEC [${server}] ${command}`); + + // spawn with array args prevents shell injection + const ssh = spawn('ssh', [ + '-o', 'ConnectTimeout=10', + '-o', 'StrictHostKeyChecking=no', + `${target.user}@${target.host}`, + command + ]); + + let stdout = ''; + let stderr = ''; + + ssh.stdout.on('data', (data) => stdout += data.toString()); + ssh.stderr.on('data', (data) => stderr += data.toString()); + + ssh.on('close', (code) => { + const success = code === 0; + log(`RESULT [${server}] success=${success}`); + resolve({ + success, + stdout: stdout.trim(), + stderr: stderr.trim(), + error: success ? null : `Exit code ${code}` + }); + }); + + ssh.on('error', (err) => { + log(`ERROR [${server}] ${err.message}`); + resolve({ + success: false, + stdout: '', + stderr: err.message, + error: err.message + }); + }); + + // Timeout after 30 seconds + setTimeout(() => { + ssh.kill(); + resolve({ + success: false, + stdout: stdout.trim(), + stderr: 'Command timed out after 30 seconds', + error: 'Timeout' + }); + }, 30000); + }); +} + mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "list_servers") { const serverList = Object.keys(SERVERS).join(", "); @@ -330,20 +388,13 @@ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => { return { content: [{ type: "text", text: `Error: Unknown server ${server}` }], isError: true }; } - log(`EXEC [${server}] ${command}`); - const sshCmd = `ssh -o ConnectTimeout=10 ${target.user}@${target.host} "${command.replace(/"/g, '\\"')}"`; - - return new Promise((resolve) => { - exec(sshCmd, { timeout: 30000 }, (error, stdout, stderr) => { - const success = !error; - log(`RESULT [${server}] success=${success}`); - - let output = stdout.trim(); - if (error || stderr) output += `\nError/Stderr: ${stderr.trim()} ${error?.message || ''}`; - - resolve({ content: [{ type: "text", text: output }], isError: !success }); - }); - }); + const result = await executeSSH(server, command); + + let output = result.stdout || '(no output)'; + if (result.stderr) output += `\nSTDERR: ${result.stderr}`; + if (result.error && !result.success) output += `\nError: ${result.error}`; + + return { content: [{ type: "text", text: output }], isError: !result.success }; } }); ``` diff --git a/docs/tasks-index/task-112-trinity-core-security.md b/docs/tasks-index/task-112-trinity-core-security.md new file mode 100644 index 0000000..2b51c21 --- /dev/null +++ b/docs/tasks-index/task-112-trinity-core-security.md @@ -0,0 +1,186 @@ +--- +task_number: 112 +title: Trinity Core Security Hardening +status: Planned +priority: P1-High +is_blocker: true +owner: Michael +tags: + - trinity-core + - security + - infrastructure +estimated_hours: 3 +--- + +# Trinity Core Security Hardening + +Address security concerns identified by Gemini before deploying web MCP upgrade. + +## Why This is a Blocker + +Trinity Core provides root SSH access to 7 production servers. Before enabling multi-user access (Holly, Meg) or native Claude.ai integration, we must address these vulnerabilities. + +--- + +## Issue 1: Command Injection (CRITICAL) + +**The Problem:** +Current code: +```javascript +const sshCmd = `ssh -o ConnectTimeout=10 ${target.user}@${target.host} "${command.replace(/"/g, '\\"')}"`; +exec(sshCmd, ...); +``` + +This only escapes double quotes. Subshells `$(...)`, backticks `` `...` ``, and environment variables `$VAR` are NOT escaped. A prompt injection attack could execute arbitrary commands. + +**Example Attack:** +``` +Command: echo $(cat /etc/shadow) +Result: Dumps password hashes +``` + +**The Fix:** +Use `child_process.spawn` with array arguments instead of `exec` with string interpolation: + +```javascript +import { spawn } from 'child_process'; + +function executeCommand(server, command) { + return new Promise((resolve, reject) => { + const target = SERVERS[server]; + + // spawn prevents shell interpretation + const ssh = spawn('ssh', [ + '-o', 'ConnectTimeout=10', + `${target.user}@${target.host}`, + command // passed as single argument, not interpolated into shell + ]); + + let stdout = ''; + let stderr = ''; + + ssh.stdout.on('data', (data) => stdout += data); + ssh.stderr.on('data', (data) => stderr += data); + + ssh.on('close', (code) => { + resolve({ + success: code === 0, + stdout: stdout.trim(), + stderr: stderr.trim(), + code + }); + }); + + ssh.on('error', reject); + }); +} +``` + +**Status:** ❌ Not implemented — MUST FIX before web MCP deployment + +--- + +## Issue 2: AI Concurrency / Race Conditions (MEDIUM) + +**The Problem:** +If Chronicler and Catalyst both run commands on the same server simultaneously: +- Catalyst restarts Docker +- Chronicler deploys container +- Chronicler's command fails or corrupts deployment + +**The Fix:** +Add server locking to Arbiter: + +```sql +CREATE TABLE server_locks ( + server VARCHAR(50) PRIMARY KEY, + locked_by VARCHAR(50), + locked_at TIMESTAMP, + expires_at TIMESTAMP +); +``` + +Before executing, check lock. If locked, return: +*"tx1-dallas is currently locked by The Catalyst. Please try again in a moment."* + +**Status:** ⏳ Future enhancement — not critical for initial deployment + +--- + +## Issue 3: SD Card Wear & Log Exhaustion (HIGH) + +**The Problem:** +- Pi SD cards have finite write cycles +- `fs.appendFileSync` on every command wears the card +- No log rotation = disk fills up + +**The Fix (Implemented):** +Added cron job to rotate logs daily: +``` +0 0 * * * truncate -s 0 /home/claude_executor/mcp-server/command.log && echo "[$(date -Iseconds)] Log rotated" >> /home/claude_executor/mcp-server/command.log +``` + +**Better Long-term Fix:** +Send logs to Arbiter's PostgreSQL instead of local disk (Task #109 already covers this). + +**Status:** ✅ Mitigated with cron rotation + +--- + +## Issue 4: Cloudflare Access Layer (MEDIUM) + +**The Problem:** +`mcp.firefrostgaming.com` is public. Malicious requests hit the Pi even if rejected by auth. + +**The Fix:** +Enable Cloudflare Access (Zero Trust) for the subdomain: +1. Only allow requests from Anthropic IP ranges +2. Or require Cloudflare Service Token header +3. Drops malicious traffic at edge before reaching Pi + +**Status:** ⏳ Future enhancement — token auth is sufficient for now + +--- + +## Issue 5: System Prompt Alignment (LOW) + +**The Problem:** +AI partners won't know how to use Trinity Core unless told. + +**The Fix:** +Update system prompts / project instructions for: +- Chronicler: Already has Trinity Core context in project instructions +- Catalyst: Add Trinity Core section to Catalyst project +- The Orb: Add Trinity Core section to Orb project + +Include: +- Available servers +- How to run commands +- What requires approval +- What to do on timeout + +**Status:** ⏳ Document when Catalyst/Orb projects are created + +--- + +## Implementation Checklist + +**Before Web MCP Deployment (Task #111):** +- [ ] Fix command injection with `spawn` instead of `exec` +- [ ] Verify log rotation is working + +**After Initial Deployment:** +- [ ] Consider Cloudflare Access +- [ ] Consider server locking for concurrency +- [ ] Update Catalyst/Orb system prompts when created + +--- + +## Related Tasks + +- Task #109: MCP Logging in Trinity Console (moves logs off Pi) +- Task #111: Trinity Core Web MCP Connector (main implementation) + +--- + +**Fire + Frost + Foundation = Where Love Builds Legacy** 💙🔥❄️