Skip to content

security(windows): enhance command argument validation#38846

Open
JnyRoad wants to merge 2 commits intoopenclaw:mainfrom
JnyRoad:bugfix/windows-cmd-escape-enhance
Open

security(windows): enhance command argument validation#38846
JnyRoad wants to merge 2 commits intoopenclaw:mainfrom
JnyRoad:bugfix/windows-cmd-escape-enhance

Conversation

@JnyRoad
Copy link
Copy Markdown

@JnyRoad JnyRoad commented Mar 7, 2026

  • Expand WINDOWS_UNSAFE_CMD_CHARS_RE with comprehensive character list
  • Add POWERSHELL_UNSAFE_PATTERNS_RE for PowerShell cmdlet blocking
  • Add validateWindowsArgument() with detailed error messages
  • Block null bytes and Unicode control characters
  • Export validateWindowsCommandArgument() for external use
  • Add security documentation and JSDoc comments

Security: Defense-in-depth for Windows command execution. This prevents command injection attacks via crafted arguments.

Reference: CVE-2024-27980, Microsoft cmd.exe parsing documentation

Fixes: Bug #3 from security audit 2026-03-07

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem:
  • Why it matters:
  • What changed:
  • What did NOT change (scope boundary):

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

  • Closes #
  • Related #

User-visible / Behavior Changes

List user-visible changes (including defaults/config).
If none, write None.

