Skip to content

Install and enforce the workspace policy on Windows - #1262

Open
billiam-wang wants to merge 13 commits into
mainfrom
mdm-policy-windows
Open

billiam-wang wants to merge 13 commits into
mainfrom
mdm-policy-windows

Conversation

@billiam-wang

@billiam-wang billiam-wang commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

🥞 Stacked PR

Use this link to review incremental changes.


Worktree: ug-policy-windows | Branch: mdm-policy-windows

What did you change, and why?

Third PR in the stack, based on #1260. It brings the workspace policy to Windows, with the same rules as Linux and macOS:

  • ug configure, run elevated, installs, replaces or removes %ProgramData%\UnityGateway\managed-settings.json. Every user can read it; only administrators can write it.
  • A non-elevated configure fails with a "run as administrator" message.
  • Launches enforce the installed workspace and never write the file unless already elevated.
  • ug revert, run elevated, removes it.

The UnityGateway folder is locked down like Codex's ProgramData folders; links are refused. UG_SKIP_OS_SETTINGS and --user keep the per-user cache.

How do you know it works?

Unit tests cover install, replace, remove, enforcement and revert on Windows. The icacls steps are stubbed, so Windows CI is their first real run.

This pull request and its description were written by Isaac.

billiam-wang and others added 5 commits October 23, 2026 02:00
Explicit ug configure now installs the workspace policy at
/etc/unitygateway/managed-settings.json on Linux and macOS, using a
non-interactive-sudo-capable writer. Unchanged policy writes nothing, and
no-config or feature-disabled removes the installed file. A failed install
raises ManagedPolicyInstallDenied with an actionable message. Launches keep
reading the per-user cache and never touch the machine file.

Co-authored-by: Isaac <no-reply@databricks.com>
Launches read the policy installed at /etc/unitygateway/managed-settings.json,
refuse a selection for another workspace, and refresh at most every five
minutes using user-owned stamps in ~/.ucode/refresh_ts.json. Non-root launches
never install or remove the policy. The per-user cache remains only for
Windows, UG_SKIP_OS_SETTINGS, and --user. Configure restores the saved
workspace on a denied install, and other agents refuse early on a machine
managed elsewhere. Also adds ug export, dry-run workspace adoption, and the
CUJ recorder and harness reading the installed policy.

Co-authored-by: Isaac <no-reply@databricks.com>
`ug revert` deletes /etc/unitygateway/managed-settings.json with sudo;
nothing else writes that file. Without sudo it warns and keeps it, and
Windows, UG_SKIP_OS_SETTINGS and `--user` leave it alone.

Test cleanup keeps a backstop removal in its own finally, so a failed or
skipped revert can no longer leave the policy behind for later cases.

Co-authored-by: Isaac <no-reply@databricks.com>
# Conflicts:
#	docs/managed-workspace-policy.md
#	src/ucode/managed_config.py
#	tests/test_managed_config.py
Windows now holds the installed workspace policy like Linux and macOS:
%ProgramData%\UnityGateway\managed-settings.json, readable by every user.

- An elevated `ug configure` writes, replaces, or removes it atomically;
  the UnityGateway folder is secured to Administrators (standard users
  read only) and links are refused. An elevated `ug revert` removes it.
- A non-elevated configure fails with an administrator hint; a launch
  never elevates, and a non-elevated launch applies the fetched policy
  for that run when nothing is installed.
- Launches on Windows read and enforce only the ProgramData file.
- The per-user cache remains only for UG_SKIP_OS_SETTINGS and --user.
- The integration preflight and cache-path helpers include the Windows
  policy path.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@billiam-wang

Copy link
Copy Markdown
Collaborator Author

/ug-review

Comment on lines +937 to +941
if current_os() is OS.WINDOWS:
hint = (
"Run `ug configure` from an elevated terminal (Run as administrator), or ask "
"your IT team or an administrator to run it."
)

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.

bug · high
will this break current non-admin windows users? A non-elevated ug configure used to succeed via the per-user cache; now it raises ManagedPolicyInstallDenied. Once installable, load_managed_state also never reads the existing per-user cache. can we fall back to the per-user cache when not elevated?


