Skip to content

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

Merged
gumadeiras merged 2 commits intoopenclaw:mainfrom
Sid-Qin:fix/33080-nodes-run-rawcommand-consistency
Mar 4, 2026
Merged

fix(node-host): sync rawCommand with hardened argv after executable path pinning#33137
gumadeiras merged 2 commits intoopenclaw:mainfrom
Sid-Qin:fix/33080-nodes-run-rawcommand-consistency

Conversation

@Sid-Qin
Copy link
Copy Markdown
Contributor

@Sid-Qin Sid-Qin commented Mar 3, 2026

Summary

  • Problem: Since 2026.3.2, all nodes.run (system.run) commands fail with "rawCommand does not match command". The security fix for GHSA-h3f9-mjwj-w476 added rawCommand/command[] consistency validation, but hardenApprovedExecutionPaths pins executables to absolute paths without updating rawCommand.
  • Why it matters: Every nodes.run command is rejected — even echo hello. Remote node execution is completely broken.
  • What changed: After hardening pins an executable to its absolute path, rawCommand is regenerated via formatExecCommand(hardening.argv) so the consistency check passes.
  • What did NOT change: The security validation itself, hardening behavior, or any other exec tool behavior.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • nodes.run commands work again after the 2026.3.2 security fix.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No (restores pre-regression behavior)
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: macOS / Linux
  • Runtime: Node.js + macOS companion app
  • Integration/channel: nodes.run tool

Steps

  1. Update to 2026.3.2
  2. Run any command via nodes.run (e.g. ["echo", "hello"])

Expected

  • Command executes normally.

Actual

  • Before fix: "rawCommand does not match command" rejection.
  • After fix: Command executes, rawCommand matches hardened argv.

Evidence

Identity comparison hardening.argv === command.argv detects when hardening created a new array. Test creates a PATH-resolvable symlink and verifies plan.rawCommand matches formatExecCommand(plan.argv).

Human Verification (required)

  • Verified scenarios: direct command, bash-wrapped command, command with arguments
  • Edge cases checked: no-hardening case (argv unchanged), hardening with path pinning
  • What I did not verify: Windows path resolution

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert: Revert the rawCommand regeneration
  • Files/config to restore: src/node-host/invoke-system-run-plan.ts
  • Known bad symptoms: nodes.run would fail validation again

Risks and Mitigations

  • Risk: rawCommand mismatch in edge cases — Mitigated: only regenerates when hardening actually changes argv (identity check)

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 3, 2026

Greptile Summary

This PR fixes a regression introduced by the GHSA-h3f9-mjwj-w476 security fix, where rawCommand was not updated after hardenApprovedExecutionPaths pinned a bare executable name (e.g. poccmd) to its absolute realpath (e.g. /usr/bin/echo). Because the consistency validator compares rawCommand against formatExecCommand(argv), having a stale rawCommand (still containing the bare name) caused every direct-command invocation via nodes.run to fail with RAW_COMMAND_MISMATCH.

Key changes:

  • buildSystemRunApprovalPlan now checks whether hardening changed the argv array via reference equality (hardening.argv === command.argv); if so, it regenerates rawCommand using formatExecCommand(hardening.argv) so validation will pass downstream.
  • The display-facing cmdText field returned to callers is intentionally left as the pre-hardening user-readable command text (unaffected by the fix).
  • A new build-plan mode test case installs a poccmd → /bin/echo symlink into PATH and asserts plan.rawCommand === formatExecCommand(plan.argv) after the plan is built, directly covering the fixed code path.

Confidence Score: 4/5

  • Safe to merge — the fix is minimal, targeted, and correctly restores nodes.run direct-command functionality broken by the prior security patch.
  • The reference-equality check (hardening.argv === command.argv) is valid and well-grounded in the implementation of hardenApprovedExecutionPaths, which always returns the original params.argv reference when no pinning occurs and allocates a new array only when it does. The regenerated rawCommand via formatExecCommand is exactly what the downstream consistency validator computes, so the fix closes the mismatch without introducing new divergence. The test case verifies the invariant directly. One point off because the test does not explicitly assert the pre-fix broken state (i.e., it would also pass if rawCommand were never set at all), and the end-to-end path through the approval socket is not unit-tested here.
  • No files require special attention.

Last reviewed commit: b25ad3b

