Skip to content

[Repo Assist] security: add ExecEnvSanitizer to block env-var injection in system.run#186

Closed
github-actions[bot] wants to merge 1 commit intomasterfrom
repo-assist/fix-exec-env-sanitizer-2026-04-20-3c2554bed14582a9
Closed

[Repo Assist] security: add ExecEnvSanitizer to block env-var injection in system.run#186
github-actions[bot] wants to merge 1 commit intomasterfrom
repo-assist/fix-exec-env-sanitizer-2026-04-20-3c2554bed14582a9

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

🤖 This is an automated PR from Repo Assist.

Closes #184 (partially — addresses the env-var injection vector; the shell wrapper bypass is a separate, larger change).

Summary

Adds ExecEnvSanitizer, a new static class that strips dangerous environment-variable overrides before they reach the process, protecting against the injection attack described in issue #184.

Attack scenario: An agent submits a system.run request for an approved command (e.g. git status) but includes an env field overriding GIT_SSH_COMMAND, PATH, NODE_OPTIONS, or LD_PRELOAD. The outer command passes the exec approval policy unchanged, but execution is silently hijacked by the injected variable.

Root Cause

SystemCapability parsed the env dict and forwarded it to LocalCommandRunner without any filtering. LocalCommandRunner copied all values directly into ProcessStartInfo.Environment.

Fix

New class ExecEnvSanitizer (src/OpenClaw.Shared/ExecEnvSanitizer.cs):

  • Exact-match denylist (FrozenSet, case-insensitive): PATH, PATHEXT, ComSpec, PSModulePath, NODE_OPTIONS, NODE_PATH, PYTHONPATH, PYTHONSTARTUP, PYTHONUSERBASE, RUBYOPT, RUBYLIB, BASH_ENV, ENV, ZDOTDIR, GIT_SSH, GIT_SSH_COMMAND, GIT_EXEC_PATH, GIT_PROXY_COMMAND, GIT_ASKPASS, LD_PRELOAD, LD_LIBRARY_PATH, LD_AUDIT, DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH, PERL5OPT, PERL5LIB, PERLIO
  • Prefix denylist: any name starting with LD_ or DYLD_
  • Name validation: rejects empty names and names containing = or control characters
  • Returns SanitizeResult with Allowed dict and Blocked list for audit logging

Integration in SystemCapability.HandleRunAsync: calls ExecEnvSanitizer.Sanitize() after parsing the env dict; logs a warning listing each blocked variable name; passes only approved variables (or null if none survive) to the runner.

Remaining Gap from #184

The shell wrapper approval bypass (item 2 in #184) — where powershell -Command Remove-Item... evades a rule on Remove-Item * — is a separate, more complex change involving command parsing. This PR does not address it.

Test Status

53 new tests in ExecEnvSanitizerTests.cs:

  • IsBlocked unit tests for all denylist entries, case-insensitive variants, safe-var allowlist (LOG_LEVEL, APP_ENV, TZ, LANG, LC_ALL, HOME, RAILS_ENV...), invalid names
  • Sanitize() scenarios: all-safe, all-dangerous, mixed, empty input, LD_ prefix variants
  • Two end-to-end integration tests through SystemCapability: verifies blocked vars don't reach the runner, safe vars pass through, and env is set to null when all vars are blocked

Totals:

  • Shared.Tests: 639 passed, 20 skipped (659 total) ✅
  • Tray.Tests: 122 passed ✅

Generated by 🌈 Repo Assist, see workflow run. Learn more.

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@97143ac59cb3a13ef2a77581f929f06719c7402a

Fixes the environment-variable injection vector described in issue #184.

An agent can supply an 'env' dict that overrides variables like PATH,
NODE_OPTIONS, GIT_SSH_COMMAND, or LD_PRELOAD, silently redirecting
execution of an otherwise-approved command. ExecEnvSanitizer filters
these variables before they reach the process, preserving the intent of
the exec approval policy.

Changes:
- New ExecEnvSanitizer (OpenClaw.Shared): static class with an exact-match
  FrozenSet denylist + LD_/DYLD_ prefix rules; blocks PATH, PATHEXT,
  ComSpec, NODE_OPTIONS, GIT_SSH_COMMAND, LD_PRELOAD, DYLD_*, BASH_ENV,
  PYTHONPATH, RUBYOPT, PERL5OPT, and related variables; also rejects empty
  names and names containing '=' or control characters.
- SystemCapability.HandleRunAsync: parse raw env dict first, call
  ExecEnvSanitizer.Sanitize(), warn about blocked vars, forward only
  approved vars (null when none survive).
- ExecEnvSanitizerTests (53 new tests): IsBlocked unit tests for all
  denylist entries (case-insensitive), safe-var allowlist, invalid names,
  Sanitize() mixed-input scenarios, LD_ prefix variants, and two
  end-to-end integration tests through SystemCapability.

639 Shared + 122 Tray tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
shanselman added a commit that referenced this pull request Apr 20, 2026
Closes #184 by blocking dangerous environment overrides and by re-evaluating nested shell-wrapper payloads and chained commands against the exec approval policy.

This extends the partial env-only approach discussed in PR #186 so the Windows node closes both vectors called out in the issue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
shanselman added a commit that referenced this pull request Apr 20, 2026
Closes #184 by blocking dangerous environment overrides and by re-evaluating nested shell-wrapper payloads and chained commands against the exec approval policy.

This extends the partial env-only approach discussed in PR #186 so the Windows node closes both vectors called out in the issue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@shanselman
Copy link
Copy Markdown
Collaborator

Superseded by the full issue #184 fix now pushed to master via PR #188 / direct merge.

@shanselman shanselman closed this Apr 20, 2026
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.

[Security] system.run: env variable injection and shell wrapper approval bypass

1 participant