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
This commit is contained in:
Claude
2026-04-11 07:57:11 +00:00
parent d725d09831
commit 0956ea7d15
2 changed files with 252 additions and 15 deletions

View File

@@ -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 };
}
});
```

View File

@@ -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** 💙🔥❄️