On Windows the path is the protected ProgramData file, never the POSIX one.
"""
return current_os() in (OS.LINUX, OS.MACOS, OS.WINDOWS)

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.

security · high
what happens if a standard user creates ProgramData\UnityGateway\managed-settings.json before any admin install? ProgramData lets users create subfolders, and _installed_config trusts the file without checking owner or ACL. On a shared machine, user A can plant {workspace: <B's workspace>, config: <hooks/MCP>}. When B launches, the non-admin write fails and the code continues with the installed policy, so A's hooks run as B. can we check that Administrators or SYSTEM own the file and folder before trusting it?


def test_only_the_program_data_file_is_read(self, tmp_path):
# A POSIX-style path under a drive root is never consulted on Windows.
planted = tmp_path / "etc" / "unitygateway" / "managed-settings.json"

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.

test · low
does this test anything? MANAGED_CONFIG_PATH is already patched to the ProgramData path, so nothing reads tmp_path/etc/... and the test passes whether or not a POSIX path would be read. can we drop it, or patch the real POSIX path instead?

billiam-wang and others added 3 commits October 22, 2026 20:46
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Removal hands the replace script the exact bytes of the installed file,
so a file with a BOM or CRLF endings still matches its byte-for-byte
check. Add a regression test and a comment on why the copy is binary.

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions

Copy link
Copy Markdown
Contributor

Polly AI Review

Blocking issues

Two admin-only-write violations are listed under Security vulnerabilities.

Security vulnerabilities

  1. Explicit user grants survive hardening — src/ucode/managed_files.py:1533.
    can we replace the folder DACL? /inheritance:r and /grant:r preserve an explicit user-SID grant, so a pre-created folder leaves the installed policy writable by that user.

  2. Matching contents bypass hardening — src/ucode/managed_files.py:1291.
    can elevated configure repair permissions when bytes match? the earlier return skips all hardening, leaving a user-precreated matching policy writable after configure succeeds.

For both fixes, extend the native Windows permission-test pattern in tests/test_managed_files.py::test_standard_users_can_only_read_secured_codex_folder: cover an explicit inheritable user grant and an insecure, byte-identical existing policy. Preserve non-admin no-ops, dry runs, and unchanged-launch behavior.

Non-blocking notes

  • Reader trust — src/ucode/managed_config.py:992.
    does the Windows reader validate ownership, write permissions and reparse points before trusting ProgramData policy? the stacked reader implementation was unavailable, so this remains unverified.

  • Disconnected scenario — tests/test_managed_config.py:1471.
    can test_only_the_program_data_file_is_read connect its planted file to the actual POSIX lookup path? currently the file is never consulted, so the assertion cannot catch that regression.

  • Duplicate coverage.
    can we consolidate test_policy_path_is_under_program_data into the writer path test, and test_elevated_launch_writes_like_root into the existing writer lifecycle case?

  • Revert guidance — src/ucode/managed_files.py:1278.
    can the elevation error name the calling command? non-elevated revert currently tells users to run configure before adding its own revert hint.

Approach

Reusing the Windows replacement path and extracting the Codex folder-hardening helper is consistent with existing patterns. The security requirement needs an exact allowlisted DACL and explicit permission validation independent of content equality.

Summary

Diff size (computed): 10 files; +461 / -99 lines (560 changed text lines); 0 binary files.

Category Files Added Deleted Share of changed text lines
Tests and test support 5 297 24 57.3%
Documentation 2 18 11 5.2%
Dependencies and lockfiles 0 0 0 0.0%
Implementation / other 3 146 64 37.5%

Shares use additions + deletions, including generated text and lockfiles. Binary files have no line count. Tests include fixtures/helpers under test paths and colocated test/spec files; categories are path-based, not coverage measurements.

Changes

The PR adds Windows machine-policy installation and enforcement, elevation guidance, revert handling, integration-runner safeguards, documentation, and focused test support. The shared ACL-helper extraction directly supports this outcome.

The implementation is proportional to the requested Windows increment. No substantial unrelated refactor or unnecessary product scaffolding was found. The main reduction opportunity is overlapping test coverage, assessed below.

Tests

Test-by-test assessment

Static assessment only: no tests were executed. The frozen diff’s checksum matched, and both trusted policy files were read in full.

The trusted checkout does not contain the supplied base/head commits or the complete stacked machine-policy implementation. Consequently, unchanged caller behavior and additional consumers of the modified fixture could not be fully verified.

Test / case Behavior protected Layer Needed? Action / rationale
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_policy_path_is_under_program_data Windows policy location Unit consolidate Duplicates the writer’s path-selection assertion; retain that lower-layer test.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_elevated_configure_installs_replaces_and_removes Configure installation, replacement and removal Component keep Protects orchestration, returned configuration and filesystem results beyond writer dispatch.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_non_elevated_configure_requires_an_administrator Configure denial without installation Component keep Checks actionable guidance and absence of side effects.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_non_admin_launch_with_nothing_installed_applies_fetched_policy First launch applies remote policy without installing it Component keep Distinct boundary between applying configuration and writing machine policy.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_non_admin_launch_never_replaces_the_installed_policy Non-admin refresh preserves installed policy Component keep Checks effective policy, unchanged contents and no ACL operations.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_unchanged_launch_does_not_write Elevated unchanged launch remains write-free Component keep Distinct orchestration guarantee; permission repair should target explicit configure.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_foreign_workspace_is_refused Installed workspace cannot be bypassed Component keep Meaningful workspace-isolation assertion.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_only_the_program_data_file_is_read Intended rejection of the POSIX policy location Component consolidate The planted file is disconnected from lookup. Correct that connection or fold the assertion into path-selection coverage.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_skip_mode_uses_the_per_user_cache Windows opt-out uses the user cache Component keep Explicitly protects the Windows compatibility escape hatch.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_elevated_revert_removes_the_policy Revert removes installed machine policy Component keep Covers public revert orchestration rather than writer removal alone.
tests/test_managed_config.py::TestWindowsInstalledPolicy::test_non_elevated_revert_warns_and_keeps_the_policy Failed revert preserves installed policy Component keep Meaningful preservation and warning assertions; current matching does not distinguish configure from revert guidance.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_path_is_under_program_data Windows path selection Unit keep Appropriate canonical layer for the location assertion.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_posix_path_is_unchanged_off_windows Existing POSIX location remains unchanged Unit keep Protects compatibility during path-selector extraction.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_elevated_run_installs_replaces_and_removes Writer lifecycle and temporary-file cleanup Component keep Exercises actual filesystem replacement with ACL commands stubbed.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_folder_is_given_to_administrators_with_read_for_users Ownership and ACL command construction Unit keep Useful command contract, but it currently encodes the vulnerable sequence and cannot prove effective permissions.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_unsecured_folder_blocks_the_write ACL-command failure prevents installation Component keep Protects failure propagation and prevents an unsecured write.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_standard_user_gets_the_admin_hint — all four allow_sudo × desired_text cases Non-admin replacement/removal is denied regardless of sudo flag Component keep Small boundary matrix with unchanged-content and no-ACL-operation assertions.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_elevated_launch_writes_like_root Elevated write without sudo permission Component consolidate The first lifecycle-test operation already exercises elevated installation with default allow_sudo=False.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_unchanged_policy_needs_no_elevation Standard-user unchanged writer call is a no-op Component keep Valid writer behavior; does not establish explicit configure’s security checks.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_skip_mode_is_rejected Direct writer respects OS-settings opt-out Unit keep Protects the workspace writer’s own gate.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_dry_run_writes_nothing Dry run has no filesystem or ACL effects Component keep Meaningful side-effect assertions.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_refuses_an_unexpected_target Workspace writer restricts its target Unit keep Distinct allowlist from the agent-managed file paths.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::test_refuses_a_linked_folder Directory-link rejection before writing Component keep Checks that the linked destination remains untouched; does not establish native junction behavior.
tests/test_integration_runner.py::test_windows_policy_paths_match_pinned_agent_locations — modified Runner preflight includes UG machine policy Unit keep Extends the existing safety-path assertion without another harness.
tests/test_managed_config.py::TestPolicyWithoutInstallation::not_installable — modified linux and Windows-with-opt-out cases Cache-only behavior under opt-out Component fixture keep Correctly removes the former assumption that all Windows installations are unmanaged.
tests/test_managed_config.py::TestPolicyWithoutInstallation::test_per_user_cache_is_private — affected fixture cases Cache privacy under opt-out Component keep Existing assertion remains relevant to the changed fixture matrix.
tests/test_managed_config.py::TestPolicyWithoutInstallation::test_export_finds_the_per_user_cache_without_a_saved_workspace — affected fixture cases Export discovers opted-out user cache Component keep Existing behavior remains necessary after Windows installation support.
tests/test_managed_config.py::TestPolicyWithoutInstallation::not_installable — other unchanged consumers outside available hunks Additional cache-only scenarios Component uncertain Their names and bodies were unavailable in the trusted checkout; full fixture-consumer assessment is incomplete.
tests/test_managed_config.py::TestPolicyWithoutInstallation::test_windows_ignores_a_planted_posix_policy_file — removed Previous blanket Windows policy rejection Component remove That platform contract is obsolete; the replacement scenario’s lookup weakness is noted above.
tests/test_managed_config.py::test_revert_leaves_the_policy_where_ug_does_not_manage_it — Windows parameter removed Revert preserves policy under opt-out Component keep Retained opt-out assertion remains valid; dedicated Windows revert cases replace the removed expectation.
tests/test_managed_config.py::TestWindowsInstalledPolicy::windows, set_admin, _REAL_UPDATE_WORKSPACE_POLICY, _REAL_WINDOWS_REPLACE — added support Isolated paths, elevation and real writer dispatch Component support keep Bounded setup exercises filesystem behavior, but stubbing ACL execution cannot establish Windows security.
tests/test_managed_config.py::TestWindowsInstalledPolicy and TestPolicyWithoutInstallation — documentation edits Explain installed versus opted-out behavior Test documentation keep Supports the changed platform contract without adding runtime setup.
tests/test_managed_files.py::TestWindowsWorkspacePolicyWriter::policy_path and class documentation — added support Isolated Windows writer scenarios Component support keep Reuses the replacement implementation rather than introducing a separate process harness.
tests/integration/utils/managed.py::is_managed_config_control_plane_cache — modified Separates UG machine-policy state from agent-owned files Integration helper keep Necessary classification change for integration comparisons.
tests/README.md — modified Describes Windows policy coverage and stubbed ACL execution Documentation keep Records the coverage boundary; it is not native permission evidence.

Existing Codex tests cover shared ACL-command failure, directory-link rejection, and native Windows ownership/access checks. Those patterns are sufficient foundations; a live-agent E2E is unnecessary for these filesystem guarantees.

The demonstrated coverage gaps are insecure pre-existing explicit ACLs and byte-identical insecure installations. The requested regressions are described under Security vulnerabilities. Native Windows ACL and junction behavior remains unverified; Linux-hosted command stubs do not establish it. Windows --user behavior and full fixture-consumer coverage also remain unverified from the available stacked context.

For human verification, use a disposable Windows machine:

  1. As a standard user, pre-create the UG folder with an explicit inheritable grant to that user.
  2. Run configure elevated, then verify that the standard user can read—but cannot modify, delete or replace—the policy.
  3. Repeat with a user-writable policy whose contents already match the desired configuration.
  4. Confirm non-admin launch leaves policy unchanged and non-admin revert preserves it with correct elevation guidance.

Scope

No clearly unrelated changes were found. Windows policy handling, runner safeguards, integration-state classification, documentation and the shared ACL-helper extraction all support the stated outcome. The unavailable stacked reader implementation limits verification of policy trust on read; it is an unresolved security question, not evidence of unrelated scope.


Automated review by Polly · workflow run

billiam-wang and others added 3 commits October 23, 2026 03:52
- The --agent refusal says the selection stays on the installed workspace
  and to rerun configure from a terminal with sudo access.
- The launch refusal tells skip-mode users to switch without
  UG_SKIP_OS_SETTINGS, which is the only way the policy can change.
- _launch_workspace_state resolves the target workspace once.
- The configure integration test checks the policy folder is root-owned.

Co-authored-by: Isaac <no-reply@databricks.com>
@billiam-wang
billiam-wang marked this pull request as ready for review October 23, 2026 04:07

@JTSIV1 JTSIV1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

Base automatically changed from mdm-policy-read to main October 24, 2026 02:03
#1259 and #1260 landed as squash commits; take main's tree and reapply only
this PR's Windows changes on top.

Co-authored-by: Isaac <no-reply@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants