Skip to content

fix(dotenv): block Windows shell trust-root vars from workspace .env [AI-assisted]#74460

Merged
drobison00 merged 3 commits intoopenclaw:mainfrom
mmaps:fix/fix-541
May 1, 2026
Merged

fix(dotenv): block Windows shell trust-root vars from workspace .env [AI-assisted]#74460
drobison00 merged 3 commits intoopenclaw:mainfrom
mmaps:fix/fix-541

Conversation

@mmaps
Copy link
Copy Markdown
Contributor

@mmaps mmaps commented Apr 29, 2026

Summary

  • Problem: Workspace .env files could set Windows path-resolution variables (ProgramFiles, ProgramW6432, SystemRoot, windir) that control where OpenClaw locates pwsh.exe / powershell.exe, enabling a malicious repository to redirect shell execution to an attacker-controlled binary.
  • Why it matters: Any user who opens an attacker-controlled repository on Windows and triggers a PTY or host-shell execution flow is exposed to local code execution in their own user context.
  • What changed: Added PROGRAMFILES, PROGRAMW6432, SYSTEMROOT, and WINDIR to BLOCKED_WORKSPACE_DOTENV_KEYS in src/infra/dotenv.ts. Added a targeted test case in src/infra/dotenv.test.ts that writes mixed-case and uppercase variants of all four keys into a workspace .env and asserts none are loaded.
  • What did NOT change: The global ~/.openclaw/.env (trusted operator surface), the host-env-security-policy.json exec-env sanitization layer, and all non-Windows code paths are unaffected.

Change Type (select all)

  • Bug fix
  • Security hardening

Scope (select all touched areas)

  • Auth / tokens
  • API / contracts

Linked Issue/PR

  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: BLOCKED_WORKSPACE_DOTENV_KEYS did not enumerate Windows shell trust-root variables, so the existing shouldBlockWorkspaceDotEnvKey guard passed them through.
  • Missing detection / guardrail: No test covered Windows-specific path-override variables in workspace .env loading.
  • Contributing context (if known): The blocklist is manually curated; Windows-specific variables were overlooked when the workspace .env security layer was first built.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/infra/dotenv.test.ts — "blocks Windows shell trust-root vars from workspace .env"
  • Scenario the test should lock in: Mixed-case and uppercase variants of all four blocked keys written to workspace .env must remain unset in process.env after loadWorkspaceDotEnvFile.
  • Why this is the smallest reliable guardrail: The blocklist check is a pure in-process Set lookup; a unit test against loadWorkspaceDotEnvFile directly is both fast and sufficient.
  • Existing test that already covers this (if any): None — this is new coverage.

User-visible / Behavior Changes

Windows users whose workspace .env previously set ProgramFiles, ProgramW6432, SystemRoot, or windir will no longer have those values applied. This was always unintended and unsafe; no legitimate workspace .env should set these variables.

Diagram (if applicable)

Before:
workspace .env (ProgramFiles=.\evil) -> shouldBlockWorkspaceDotEnvKey -> NOT BLOCKED -> process.env polluted

After:
workspace .env (ProgramFiles=.\evil) -> shouldBlockWorkspaceDotEnvKey -> BLOCKED (PROGRAMFILES in Set) -> process.env unchanged

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? Yes — Windows shell resolution can no longer be redirected via workspace .env
  • Data access scope changed? No
  • Risk + mitigation: Restricting these variables removes an attacker's ability to redirect pwsh.exe/powershell.exe resolution. No legitimate use case for setting these from an untrusted workspace .env.

Repro + Verification

Environment

  • OS: Windows (vulnerability path), Linux (test execution)
  • Runtime/container: Node 22+
  • Model/provider: N/A
  • Integration/channel (if any): N/A

Steps

  1. Create a workspace .env containing ProgramFiles=.\evil-pfiles.
  2. Call loadWorkspaceDotEnvFile on that file.
  3. Assert process.env.ProgramFiles and process.env.PROGRAMFILES remain undefined.

Expected

  • All four variable families blocked in all case variants.

Actual

  • Test passes; variables are not loaded into process.env.

