diff --git a/src/agent-manager.ts b/src/agent-manager.ts index cf79e7d..1c724c3 100644 --- a/src/agent-manager.ts +++ b/src/agent-manager.ts @@ -10,6 +10,13 @@ import { import { join } from "node:path"; import { execFileSync } from "node:child_process"; import { CONFIG } from "./config.js"; + +const SSH_OPTS = [ + "-o", "StrictHostKeyChecking=no", + "-o", "UserKnownHostsFile=/dev/null", + "-o", "ConnectTimeout=5", + "-i", CONFIG.sshKeyPath, +]; import { allocateIp, releaseIp, @@ -103,7 +110,7 @@ function injectAgentConfig( { stdio: "pipe" } ); - // Write config + // Write config (via stdin to avoid shell injection) const configJson = JSON.stringify({ nick: config.nick, model: config.model, @@ -112,26 +119,18 @@ function injectAgentConfig( port: 6667, ollama_url: "http://172.16.0.1:11434", }); - execFileSync( - "sudo", - [ - "bash", - "-c", - `echo '${configJson}' > ${join(mountPoint, "etc/agent/config.json")}`, - ], - { stdio: "pipe" } - ); + const configPath = join(mountPoint, "etc/agent/config.json"); + execFileSync("sudo", ["tee", configPath], { + input: configJson, + stdio: ["pipe", "pipe", "pipe"], + }); - // Write persona - execFileSync( - "sudo", - [ - "bash", - "-c", - `cat > ${join(mountPoint, "etc/agent/persona.md")} << 'PERSONA_EOF'\n${persona}\nPERSONA_EOF`, - ], - { stdio: "pipe" } - ); + // Write persona (via stdin to avoid shell injection) + const personaPath = join(mountPoint, "etc/agent/persona.md"); + execFileSync("sudo", ["tee", personaPath], { + input: persona, + stdio: ["pipe", "pipe", "pipe"], + }); // Inject SSH key for debugging access execFileSync("sudo", ["mkdir", "-p", join(mountPoint, "root/.ssh")], { @@ -301,14 +300,7 @@ export async function stopAgent(name: string) { try { execFileSync( "ssh", - [ - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "ConnectTimeout=3", - "-i", CONFIG.sshKeyPath, - `root@${info.ip}`, - "killall python3 2>/dev/null; sleep 1", - ], + [...SSH_OPTS, `root@${info.ip}`, "killall python3 2>/dev/null; sleep 1"], { stdio: "pipe", timeout: 5_000 } ); } catch { @@ -405,13 +397,6 @@ export async function reloadAgent( } if (updates.trigger) configUpdates.trigger = updates.trigger; - // Write updated config as a temp file on the VM via SSH - const sshOpts = [ - "-o", "StrictHostKeyChecking=no", - "-o", "UserKnownHostsFile=/dev/null", - "-o", "ConnectTimeout=5", - "-i", CONFIG.sshKeyPath, - ]; const sshTarget = `root@${info.ip}`; try { @@ -419,7 +404,7 @@ export async function reloadAgent( // Read current config from VM const currentRaw = execFileSync( "ssh", - [...sshOpts, sshTarget, "cat /etc/agent/config.json"], + [...SSH_OPTS, sshTarget, "cat /etc/agent/config.json"], { encoding: "utf-8", timeout: 10_000 } ); const current = JSON.parse(currentRaw); @@ -429,7 +414,7 @@ export async function reloadAgent( // Write back via stdin execFileSync( "ssh", - [...sshOpts, sshTarget, `cat > /etc/agent/config.json`], + [...SSH_OPTS, sshTarget, `cat > /etc/agent/config.json`], { input: newConfig, timeout: 10_000 } ); } @@ -437,7 +422,7 @@ export async function reloadAgent( if (updates.persona) { execFileSync( "ssh", - [...sshOpts, sshTarget, `cat > /etc/agent/persona.md`], + [...SSH_OPTS, sshTarget, `cat > /etc/agent/persona.md`], { input: updates.persona, timeout: 10_000 } ); } @@ -445,7 +430,7 @@ export async function reloadAgent( // Signal agent to reload execFileSync( "ssh", - [...sshOpts, sshTarget, "killall -HUP python3"], + [...SSH_OPTS, sshTarget, "killall -HUP python3"], { stdio: "pipe", timeout: 10_000 } ); } catch (err) {