Security Impact (required)

  • New permissions/capabilities? (Yes/No)
  • Secrets/tokens handling changed? (Yes/No)
  • New/changed network calls? (Yes/No)
  • Command/tool execution surface changed? (Yes/No)
  • Data access scope changed? (Yes/No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS:
  • Runtime/container:
  • Model/provider:
  • Integration/channel (if any):
  • Relevant config (redacted):

Steps

Expected

Actual

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
  • Edge cases checked:
  • What you did not verify:

Compatibility / Migration

  • Backward compatible? (Yes/No)
  • Config/env changes? (Yes/No)
  • Migration needed? (Yes/No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
  • Files/config to restore:
  • Known bad symptoms reviewers should watch for:

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk:
    • Mitigation:

- Expand WINDOWS_UNSAFE_CMD_CHARS_RE with comprehensive character list
- Add POWERSHELL_UNSAFE_PATTERNS_RE for PowerShell cmdlet blocking
- Add validateWindowsArgument() with detailed error messages
- Block null bytes and Unicode control characters
- Export validateWindowsCommandArgument() for external use
- Add security documentation and JSDoc comments

Security: Defense-in-depth for Windows command execution.
This prevents command injection attacks via crafted arguments.

Reference: CVE-2024-27980, Microsoft cmd.exe parsing documentation

Fixes: Bug openclaw#3 from security audit 2026-03-07
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 73a73bbf30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* * - Wildcard
* ? - Wildcard
*/
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>\^%\r\n;`$(){}[\]=+'\\/*?!~]/;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow path separators in validated cmd.exe arguments

The expanded WINDOWS_UNSAFE_CMD_CHARS_RE now rejects / and \\, so any Windows .cmd wrapper invocation fails for normal arguments that contain path separators, including scoped npm package specs (for example the packageName passed by buildNodeInstallCommand in src/agents/skills-install.ts, such as @openclaw/matrix). In the Windows pnpm/yarn path this throws inside escapeForCmdExe before spawning, which turns legitimate installs/commands into hard errors rather than just blocking injection payloads.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR expands Windows command-injection defences in src/process/exec.ts by broadening the blocked character set, extracting validation into a dedicated validateWindowsArgument() function, and adding PowerShell-pattern and null-byte checks. However, the implementation introduces critical regressions that break core functionality.

Key changes:

  • WINDOWS_UNSAFE_CMD_CHARS_RE expanded from 8 characters to ~30, including \, /, and =
  • New validateWindowsArgument() called inside escapeForCmdExe() (which is also called on the command path itself in buildCmdExeCommandLine)
  • New POWERSHELL_UNSAFE_PATTERNS_RE pattern check added
  • Null-byte and Unicode control character checks added
  • validateWindowsCommandArgument() exported for external callers

Critical issues found:

  • Backslash (\) blocks all .cmd/.bat executionbuildCmdExeCommandLine passes the resolved command (a Windows absolute path) through escapeForCmdExevalidateWindowsArgument, which now throws on any \. Every .bat/.cmd invocation on Windows is broken.
  • / and = rejection is too broad — Windows CLI switches (/E, /v, etc.) and long-option arguments (--output=file.txt) are now rejected, breaking legitimate callers.
  • Error messages leak full argument valuesJSON.stringify(arg) in error paths will expose any sensitive argument content (credentials, tokens) in logs and stack traces.

Confidence Score: 1/5

  • Not safe to merge — the PR breaks all .cmd/.bat execution on Windows and rejects many legitimate argument patterns.
  • The backslash regression alone is a showstopper: blocking \ causes validateWindowsArgument to throw on every Windows absolute path, making buildCmdExeCommandLine (and therefore any .cmd/.bat invocation) non-functional. The over-broad blocking of / and = compounds this, rejecting valid CLI switch and long-option arguments. These are not edge cases — they are the primary use patterns for the code path being hardened. Information disclosure via unredacted argument values in errors is a secondary concern but also fixable.
  • src/process/exec.ts — specifically the WINDOWS_UNSAFE_CMD_CHARS_RE character set and the interaction between escapeForCmdExe() and validateWindowsArgument()

Last reviewed commit: 73a73bb

* * - Wildcard
* ? - Wildcard
*/
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>\^%\r\n;`$(){}[\]=+'\\/*?!~]/;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backslash in blocked set breaks all .cmd/.bat execution on Windows

\\ (backslash) is now in WINDOWS_UNSAFE_CMD_CHARS_RE. On Windows, every absolute path uses backslashes — e.g. C:\Users\alice\project\build.bat. The call chain is:

buildCmdExeCommandLine(resolvedCommand, execArgs)     // line 130
  → escapeForCmdExe(resolvedCommand)                   // line 120
    → validateWindowsArgument("C:\...\build.bat")      // line 69
      → WINDOWS_UNSAFE_CMD_CHARS_RE.test(...)          // line 70 → matches '\' → throws

This means every .cmd/.bat invocation will throw before execution. Backslash should be removed from the blocked set; the surrounding buildCmdExeCommandLine approach (passing the whole command line as a single /c argument with windowsVerbatimArguments: true) already prevents UNC injection at the structural level.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/exec.ts
Line: 45

Comment:
**Backslash in blocked set breaks all `.cmd`/`.bat` execution on Windows**

`\\` (backslash) is now in `WINDOWS_UNSAFE_CMD_CHARS_RE`. On Windows, every absolute path uses backslashes — e.g. `C:\Users\alice\project\build.bat`. The call chain is:

```
buildCmdExeCommandLine(resolvedCommand, execArgs)     // line 130
  → escapeForCmdExe(resolvedCommand)                   // line 120
    → validateWindowsArgument("C:\...\build.bat")      // line 69
      → WINDOWS_UNSAFE_CMD_CHARS_RE.test(...)          // line 70 → matches '\' → throws
```

This means **every** `.cmd`/`.bat` invocation will throw before execution. Backslash should be removed from the blocked set; the surrounding `buildCmdExeCommandLine` approach (passing the whole command line as a single `/c` argument with `windowsVerbatimArguments: true`) already prevents UNC injection at the structural level.

How can I resolve this? If you propose a fix, please make it concise.

* * - Wildcard
* ? - Wildcard
*/
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>\^%\r\n;`$(){}[\]=+'\\/*?!~]/;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ and = blocking rejects valid command arguments

Both / and = are added to the blocked character set, but they appear in completely legitimate argument patterns:

  • / is the standard Windows switch prefix — arguments like /E, /I, /H (used by XCOPY) and /v (used by many utilities) would all be rejected.
  • = is used in long-option style arguments: --output=file.txt, MSBuild-style /p:Configuration=Release, etc.

For cmd.exe, neither character is an injection vector on its own — injection requires &, |, <, >, ^, %, and newlines (the original character set). Removing / and = from the blocked set would restore legitimate use without weakening the actual injection defences.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/exec.ts
Line: 45

Comment:
**`/` and `=` blocking rejects valid command arguments**

Both `/` and `=` are added to the blocked character set, but they appear in completely legitimate argument patterns:

- `/` is the standard Windows switch prefix — arguments like `/E`, `/I`, `/H` (used by `XCOPY`) and `/v` (used by many utilities) would all be rejected.
- `=` is used in long-option style arguments: `--output=file.txt`, MSBuild-style `/p:Configuration=Release`, etc.

For cmd.exe, neither character is an injection vector on its own — injection requires `&`, `|`, `<`, `>`, `^`, `%`, and newlines (the original character set). Removing `/` and `=` from the blocked set would restore legitimate use without weakening the actual injection defences.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 73 to +78
throw new Error(
`Unsafe Windows cmd.exe argument detected: ${JSON.stringify(arg)}. ` +
"Pass an explicit shell-wrapper argv at the call site instead.",
`Dangerous character '${dangerousChar}' found. ` +
(context ? `Context: ${context}. ` : "") +
"Pass an explicit shell-wrapper argv at the call site instead."
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sensitive argument values are echoed verbatim in error messages

JSON.stringify(arg) embeds the full argument string in every thrown Error (lines 74, 84, 92). If a caller passes an argument that contains credentials, tokens, or API keys, the secret will be visible in:

  • Exception .message (surfaced in catch blocks and stack traces)
  • Any error-logging pipeline that records the caught error

Consider truncating or redacting the value to avoid inadvertent secret exposure in logs. For example:

Suggested change
throw new Error(
`Unsafe Windows cmd.exe argument detected: ${JSON.stringify(arg)}. ` +
"Pass an explicit shell-wrapper argv at the call site instead.",
`Dangerous character '${dangerousChar}' found. ` +
(context ? `Context: ${context}. ` : "") +
"Pass an explicit shell-wrapper argv at the call site instead."
);
if (WINDOWS_UNSAFE_CMD_CHARS_RE.test(arg)) {
const charMatch = arg.match(WINDOWS_UNSAFE_CMD_CHARS_RE);
const dangerousChar = charMatch ? charMatch[0] : "unknown";
throw new Error(
`Unsafe Windows cmd.exe argument detected. ` +
`Dangerous character '${dangerousChar}' found (length: ${arg.length}). ` +
(context ? `Context: ${context}. ` : "") +
"Pass an explicit shell-wrapper argv at the call site instead."
);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/process/exec.ts
Line: 73-78

Comment:
**Sensitive argument values are echoed verbatim in error messages**

`JSON.stringify(arg)` embeds the full argument string in every thrown `Error` (lines 74, 84, 92). If a caller passes an argument that contains credentials, tokens, or API keys, the secret will be visible in:

- Exception `.message` (surfaced in catch blocks and stack traces)
- Any error-logging pipeline that records the caught error

Consider truncating or redacting the value to avoid inadvertent secret exposure in logs. For example:

```suggestion
  if (WINDOWS_UNSAFE_CMD_CHARS_RE.test(arg)) {
    const charMatch = arg.match(WINDOWS_UNSAFE_CMD_CHARS_RE);
    const dangerousChar = charMatch ? charMatch[0] : "unknown";
    throw new Error(
      `Unsafe Windows cmd.exe argument detected. ` +
      `Dangerous character '${dangerousChar}' found (length: ${arg.length}). ` +
      (context ? `Context: ${context}. ` : "") +
      "Pass an explicit shell-wrapper argv at the call site instead."
    );
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc1c169fad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* * - Wildcard
* ? - Wildcard
*/
const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>\^%\r\n;`$(){}[\]=+'\\/*?!~]/;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Permit '=' in cmd-wrapper argument validation

The expanded blacklist now rejects = in every argument passed through the Windows .cmd wrapper, which breaks legitimate npm flags that this repo always emits (for example --loglevel=error in src/infra/update-global.ts). In Bun-based Windows runs, resolveNpmArgvForWindows explicitly falls back to npm.cmd, so these update/install commands now throw inside escapeForCmdExe before the process is even spawned, turning normal openclaw update/global-install flows into hard failures.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@mdlmarkham mdlmarkham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Review: Windows Command Argument Validation

Verdict: Strong defense-in-depth improvement. A few potential bypass vectors to consider.

What's Covered ✅

The expanded blocklist is comprehensive:

  • Command separators: &, |, ;
  • Redirection: <, >, [, ]
  • Variable expansion: %, $, !
  • Shell metacharacters: `, ^, (, ), {, }
  • PowerShell cmdlets: Invoke-Expression, IEX, Invoke-Command, etc.
  • Null bytes and control characters

Good export of validateWindowsCommandArgument for pre-validation.

Potential Bypass Vectors to Consider

1. Base64 Encoded Commands (PowerShell)

powershell -EncodedCommand <base64>

The POWERSHELL_UNSAFE_PATTERNS_RE blocks Invoke-Expression in the argument, but -EncodedCommand allows arbitrary base64-encoded commands to execute.

Mitigation: Consider blocking -EncodedCommand or -e, -enc variants.

2. Environment Variable Expansion via CMD

%COMSPEC% /c whoami

%COMSPEC% expands to cmd.exe path. The % is blocked, but %PATH% and other env vars might slip through context-dependent expansion.

Current protection: % is in blocklist ✅

3. forfiles / Other Native Tools

forfiles /P C:\ /M notepad.exe /C "cmd /c whoami"

Native Windows tools that execute commands. Not blocked by current patterns.

Assessment: These require the binary to exist and might be out of scope for argument validation. Document scope if intentional.

4. Path Traversal via \

The backslash \ is blocked, which prevents:

..\..\..\..\..\..\etc\passwd

But on Windows, \ is a path separator. Consider if this blocks legitimate paths like C:\Users\....

Recommendation: Allow \ when path is validated as absolute or within allowed directories. Currently blocks all \, which might break legitimate use cases.

Suggested Additions

  1. Block -EncodedCommand variants:

    const ENCODED_COMMAND_RE = /\b-[Ee](?:ncodedCommand|nc|ncodedCommand)\b/;
  2. Block environment variable expansion:

    // Already covered by % in blocklist, but explicit:
    const ENV_VAR_RE = /\%[^%]+\%/;
  3. Document scope: Clarify that this validates individual arguments, not command composition. Attackers could still chain multiple validated arguments if the call site builds a command line incorrectly.

Testing Recommendations

  • Test base64-encoded PowerShell commands bypass
  • Test legitimate paths with \ (should they pass?)
  • Test forfiles and other native execution tools
  • Test Unicode normalization attacks (e.g., \u0026 for &)
  • Test with real-world command injection payloads from OWASP

Overall: Good security hardening. Consider the bypass vectors above, especially -EncodedCommand. Approve with minor suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants