From fefb2fbe67f13a5bb4ffdcd27242edb67a4881a7 Mon Sep 17 00:00:00 2001 From: SidQin-cyber Date: Tue, 3 Mar 2026 22:58:52 +0800 Subject: [PATCH] fix(node-host): sync rawCommand with hardened argv after executable path pinning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The security fix for GHSA-h3f9-mjwj-w476 added rawCommand/command[] consistency validation. After hardenApprovedExecutionPaths pins an executable to its absolute path (e.g. echo → /usr/bin/echo), the rawCommand was not updated, causing all nodes.run direct commands to fail validation. Regenerate rawCommand via formatExecCommand(hardening.argv) when hardening produces a new argv, so the consistency check passes. Closes #33080 --- src/node-host/invoke-system-run-plan.test.ts | 13 +++++++++++++ src/node-host/invoke-system-run-plan.ts | 8 ++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/node-host/invoke-system-run-plan.test.ts b/src/node-host/invoke-system-run-plan.test.ts index 3953c8f2d30..9aedc8d3b6f 100644 --- a/src/node-host/invoke-system-run-plan.test.ts +++ b/src/node-host/invoke-system-run-plan.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { formatExecCommand } from "../infra/system-run-command.js"; import { buildSystemRunApprovalPlan, hardenApprovedExecutionPaths, @@ -19,6 +20,7 @@ type HardeningCase = { withPathToken?: boolean; expectedArgv: (ctx: { pathToken: PathTokenSetup | null }) => string[]; expectedCmdText?: string; + checkRawCommandMatchesArgv?: boolean; }; describe("hardenApprovedExecutionPaths", () => { @@ -53,6 +55,14 @@ describe("hardenApprovedExecutionPaths", () => { withPathToken: true, expectedArgv: () => ["env", "poccmd", "SAFE"], }, + { + name: "rawCommand matches hardened argv after executable path pinning", + mode: "build-plan", + argv: ["poccmd", "hello"], + withPathToken: true, + expectedArgv: ({ pathToken }) => [pathToken!.expected, "hello"], + checkRawCommandMatchesArgv: true, + }, ]; for (const testCase of cases) { @@ -82,6 +92,9 @@ describe("hardenApprovedExecutionPaths", () => { if (testCase.expectedCmdText) { expect(prepared.cmdText).toBe(testCase.expectedCmdText); } + if (testCase.checkRawCommandMatchesArgv) { + expect(prepared.plan.rawCommand).toBe(formatExecCommand(prepared.plan.argv)); + } return; } diff --git a/src/node-host/invoke-system-run-plan.ts b/src/node-host/invoke-system-run-plan.ts index 6bb5f28034b..5d7a964583f 100644 --- a/src/node-host/invoke-system-run-plan.ts +++ b/src/node-host/invoke-system-run-plan.ts @@ -3,7 +3,7 @@ import path from "node:path"; import type { SystemRunApprovalPlan } from "../infra/exec-approvals.js"; import { resolveCommandResolutionFromArgv } from "../infra/exec-command-resolution.js"; import { sameFileIdentity } from "../infra/file-identity.js"; -import { resolveSystemRunCommand } from "../infra/system-run-command.js"; +import { formatExecCommand, resolveSystemRunCommand } from "../infra/system-run-command.js"; export type ApprovedCwdSnapshot = { cwd: string; @@ -239,12 +239,16 @@ export function buildSystemRunApprovalPlan(params: { if (!hardening.ok) { return { ok: false, message: hardening.message }; } + const rawCommand = + hardening.argv === command.argv + ? command.cmdText.trim() || null + : formatExecCommand(hardening.argv) || null; return { ok: true, plan: { argv: hardening.argv, cwd: hardening.cwd ?? null, - rawCommand: command.cmdText.trim() || null, + rawCommand, agentId: normalizeString(params.agentId), sessionKey: normalizeString(params.sessionKey), },