@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
561 lines (499 loc) • 16.6 kB
text/typescript
/**
* Multiplexed remote SSH execution, worker orchestration, and secure config discovery.
*
* Implements:
* - Safe `~/.ssh/config` parsing and alias discovery without socket connections or credential leaks.
* - OpenSSH `ControlMaster` connection reuse (.kxm/run/ssh-sockets/%C) for low-latency command execution.
* - In-memory masked credential passing via `sshpass -e` (env) and `sudo -S -p ''` (stdin).
* - Safety seatbelt validation and pinned host key verification.
* - Output bounding and truncation (50KB / 2000 lines).
*/
import { spawnSync, type SpawnSyncOptionsWithStringEncoding } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { assertCommandSeatbelt, assertPinnedSshHostKeyPolicy } from "./safety-integrity.ts";
export const MAX_SSH_OUTPUT_BYTES = 50 * 1024; // 50KB
export const MAX_SSH_OUTPUT_LINES = 2000;
export const DEFAULT_SOCKET_DIR = ".kxm/run/ssh-sockets";
export const DEFAULT_CONTROL_PERSIST = "10m";
export interface SshHostInfo {
alias: string;
hostName?: string | undefined;
user?: string | undefined;
port?: number | undefined;
proxyJump?: string | undefined;
identityFiles?: string[] | undefined;
}
export interface SshRunParams {
action: "info" | "command" | "file";
host?: string | undefined;
command?: string | undefined;
sudo?: boolean | undefined;
file_path?: string | undefined;
file_content?: string | undefined;
file_op?: "write" | "read" | "append" | undefined;
reason?: string | undefined;
password?: string | undefined;
sudo_password?: string | undefined;
socket_dir?: string | undefined;
control_persist?: string | undefined;
timeout_ms?: number | undefined;
strict_host_key?: boolean | undefined;
execFn?: ((cmd: string, args: string[], options: SpawnSyncOptionsWithStringEncoding) => {
status: number | null;
stdout: string | Buffer;
stderr: string | Buffer;
error?: Error | undefined;
}) | undefined;
}
export interface SshExecutionReceipt {
ok: boolean;
action: "info" | "command" | "file";
host?: string | undefined;
resolvedHost?: string | undefined;
user?: string | undefined;
port?: number | undefined;
exitCode?: number | undefined;
stdout?: string | undefined;
stderr?: string | undefined;
durationMs?: number | undefined;
truncated?: boolean | undefined;
socketReused?: boolean | undefined;
fileProcessed?: string | undefined;
bytesProcessed?: number | undefined;
hosts?: SshHostInfo[] | undefined;
error?: string | undefined;
}
/**
* Truncates output to 50KB and 2000 lines max.
*/
export function truncateSshOutput(raw: string): { text: string; truncated: boolean } {
let text = raw;
let truncated = false;
if (Buffer.byteLength(text, "utf-8") > MAX_SSH_OUTPUT_BYTES) {
const buf = Buffer.from(text, "utf-8");
text = buf.subarray(0, MAX_SSH_OUTPUT_BYTES).toString("utf-8");
truncated = true;
}
const lines = text.split("\n");
if (lines.length > MAX_SSH_OUTPUT_LINES) {
text = lines.slice(0, MAX_SSH_OUTPUT_LINES).join("\n");
truncated = true;
}
if (truncated) {
text += `\n\n[kxm: ssh output truncated to ${MAX_SSH_OUTPUT_BYTES / 1024}KB / ${MAX_SSH_OUTPUT_LINES} lines]`;
}
return { text, truncated };
}
/**
* Parses ~/.ssh/config safely to list host aliases and their configurations.
* Does not read private key files or open network sockets.
*/
export function parseSshConfig(configPath?: string): SshHostInfo[] {
const targetPath = configPath ?? join(homedir(), ".ssh", "config");
if (!existsSync(targetPath)) {
return [];
}
try {
const content = readFileSync(targetPath, "utf-8");
const lines = content.split("\n");
const hosts: SshHostInfo[] = [];
let currentHosts: SshHostInfo[] = [];
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line || line.startsWith("#")) continue;
const spaceIdx = line.search(/\s/);
if (spaceIdx === -1) continue;
const key = line.slice(0, spaceIdx).trim().toLowerCase();
const value = line.slice(spaceIdx + 1).trim();
if (key === "host") {
// Space-separated host patterns; skip wildcard-only matches like "*"
const aliases = value.split(/\s+/).filter((a) => a !== "*");
currentHosts = [];
for (const alias of aliases) {
const entry: SshHostInfo = { alias, identityFiles: [] };
currentHosts.push(entry);
hosts.push(entry);
}
} else if (currentHosts.length > 0) {
for (const currentHost of currentHosts) {
if (key === "hostname") {
currentHost.hostName = value;
} else if (key === "user") {
currentHost.user = value;
} else if (key === "port") {
const p = Number.parseInt(value, 10);
if (!Number.isNaN(p)) currentHost.port = p;
} else if (key === "proxyjump") {
currentHost.proxyJump = value;
} else if (key === "identityfile") {
currentHost.identityFiles = currentHost.identityFiles ?? [];
currentHost.identityFiles.push(value);
}
}
}
}
return hosts;
} catch {
return [];
}
}
/**
* Resolves effective SSH parameters for a specific host using `ssh -G <host>`.
* Does not initiate any network connections.
*/
export function resolveSshHostG(
host: string,
execFn: typeof spawnSync = spawnSync,
): SshHostInfo {
try {
const result = execFn("ssh", ["-G", host], { encoding: "utf-8" });
if (result.status !== 0 || !result.stdout) {
return { alias: host, hostName: host };
}
const lines = String(result.stdout).split("\n");
const info: SshHostInfo = { alias: host, identityFiles: [] };
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line) continue;
const spaceIdx = line.indexOf(" ");
if (spaceIdx === -1) continue;
const key = line.slice(0, spaceIdx).toLowerCase();
const value = line.slice(spaceIdx + 1).trim();
if (key === "hostname") {
info.hostName = value;
} else if (key === "user") {
info.user = value;
} else if (key === "port") {
const p = Number.parseInt(value, 10);
if (!Number.isNaN(p)) info.port = p;
} else if (key === "proxyjump") {
if (value && value !== "none") info.proxyJump = value;
} else if (key === "identityfile") {
info.identityFiles = info.identityFiles ?? [];
info.identityFiles.push(value);
}
}
return info;
} catch {
return { alias: host, hostName: host };
}
}
/**
* Ensures control socket directory exists with secure permissions (0700).
*/
export function ensureSocketDir(socketDir: string = DEFAULT_SOCKET_DIR): string {
const resolved = resolve(socketDir);
if (!existsSync(resolved)) {
mkdirSync(resolved, { recursive: true, mode: 0o700 });
}
return resolved;
}
/**
* Formats standard OpenSSH arguments including ControlMaster multiplexing
* and safety invariants.
*/
export function buildSshArgs(options: {
host: string;
socketDir?: string | undefined;
controlPersist?: string | undefined;
batchMode?: boolean | undefined;
strictHostKey?: boolean | undefined;
extraArgs?: string[] | undefined;
}): string[] {
const socketDir = ensureSocketDir(options.socketDir ?? DEFAULT_SOCKET_DIR);
const controlPath = join(socketDir, "%C");
const persist = options.controlPersist ?? DEFAULT_CONTROL_PERSIST;
const args: string[] = [
"-o", `ControlMaster=auto`,
"-o", `ControlPath=${controlPath}`,
"-o", `ControlPersist=${persist}`,
];
if (options.batchMode !== false) {
args.push("-o", "BatchMode=yes");
}
if (options.strictHostKey !== false) {
args.push("-o", "StrictHostKeyChecking=yes");
}
if (options.extraArgs) {
args.push(...options.extraArgs);
}
// Validate that no insecure host key flags are present
assertPinnedSshHostKeyPolicy(args);
args.push(options.host);
return args;
}
/**
* Checks if a ControlMaster socket is active for the target host.
*/
export function checkControlSocket(
host: string,
socketDir: string = DEFAULT_SOCKET_DIR,
execFn: typeof spawnSync = spawnSync,
): boolean {
const resolvedDir = ensureSocketDir(socketDir);
const controlPath = join(resolvedDir, "%C");
try {
const result = execFn("ssh", ["-O", "check", "-o", `ControlPath=${controlPath}`, host], {
encoding: "utf-8",
});
return result.status === 0;
} catch {
return false;
}
}
/**
* Closes an active ControlMaster socket for the target host.
*/
export function closeControlSocket(
host: string,
socketDir: string = DEFAULT_SOCKET_DIR,
execFn: typeof spawnSync = spawnSync,
): boolean {
const resolvedDir = ensureSocketDir(socketDir);
const controlPath = join(resolvedDir, "%C");
try {
const result = execFn("ssh", ["-O", "stop", "-o", `ControlPath=${controlPath}`, host], {
encoding: "utf-8",
});
return result.status === 0;
} catch {
return false;
}
}
/**
* Cleans up all orphaned or expired ControlMaster sockets in the socket directory.
*/
export function pruneSocketDir(socketDir: string = DEFAULT_SOCKET_DIR): number {
const resolvedDir = ensureSocketDir(socketDir);
let removed = 0;
try {
const entries = readdirSync(resolvedDir);
for (const entry of entries) {
const fullPath = join(resolvedDir, entry);
try {
const stat = statSync(fullPath);
if (stat.isSocket()) {
rmSync(fullPath, { force: true });
removed++;
}
} catch {
// Skip unreadable entries
}
}
} catch {
// Ignore directory read errors
}
return removed;
}
/**
* Main execution handler for `ssh_run` tool.
*/
export function executeSshRun(params: SshRunParams): SshExecutionReceipt {
const startTime = Date.now();
const execSyncFn = params.execFn ?? spawnSync;
// 1. Action: "info"
if (params.action === "info") {
if (params.host) {
const hostInfo = resolveSshHostG(params.host, execSyncFn as typeof spawnSync);
return {
ok: true,
action: "info",
host: params.host,
resolvedHost: hostInfo.hostName ?? params.host,
user: hostInfo.user,
port: hostInfo.port,
hosts: [hostInfo],
durationMs: Date.now() - startTime,
};
}
const discovered = parseSshConfig();
return {
ok: true,
action: "info",
hosts: discovered,
durationMs: Date.now() - startTime,
};
}
// Require host for command and file actions
if (!params.host) {
return {
ok: false,
action: params.action,
error: 'Parameter "host" is required for command and file actions.',
durationMs: Date.now() - startTime,
};
}
const host = params.host;
const socketDir = params.socket_dir ?? DEFAULT_SOCKET_DIR;
const wasSocketActive = checkControlSocket(host, socketDir, execSyncFn as typeof spawnSync);
// 2. Action: "command"
if (params.action === "command") {
if (!params.command) {
return {
ok: false,
action: "command",
host,
error: 'Parameter "command" is required for command action.',
durationMs: Date.now() - startTime,
};
}
// Safety check: block destructive commands
try {
assertCommandSeatbelt(params.command);
} catch (err: unknown) {
return {
ok: false,
action: "command",
host,
error: (err as Error).message,
durationMs: Date.now() - startTime,
};
}
let remoteCommand = params.command;
let inputStdin: string | undefined = undefined;
// Handle remote sudo via stdin pipe
if (params.sudo) {
const sudoPwd = params.sudo_password ?? params.password;
if (sudoPwd) {
inputStdin = `${sudoPwd}\n`;
remoteCommand = `sudo -S -p '' -- sh -c ${JSON.stringify(params.command)}`;
} else {
remoteCommand = `sudo -- sh -c ${JSON.stringify(params.command)}`;
}
}
const useSshPass = Boolean(params.password);
const sshArgs = buildSshArgs({
host,
socketDir,
controlPersist: params.control_persist,
batchMode: !useSshPass,
strictHostKey: params.strict_host_key,
});
sshArgs.push("--", remoteCommand);
const spawnEnv: NodeJS.ProcessEnv = { ...process.env };
let binary = "ssh";
let finalArgs = sshArgs;
// Masked credential security: SSH password via SSHPASS environment variable
if (useSshPass && params.password) {
binary = "sshpass";
finalArgs = ["-e", "ssh", ...sshArgs];
spawnEnv.SSHPASS = params.password;
}
try {
const result = execSyncFn(binary, finalArgs, {
encoding: "utf-8",
env: spawnEnv,
input: inputStdin,
timeout: params.timeout_ms ?? 120_000,
maxBuffer: 10 * 1024 * 1024,
});
const stdoutRaw = String(result.stdout || "");
const stderrRaw = String(result.stderr || "");
// Sanitize stderr from potential sudo password echoes
const sanitizedStderr = stderrRaw.replace(/\[sudo\] password for [^:]+:\s*/gi, "");
const stdoutTrunc = truncateSshOutput(stdoutRaw);
const stderrTrunc = truncateSshOutput(sanitizedStderr);
return {
ok: result.status === 0,
action: "command",
host,
exitCode: result.status ?? 1,
stdout: stdoutTrunc.text,
stderr: stderrTrunc.text,
truncated: stdoutTrunc.truncated || stderrTrunc.truncated,
socketReused: wasSocketActive,
durationMs: Date.now() - startTime,
error: result.error ? result.error.message : undefined,
};
} catch (err: unknown) {
return {
ok: false,
action: "command",
host,
error: (err as Error).message,
durationMs: Date.now() - startTime,
};
}
}
// 3. Action: "file"
if (params.action === "file") {
if (!params.file_path) {
return {
ok: false,
action: "file",
host,
error: 'Parameter "file_path" is required for file action.',
durationMs: Date.now() - startTime,
};
}
const filePath = params.file_path;
const op = params.file_op ?? "write";
if (op === "read") {
const readCmd = params.sudo ? `sudo cat ${JSON.stringify(filePath)}` : `cat ${JSON.stringify(filePath)}`;
return executeSshRun({
...params,
action: "command",
command: readCmd,
});
}
const content = params.file_content ?? "";
const redirectOp = op === "append" ? ">>" : ">";
const writeCmd = params.sudo
? `sudo tee ${op === "append" ? "-a " : ""}${JSON.stringify(filePath)} > /dev/null`
: `cat ${redirectOp} ${JSON.stringify(filePath)}`;
const useSshPass = Boolean(params.password);
const sshArgs = buildSshArgs({
host,
socketDir,
controlPersist: params.control_persist,
batchMode: !useSshPass,
strictHostKey: params.strict_host_key,
});
sshArgs.push("--", writeCmd);
const spawnEnv: NodeJS.ProcessEnv = { ...process.env };
let binary = "ssh";
let finalArgs = sshArgs;
if (useSshPass && params.password) {
binary = "sshpass";
finalArgs = ["-e", "ssh", ...sshArgs];
spawnEnv.SSHPASS = params.password;
}
try {
const result = execSyncFn(binary, finalArgs, {
encoding: "utf-8",
env: spawnEnv,
input: content,
timeout: params.timeout_ms ?? 60_000,
});
return {
ok: result.status === 0,
action: "file",
host,
exitCode: result.status ?? 1,
stdout: String(result.stdout || ""),
stderr: String(result.stderr || ""),
fileProcessed: filePath,
bytesProcessed: Buffer.byteLength(content, "utf-8"),
socketReused: wasSocketActive,
durationMs: Date.now() - startTime,
error: result.error ? result.error.message : undefined,
};
} catch (err: unknown) {
return {
ok: false,
action: "file",
host,
error: (err as Error).message,
durationMs: Date.now() - startTime,
};
}
}
return {
ok: false,
action: params.action,
error: `Unknown action: ${(params as { action: string }).action}`,
durationMs: Date.now() - startTime,
};
}