Fix shell injection — use tee with stdin instead of echo interpolation
This commit is contained in:
+24
-39
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user