Evidence

  • Failing test/log before + passing after — new test "blocks Windows shell trust-root vars from workspace .env" in src/infra/dotenv.test.ts provides direct before/after proof.

Human Verification (required)

  • Verified scenarios: Blocklist Set lookup logic; uppercase normalization in shouldBlockWorkspaceDotEnvKey (line 113) correctly handles ProgramFilesPROGRAMFILES; alphabetical ordering of new entries verified (PROGRAMFILES, PROGRAMW6432 before SYNOLOGY_*; SYSTEMROOT after SYNOLOGY_NAS_HOST; WINDIR after UV_PYTHON).
  • Edge cases checked: Mixed-case variants (ProgramFiles, ProgramW6432, SystemRoot, windir) all normalize to the blocked uppercase keys.
  • What you did not verify: Live Windows PTY execution end-to-end.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No — workspace .env keys that were previously (incorrectly) accepted are now blocked.
  • Migration needed? No

Risks and Mitigations

  • Risk: A user who legitimately relied on workspace .env to set these variables loses that capability.
    • Mitigation: These are Windows system path variables; there is no valid reason for a workspace .env to override them. The global ~/.openclaw/.env (trusted operator surface) is unaffected and can still set these if needed.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR adds four Windows shell path-resolution variables (PROGRAMFILES, PROGRAMW6432, SYSTEMROOT, WINDIR) to BLOCKED_WORKSPACE_DOTENV_KEYS in dotenv.ts, preventing a malicious workspace .env from redirecting pwsh.exe/powershell.exe resolution to an attacker-controlled binary. The implementation is correct — entries are alphabetically ordered and shouldBlockWorkspaceDotEnvKey already normalizes keys to uppercase before the Set lookup, so all case variants are handled.

Confidence Score: 4/5

Safe to merge — the blocking logic is correct and the security fix is sound; only minor test completeness suggestions remain.

The production change in dotenv.ts is straightforward and correct. The only findings are P2: the test doesn't write uppercase variants of three of the four variable families to the .env file (so those assertions pass trivially), and COMSPEC is a related Windows shell-redirect variable not yet covered. Neither represents a defect in the actual fix.

No files require special attention; both findings are minor test/coverage suggestions.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/infra/dotenv.test.ts
Line: 302-309

Comment:
**Uppercase variants not written to .env in test**

The .env content only includes mixed-case entries for `ProgramW6432`, `SystemRoot`, and `windir` — their all-uppercase counterparts (`PROGRAMW6432`, `SYSTEMROOT`, `WINDIR`) are never written to the file. After `clearEnv` deletes all 8 keys, the assertions for those three uppercase variants pass trivially (they were deleted and never loaded), so the test doesn't actually exercise blocking of those specific inputs. Adding the uppercase variants to the .env content would make the coverage complete.

```suggestion
            "ProgramFiles=.\\evil-pfiles",
            "PROGRAMFILES=.\\evil-pfiles-upper",
            "ProgramW6432=.\\evil-pw6432",
            "PROGRAMW6432=.\\evil-pw6432-upper",
            "SystemRoot=.\\fake-root",
            "SYSTEMROOT=.\\fake-root-upper",
            "windir=.\\fake-windir",
            "WINDIR=.\\fake-windir-upper",
```

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

---

This is a comment left during a code review.
Path: src/infra/dotenv.ts
Line: 73-79

Comment:
**`COMSPEC` not blocked**

