fix(node-host): enforce system.run rawCommand/argv consistency

This commit is contained in:
Peter Steinberger
2026-02-14 18:52:13 +01:00
parent 71f357d949
commit cb3290fca3
6 changed files with 241 additions and 23 deletions

View File

@@ -31,6 +31,7 @@ import {
type ExecHostResponse,
type ExecHostRunResult,
} from "../infra/exec-host.js";
import { validateSystemRunCommandConsistency } from "../infra/system-run-command.js";
import { runBrowserProxyCommand } from "./invoke-browser.js";
const OUTPUT_CAP = 200_000;
@@ -174,22 +175,6 @@ function sanitizeEnv(
return merged;
}
function formatCommand(argv: string[]): string {
return argv
.map((arg) => {
const trimmed = arg.trim();
if (!trimmed) {
return '""';
}
const needsQuotes = /\s|"/.test(trimmed);
if (!needsQuotes) {
return trimmed;
}
return `"${trimmed.replace(/"/g, '\\"')}"`;
})
.join(" ");
}
function truncateOutput(raw: string, maxChars: number): { text: string; truncated: boolean } {
if (raw.length <= maxChars) {
return { text: raw, truncated: false };
@@ -514,7 +499,20 @@ export async function handleInvoke(
const argv = params.command.map((item) => String(item));
const rawCommand = typeof params.rawCommand === "string" ? params.rawCommand.trim() : "";
const cmdText = rawCommand || formatCommand(argv);
const consistency = validateSystemRunCommandConsistency({
argv,
rawCommand: rawCommand || null,
});
if (!consistency.ok) {
await sendInvokeResult(client, frame, {
ok: false,
error: { code: "INVALID_REQUEST", message: consistency.message },
});
return;
}
const shellCommand = consistency.shellCommand;
const cmdText = consistency.cmdText;
const agentId = params.agentId?.trim() || undefined;
const cfg = loadConfig();
const agentExec = agentId ? resolveAgentConfig(cfg, agentId)?.tools?.exec : undefined;
@@ -536,9 +534,9 @@ export async function handleInvoke(
let allowlistMatches: ExecAllowlistEntry[] = [];
let allowlistSatisfied = false;
let segments: ExecCommandSegment[] = [];
if (rawCommand) {
if (shellCommand) {
const allowlistEval = evaluateShellAllowlist({
command: rawCommand,
command: shellCommand,
allowlist: approvals.allowlist,
safeBins,
cwd: params.cwd ?? undefined,
@@ -569,7 +567,7 @@ export async function handleInvoke(
segments = analysis.segments;
}
const isWindows = process.platform === "win32";
const cmdInvocation = rawCommand
const cmdInvocation = shellCommand
? isCmdExeInvocation(segments[0]?.argv ?? [])
: isCmdExeInvocation(argv);
if (security === "allowlist" && isWindows && cmdInvocation) {
@@ -585,7 +583,7 @@ export async function handleInvoke(
: null;
const execRequest: ExecHostRequest = {
command: argv,
rawCommand: rawCommand || null,
rawCommand: rawCommand || shellCommand || null,
cwd: params.cwd ?? null,
env: params.env ?? null,
timeoutMs: params.timeoutMs ?? null,
@@ -780,7 +778,7 @@ export async function handleInvoke(
security === "allowlist" &&
isWindows &&
!approvedByAsk &&
rawCommand &&
shellCommand &&
analysisOk &&
allowlistSatisfied &&
segments.length === 1 &&