fix(cli): Windows PATH registration is unusable under Constrained Language Mode #59

Open
opened 2026-09-22 19:09:33 +00:00 by gabogg · 0 comments
Owner

Follow-up from the round-5 review of #47 (comment). Not a regression — #47's CLM handling never worked in the case it was written for.

Problem

_execute_path_operation (app/cli/adapters/windows_adapter.py) appends a WM_SETTINGCHANGE broadcast guarded by try { Add-Type ... } catch { Write-Output "CLM_FALLBACK" }, on the theory that Constrained Language Mode blocks Add-Type and the operator should then be told to open a new shell.

Under real CLM the script never reaches that guard. The registry block above it calls:

$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($regPath, $true)

CLM disallows method invocation on non-allowed .NET types, and Microsoft.Win32.Registry is not on the allowed list. The registry block has try ... finally with no catch, so the error propagates, PowerShell exits non-zero, and the operator gets:

[WARNING] Windows system PATH registration failed: ...

never the CLM warning. Conversely, when the catch does fire (e.g. Add-Type blocked by WDAC with no compiler available), the message asserts "Constrained Language Mode detected" and claims "Global PATH updated in registry" — which may be false.

Proposed fix

Rewrite the PATH read/write to use reg.exe instead of .NET registry types. reg.exe is an external binary, so it is unaffected by CLM:

  • read unexpanded: reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path (returns the raw REG_EXPAND_SZ value)
  • write preserving type: reg add ... /v Path /t REG_EXPAND_SZ /d "<value>" /f

This keeps AC4's REG_EXPAND_SZ preservation, which was the whole reason for avoiding [Environment]::SetEnvironmentVariable. The broadcast can stay as-is: under CLM it genuinely cannot run, and the CLM_FALLBACK warning would then be both reachable and accurate.

Parsing reg query output needs care (leading whitespace, the REG_EXPAND_SZ type token, values containing spaces). Worth a dedicated parser plus tests.

Acceptance

  • PATH read/write survives Constrained Language Mode
  • REG_EXPAND_SZ still preserved; %SystemRoot% not corrupted
  • CLM_FALLBACK only emitted when the registry write actually succeeded
  • Failure messages distinguish "blocked by CLM" from "registry write failed"
  • Verified on a real Windows Server 2019 host (see the validation issue)

Why ready-for-human

The change is specifiable, but nothing in this repo executes PowerShell, so it cannot be verified here. Shipping an unverifiable rewrite of the load-bearing path is what this issue exists to avoid repeating.

Related: #23, #47.


Triage resolution — 2026-09-23

Agent implementation and portable tests may proceed. Human Windows verification
is the merge gate, not a requirement to implement every part manually.

Retain the original PATH correctness requirements: successful registry access
under constrained-language execution, preservation of unexpanded values and
registry type, truthful broadcast-fallback reporting and distinct failure
diagnostics. Validate behavior rather than relying only on generated-script
substring assertions.

Before merge, the user must run the agent-prepared checklist on Windows Server
2019 under both normal and constrained-language execution and record the
results. Issue #60 tracks that verification. Linux tests alone cannot satisfy
the Windows acceptance criteria. A Windows CI runner is separate infrastructure
work and is not a prerequisite for beginning implementation.

Follow-up from the round-5 review of #47 ([comment](https://git.gaboggamer.online/gabogg/hikcentral/pulls/47#issuecomment-1117)). Not a regression — #47's CLM handling never worked in the case it was written for. ## Problem `_execute_path_operation` (`app/cli/adapters/windows_adapter.py`) appends a `WM_SETTINGCHANGE` broadcast guarded by `try { Add-Type ... } catch { Write-Output "CLM_FALLBACK" }`, on the theory that Constrained Language Mode blocks `Add-Type` and the operator should then be told to open a new shell. Under real CLM the script never reaches that guard. The registry block above it calls: ```powershell $key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($regPath, $true) ``` CLM disallows method invocation on non-allowed .NET types, and `Microsoft.Win32.Registry` is not on the allowed list. The registry block has `try ... finally` with **no `catch`**, so the error propagates, PowerShell exits non-zero, and the operator gets: > `[WARNING] Windows system PATH registration failed: ...` never the CLM warning. Conversely, when the `catch` *does* fire (e.g. `Add-Type` blocked by WDAC with no compiler available), the message asserts "Constrained Language Mode detected" and claims "Global PATH updated in registry" — which may be false. ## Proposed fix Rewrite the PATH read/write to use `reg.exe` instead of .NET registry types. `reg.exe` is an external binary, so it is unaffected by CLM: - read unexpanded: `reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path` (returns the raw `REG_EXPAND_SZ` value) - write preserving type: `reg add ... /v Path /t REG_EXPAND_SZ /d "<value>" /f` This keeps AC4's `REG_EXPAND_SZ` preservation, which was the whole reason for avoiding `[Environment]::SetEnvironmentVariable`. The broadcast can stay as-is: under CLM it genuinely cannot run, and the `CLM_FALLBACK` warning would then be both reachable and accurate. Parsing `reg query` output needs care (leading whitespace, the `REG_EXPAND_SZ` type token, values containing spaces). Worth a dedicated parser plus tests. ## Acceptance - [ ] PATH read/write survives Constrained Language Mode - [ ] `REG_EXPAND_SZ` still preserved; `%SystemRoot%` not corrupted - [ ] `CLM_FALLBACK` only emitted when the registry write actually succeeded - [ ] Failure messages distinguish "blocked by CLM" from "registry write failed" - [ ] Verified on a real Windows Server 2019 host (see the validation issue) ## Why `ready-for-human` The change is specifiable, but nothing in this repo executes PowerShell, so it cannot be verified here. Shipping an unverifiable rewrite of the load-bearing path is what this issue exists to avoid repeating. Related: #23, #47. --- ### Triage resolution — 2026-09-23 Agent implementation and portable tests may proceed. Human Windows verification is the merge gate, not a requirement to implement every part manually. Retain the original PATH correctness requirements: successful registry access under constrained-language execution, preservation of unexpanded values and registry type, truthful broadcast-fallback reporting and distinct failure diagnostics. Validate behavior rather than relying only on generated-script substring assertions. Before merge, the user must run the agent-prepared checklist on Windows Server 2019 under both normal and constrained-language execution and record the results. Issue #60 tracks that verification. Linux tests alone cannot satisfy the Windows acceptance criteria. A Windows CI runner is separate infrastructure work and is not a prerequisite for beginning implementation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gabogg/hikcentral#59
No description provided.