`COMSPEC` is the Windows variable that tells the OS where to find `cmd.exe`. If a workspace `.env` sets `COMSPEC=.\evil.exe`, any code path that spawns a shell via `cmd /c …` (directly or through Node's `spawn`/`exec` with `shell: true`) would resolve to the attacker-supplied binary. Since the PR's stated goal is to block Windows shell trust-root variables, `COMSPEC` fits the same threat model as `WINDIR`/`SYSTEMROOT` and is worth adding to the blocklist for completeness.

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

Reviews (1): Last reviewed commit: "fix: address review feedback" | Re-trigger Greptile

Comment thread src/infra/dotenv.test.ts
Comment thread src/infra/dotenv.ts
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 29, 2026

Codex review: needs maintainer review before merge.

What this changes:

The branch adds COMSPEC to the workspace dotenv blocklist, adds mixed-case and uppercase Windows shell trust-root regression coverage, and records the security hardening in the changelog.

Maintainer follow-up before merge:

This is security-sensitive Windows command-resolution hardening with no discrete automated repair finding; the remaining action is maintainer/security review, validation, and reconciliation with related Windows hardening PRs.

Security review:

Security review cleared: The diff narrows untrusted workspace dotenv influence over Windows command resolution and adds tests/changelog only; it introduces no new dependency, workflow, script, permission, publishing, generated-code, or secret-handling risk.

Review details

Best possible solution:

Land or fold this narrow COMSPEC workspace-dotenv block with focused regression coverage and the changelog entry, while keeping the broader reg.exe and ACL command-path hardening decisions in the related Windows PRs.

Do we have a high-confidence way to reproduce the issue?

Yes. A source-level reproduction exists on current main: load a workspace .env containing ComSpec=..., observe it is not blocked by BLOCKED_WORKSPACE_DOTENV_KEYS, and then reach a Windows batch-command path that reads process.env.ComSpec.

Is this the best way to solve the issue?

Yes for the narrow remaining gap. Adding COMSPEC to the existing case-normalized workspace dotenv blocklist is the smallest maintainable fix for this PR's behavior; broader command-path hardening should remain in the related PRs.

Acceptance criteria:

  • pnpm test src/infra/dotenv.test.ts src/process/exec.windows.test.ts src/agents/shell-utils.test.ts
  • pnpm check:changed in Testbox before merge

What I checked:

  • current_main_blocklist_missing_comspec: Current main's BLOCKED_WORKSPACE_DOTENV_KEYS includes PROGRAMFILES, PROGRAMFILES(X86), PROGRAMW6432, SYSTEMROOT, and WINDIR, but there is no COMSPEC entry. (src/infra/dotenv.ts:16, 217273037b26)
  • case_normalized_workspace_guard: Workspace dotenv keys are uppercased before the explicit blocklist lookup, so adding COMSPEC covers both ComSpec and COMSPEC. (src/infra/dotenv.ts:120, 217273037b26)
  • workspace_dotenv_assignment_surface: Allowed workspace dotenv entries are later assigned into process.env when unset, so an unblocked ComSpec key can affect later process execution. (src/infra/dotenv.ts:179, 217273037b26)
  • comspec_execution_surface: The shared Windows batch-command wrapper still chooses process.env.ComSpec before falling back to cmd.exe, making workspace dotenv pollution relevant on current main. (src/process/exec.ts:110, 217273037b26)
  • powershell_trust_roots_already_blocked: PowerShell discovery reads ProgramFiles, PROGRAMFILES, ProgramW6432, SystemRoot, and WINDIR; these are already covered on current main, leaving COMSPEC as the distinct remaining gap in this PR. (src/agents/shell-utils.ts:6, 217273037b26)
  • current_main_test_gap: Current main's workspace runtime-control dotenv test includes Windows system path variables but does not include ComSpec or COMSPEC. (src/infra/dotenv.test.ts:689, 217273037b26)

Likely related people:

  • steipete: Current-main blame for the central dotenv, Windows exec, and PowerShell shell-selection files points to Peter Steinberger's f77acff934cc6 baseline/refactor commit, making him the safest maintainer routing candidate for this cross-cutting env and Windows execution surface. (role: recent maintainer; confidence: medium; commits: f77acff934cc; files: src/infra/dotenv.ts, src/infra/dotenv.test.ts, src/process/exec.ts)
  • pgondhi987: The current changelog and related PR context credit @pgondhi987 for the merged adjacent Windows workspace dotenv hardening in fix(infra): block Windows system path env vars from workspace .env injection #74456, which added the current ProgramFiles/SystemRoot/WINDIR coverage and taskkill path changes that overlap this PR's blocklist area. (role: adjacent merged hardening contributor; confidence: medium; commits: 4cc673c1e8a2; files: src/infra/dotenv.ts, src/infra/dotenv.test.ts, src/infra/restart-stale-pids.ts)

Remaining risk / open question:

Codex review notes: model gpt-5.5, reasoning high; reviewed against 217273037b26.

@mmaps
Copy link
Copy Markdown
Contributor Author

mmaps commented Apr 29, 2026

Addressed in 1ef1c423a859443fbba015f63a0e78027e7e8503.

Quoted comment from @clawsweeper:

Codex review: needs maintainer review before merge.

What this changes:

The PR adds COMSPEC, PROGRAMFILES, PROGRAMW6432, SYSTEMROOT, and WINDIR to the workspace dotenv blocklist and adds a targeted dotenv unit test for mixed-case and uppercase variants.

Maintainer follow-up before merge:

This is a plausible security hardening PR with no blocking finding, but it changes Windows command-resolution trust roots and overlaps a protected maintainer-labeled PR, so the next action is maintainer/security reconciliation and validation rather than an automated repair branch.

Review details

Best possible solution:

Land one canonical, security-reviewed Windows dotenv hardening that blocks these workspace-controlled shell trust roots, including COMSPEC, and reconcile it with #74456 so the repository does not carry parallel partial fixes.

Acceptance criteria:

  • pnpm test src/infra/dotenv.test.ts src/agents/shell-utils.test.ts src/process/exec.windows.test.ts
  • pnpm check:changed in Testbox before merge if maintainer approval proceeds

What I checked:

  • Current main dotenv blocklist missing requested keys: At current main, BLOCKED_WORKSPACE_DOTENV_KEYS contains existing credential, proxy, OpenClaw, and helper-path controls but does not include COMSPEC, PROGRAMFILES, PROGRAMW6432, SYSTEMROOT, or WINDIR. (src/infra/dotenv.ts:13, e8b82d1cf92f)
  • Workspace dotenv keys are normalized before blocking: shouldBlockWorkspaceDotEnvKey uppercases the normalized key before checking dangerous host env rules, the explicit set, prefixes, and suffixes, so a blocklist entry such as COMSPEC covers ComSpec and COMSPEC. (src/infra/dotenv.ts:108, e8b82d1cf92f)
  • Current main has Windows shell resolution inputs affected by the PR: resolvePowerShellPath reads ProgramFiles, PROGRAMFILES, ProgramW6432, SystemRoot, and WINDIR, while the Windows .cmd wrapper path uses process.env.ComSpec; that makes the requested dotenv blocklist hardening relevant to command resolution. (src/agents/shell-utils.ts:6, e8b82d1cf92f)
  • PR patch scope and security pass: The provided PR diff changes only src/infra/dotenv.ts and src/infra/dotenv.test.ts, adds no workflows, dependency sources, lockfiles, package scripts, publishing metadata, generated/vendor files, or new permissions, and the code-execution surface change matches the stated security purpose. (src/infra/dotenv.ts:19, 1ef1c423a859)
  • Related protected overlap: The provided related context and local ClawSweeper report show fix(infra): block Windows system path env vars from workspace .env injection #74456 is an open protected maintainer PR for overlapping Windows workspace .env hardening, though it is not identical because this PR also includes COMSPEC.
  • Feature-history routing: Available local blame for the central dotenv and Windows command-resolution files points to commit 65b09274... by Peter Steinberger, with the shallow checkout exposing that commit as the baseline for these files. (src/infra/dotenv.ts:13, 65b0927490b3)

Likely related people:

Remaining risk / open question:

  • No tests were run in this read-only sweep; src/infra/dotenv.test.ts and the changed gate still need maintainer validation before merge.
  • The PR overlaps open protected fix(infra): block Windows system path env vars from workspace .env injection #74456 but is not an exact duplicate because it adds COMSPEC; a maintainer should decide whether to merge this, combine the coverage, or close one PR after a canonical fix exists.

Codex review notes: model gpt-5.5, reasoning high; reviewed against e8b82d1cf92f.

@drobison00 drobison00 merged commit b56bb9f into openclaw:main May 1, 2026
86 checks passed
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
…[AI-assisted] (openclaw#74460)

* fix: address issue

* fix: address PR review feedback

* changelog: PR openclaw#74460

---------

Co-authored-by: Devin Robison <[email protected]>
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