fix(node-host): sync rawCommand with hardened argv after executable path pinning

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
This commit is contained in:
SidQin-cyber
2026-03-03 22:58:52 +08:00
committed by Gustavo Madeira Santana
parent 4fb40497d4
commit fefb2fbe67
2 changed files with 19 additions and 2 deletions

View File

@@ -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;
}

View File

@@ -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),
},