@gumadeiras gumadeiras self-assigned this Mar 4, 2026
@gumadeiras gumadeiras force-pushed the fix/33080-nodes-run-rawcommand-consistency branch from b25ad3b to 9d1d007 Compare March 4, 2026 16:19
SidQin-cyber and others added 2 commits March 4, 2026 11:27
…ath 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 openclaw#33080
@gumadeiras gumadeiras force-pushed the fix/33080-nodes-run-rawcommand-consistency branch from 9d1d007 to a798790 Compare March 4, 2026 16:29
@gumadeiras gumadeiras merged commit c8ebd48 into openclaw:main Mar 4, 2026
9 checks passed
@gumadeiras
Copy link
Copy Markdown
Member

Merged via squash.

Thanks @Sid-Qin!

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot bot commented Mar 4, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Control-character/log injection risk via rawCommand regeneration using formatExecCommand

1. 🟡 Control-character/log injection risk via rawCommand regeneration using formatExecCommand

Property Value
Severity Medium
CWE CWE-117
Location src/node-host/invoke-system-run-plan.ts:257-259

Description

buildSystemRunApprovalPlan(...) now regenerates plan.rawCommand from the hardened argv when PATH pinning rewrites argv[0].

Because formatExecCommand(argv) only quotes whitespace / " and does not escape control characters (e.g. \n, \r, \t, ANSI \x1b), an attacker who can influence argv tokens or the resolved executable path (e.g. by placing a weirdly-named executable in a writable PATH directory) can cause rawCommand to contain embedded newlines/control characters.

Security impact (grounded in current flows):

  • plan.rawCommand is used as human-facing approval text (gateway uses plan.rawCommand ?? formatExecCommand(plan.argv) as the ExecApprovalRequestPayload.command).
  • cmdText/command strings are also propagated into exec-related system events (exec.denied/exec.started) and may be surfaced in operator UIs or logs.
  • Control characters in those strings can enable log/UI injection (multi-line spoofing, confusing approvals, terminal escape sequence injection in non-Discord sinks), and can also create ambiguity if any downstream component ever re-parses rawCommand as a shell command.

Vulnerable code (new behavior):

const rawCommand = hardening.argvChanged
  ? formatExecCommand(hardening.argv) || null
  : command.cmdText.trim() || null;

Relevant formatter behavior:

const needsQuotes = /\s|"/.test(arg);
return `"${arg.replace(/"/g, "\\\"")}"`;// (no escaping/sanitization for \r, \n, \t, \x1b, etc.)

Recommendation

Treat rawCommand as display-only and ensure it is safe and unambiguous for UIs/logs.

Options (in increasing robustness):

  1. Escape control characters for display (at minimum \r, \n, \t, \x1b, and other C0 controls) before embedding into approval prompts/system events.

  2. Prefer a reversible/collision-resistant encoding for display, e.g. render argv as JSON so control characters are unambiguously escaped:

export function formatExecCommandForDisplay(argv: string[]): string {// JSON escaping distinguishes newline vs literal "\\n".
  return argv.map((a) => JSON.stringify(a)).join(" ");
}
  1. Keep storing/approving against structured argv (already done) and avoid any downstream code re-parsing rawCommand as an execution string.

If you must keep a shell-like rendering, implement a dedicated escaping layer that:

  • encodes control chars (\n -> \\n, \r -> \\r, \x1b -> \\x1b, etc.)
  • is deterministic and (ideally) reversible
  • is applied consistently anywhere rawCommand/cmdText is displayed or logged.

Analyzed PR: #33137 at commit a798790

Last updated on: 2026-03-04T17:23:36Z

mrosmarin added a commit to mrosmarin/openclaw that referenced this pull request Mar 4, 2026
* main:
  Compaction/Safeguard: require structured summary headings (openclaw#25555)
  Fix gateway restart false timeouts on Debian/systemd (openclaw#34874)
  fix(review): enforce behavioral sweep validation
  chore(changelog): clarify outbound media-only fallback openclaw#32788 thanks @liuxiaopai-ai
  fix(outbound): fail media-only text-only adapter fallback
  chore(changelog): align outbound adapter entry openclaw#32788 thanks @liuxiaopai-ai
  Outbound: avoid empty multi-media fallback sends
  Outbound: allow text-only plugin adapters
  chore(changelog): add PR entry openclaw#24337 thanks @echoVic
  test(ollama): add default header precedence coverage
  fix(ollama): pass provider headers to Ollama stream function (openclaw#24285)
  Agents: add generic poll-vote action support
  fix(node-host): sync rawCommand with hardened argv after executable path pinning (openclaw#33137)
  fix(daemon): handle systemctl is-enabled exit 4 (not-found) on Ubuntu (openclaw#33634)
  fix(model): propagate custom provider headers to model objects (openclaw#27490)
  fix(memory): serialize local embedding initialization to avoid duplicate model loads (openclaw#15639)
  Delete changelog/fragments directory
  fix(slack): route system events to bound agent sessions (openclaw#34045)