Fix shell injection — use tee with stdin instead of echo interpolation

This commit is contained in:
2026-04-08 01:36:01 +00:00
parent d9b695d5a0
commit 74e79f2870
+24 -39
View File
@@ -10,6 +10,13 @@ import {
import { join } from "node:path"; import { join } from "node:path";
import { execFileSync } from "node:child_process"; import { execFileSync } from "node:child_process";
import { CONFIG } from "./config.js"; import { CONFIG } from "./config.js";
const SSH_OPTS = [
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "ConnectTimeout=5",
"-i", CONFIG.sshKeyPath,
];
import { import {
allocateIp, allocateIp,
releaseIp, releaseIp,
@@ -103,7 +110,7 @@ function injectAgentConfig(
{ stdio: "pipe" } { stdio: "pipe" }
); );
// Write config // Write config (via stdin to avoid shell injection)
const configJson = JSON.stringify({ const configJson = JSON.stringify({
nick: config.nick, nick: config.nick,
model: config.model, model: config.model,
@@ -112,26 +119,18 @@ function injectAgentConfig(
port: 6667, port: 6667,
ollama_url: "http://172.16.0.1:11434", ollama_url: "http://172.16.0.1:11434",
}); });
execFileSync( const configPath = join(mountPoint, "etc/agent/config.json");
"sudo", execFileSync("sudo", ["tee", configPath], {
[ input: configJson,
"bash", stdio: ["pipe", "pipe", "pipe"],
"-c", });
`echo '${configJson}' > ${join(mountPoint, "etc/agent/config.json")}`,
],
{ stdio: "pipe" }
);
// Write persona // Write persona (via stdin to avoid shell injection)
execFileSync( const personaPath = join(mountPoint, "etc/agent/persona.md");
"sudo", execFileSync("sudo", ["tee", personaPath], {
[ input: persona,
"bash", stdio: ["pipe", "pipe", "pipe"],
"-c", });
`cat > ${join(mountPoint, "etc/agent/persona.md")} << 'PERSONA_EOF'\n${persona}\nPERSONA_EOF`,
],
{ stdio: "pipe" }
);
// Inject SSH key for debugging access // Inject SSH key for debugging access
execFileSync("sudo", ["mkdir", "-p", join(mountPoint, "root/.ssh")], { execFileSync("sudo", ["mkdir", "-p", join(mountPoint, "root/.ssh")], {
@@ -301,14 +300,7 @@ export async function stopAgent(name: string) {
try { try {
execFileSync( execFileSync(
"ssh", "ssh",
[ [...SSH_OPTS, `root@${info.ip}`, "killall python3 2>/dev/null; sleep 1"],
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "ConnectTimeout=3",
"-i", CONFIG.sshKeyPath,
`root@${info.ip}`,
"killall python3 2>/dev/null; sleep 1",
],
{ stdio: "pipe", timeout: 5_000 } { stdio: "pipe", timeout: 5_000 }
); );
} catch { } catch {
@@ -405,13 +397,6 @@ export async function reloadAgent(
} }
if (updates.trigger) configUpdates.trigger = updates.trigger; 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}`; const sshTarget = `root@${info.ip}`;
try { try {
@@ -419,7 +404,7 @@ export async function reloadAgent(
// Read current config from VM // Read current config from VM
const currentRaw = execFileSync( const currentRaw = execFileSync(
"ssh", "ssh",
[...sshOpts, sshTarget, "cat /etc/agent/config.json"], [...SSH_OPTS, sshTarget, "cat /etc/agent/config.json"],
{ encoding: "utf-8", timeout: 10_000 } { encoding: "utf-8", timeout: 10_000 }
); );
const current = JSON.parse(currentRaw); const current = JSON.parse(currentRaw);
@@ -429,7 +414,7 @@ export async function reloadAgent(
// Write back via stdin // Write back via stdin
execFileSync( execFileSync(
"ssh", "ssh",
[...sshOpts, sshTarget, `cat > /etc/agent/config.json`], [...SSH_OPTS, sshTarget, `cat > /etc/agent/config.json`],
{ input: newConfig, timeout: 10_000 } { input: newConfig, timeout: 10_000 }
); );
} }
@@ -437,7 +422,7 @@ export async function reloadAgent(
if (updates.persona) { if (updates.persona) {
execFileSync( execFileSync(
"ssh", "ssh",
[...sshOpts, sshTarget, `cat > /etc/agent/persona.md`], [...SSH_OPTS, sshTarget, `cat > /etc/agent/persona.md`],
{ input: updates.persona, timeout: 10_000 } { input: updates.persona, timeout: 10_000 }
); );
} }
@@ -445,7 +430,7 @@ export async function reloadAgent(
// Signal agent to reload // Signal agent to reload
execFileSync( execFileSync(
"ssh", "ssh",
[...sshOpts, sshTarget, "killall -HUP python3"], [...SSH_OPTS, sshTarget, "killall -HUP python3"],
{ stdio: "pipe", timeout: 10_000 } { stdio: "pipe", timeout: 10_000 }
); );
} catch (err) { } catch (err) {