Skip to content

fix(sandbox): keep Windows restricted-token SIDs narrow and fail closed on DenyRead - #1006

Open
euxaristia wants to merge 6 commits into
Gitlawb:mainfrom
euxaristia:fix/windows-sandbox-restricted-token-sids-v2
Open

fix(sandbox): keep Windows restricted-token SIDs narrow and fail closed on DenyRead#1006
euxaristia wants to merge 6 commits into
Gitlawb:mainfrom
euxaristia:fix/windows-sandbox-restricted-token-sids-v2

Conversation

@euxaristia

@euxaristia euxaristia commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Windows restricted-token sandboxing cannot support DenyRead without access-time confinement because omitting Users/Authenticated Users prevents system binaries from executing, while adding those groups reopens ambient write access. This PR rejects unsupported DenyRead configurations upfront before setup/token creation, migrates legacy SYNCHRONIZE DenyWrite ACEs in-place on upgraded hosts, randomizes shared-directory smoke test probes, and removes dead descendant scanning machinery.

Refs #639

Changes

  • Reject non-empty DenyRead profiles upfront in internal/sandbox/windows_command_runner.go and windows_setup_windows.go.
  • Narrow legacy DenyWrite ACEs containing SYNCHRONIZE in-place while preserving co-resident DenyRead in internal/sandbox/windows_acl_apply_windows.go.
  • Use collision-resistant probes via allocateSharedDirectoryProbe in internal/sandbox/runner_windows_integration_test.go.
  • Delete unused descendant scanning and path resolution files.

Test plan

  • go test ./internal/sandbox/... -count=1
  • go run ./cmd/zero-release build
  • go run ./cmd/zero-release smoke

Summary by CodeRabbit

  • Bug Fixes
    • Windows restricted-token and unelevated sandboxes now reject unsupported DenyRead configurations before setup or process launch, with clearer error messages.
    • Legacy Windows write-denial permissions are migrated to the current behavior while preserving read-denial protections.
    • Sandbox access controls no longer apply broader write restrictions to shared system directories.
    • Improved cleanup of outdated Windows access-control entries prevents stale permissions from persisting.

euxaristia and others added 6 commits August 31, 2026 03:27
Co-Authored-By: cairn-code <cairn-code@users.noreply.github.com>
…setup

Skip inherited ACEs when preserving read deny entries, correct syntax in the command runner error message, and evaluate unsupported DenyRead profiles before the elevation check in Windows sandbox setup.

Refs Gitlawb#640
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR rejects Windows restricted-token profiles containing DenyRead, narrows newly applied DenyWrite masks, and refreshes setup markers for legacy ACL migration.

  • Adds fail-closed DenyRead validation across manager, setup, and command-runner paths.
  • Reworks Windows DACL application to migrate matching legacy DenyWrite ACEs in place.
  • Adds randomized shared-directory smoke probes and removes descendant-scanning machinery.

Confidence Score: 3/5

The PR should not merge until legacy descendant ACL migration and the malformed denied-write smoke probes are corrected.

Setup refresh only migrates exact paths represented in the current plan, leaving previously propagated SYNCHRONIZE denies behind, while the new cmd.exe quoting can make confinement probes pass because of malformed redirects rather than enforced access denial.

Files Needing Attention: internal/sandbox/windows_acl_apply_windows.go; internal/sandbox/runner_windows_integration_test.go

Important Files Changed

Filename Overview
internal/sandbox/windows_acl_apply_windows.go Adds exact-path in-place ACL migration, but leaves legacy descendant ACEs outside the migration path.
internal/sandbox/runner_windows_integration_test.go Adds broader smoke probes, but command quoting can make denied writes fail syntactically and probe cleanup does not establish ownership.
internal/sandbox/windows_command_runner.go Consistently rejects unsupported restricted-token DenyRead profiles before persistent setup or command launch.
internal/sandbox/windows_setup.go Bumps the setup marker schema and adds early DenyRead rejection, though the resulting refresh cannot migrate unplanned descendant ACEs.
internal/sandbox/windows_acl.go Extends ACL entry identity with inheritance shape and defines migration actions without introducing a current plan-generation path for revocation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Existing schema-v4 installation] --> B[Schema-v5 setup refresh]
    B --> C[Build current ACL plan]
    C --> D{Exact DenyWrite path and SID in plan?}
    D -->|Yes| E[Migrate matching ACE to narrow mask]
    D -->|No: legacy descendant| F[ACE is not visited]
    F --> G[Legacy SYNCHRONIZE deny remains]
Loading

Reviews (1): Last reviewed commit: "Migrate legacy Windows DenyWrite ACEs, r..." | Re-trigger Greptile

Comment on lines +226 to +241
if entry.Action == WindowsACLDenyWrite {
// Replace any pre-existing broader DenyWrite mask (e.g. from
// builds that included SYNCHRONIZE) with the current narrow
// mask. We patch the mask in-place within a DACL copy rather
// than filtering the old ACE and re-adding via ACLFromEntries,
// because SetEntriesInAcl merges DENY entries for the same
// SID — which would combine the new DenyWrite with any
// co-resident DenyRead into a single deny-all ACE.
if baseDACL != nil && windowsHasExplicitDenyWriteForSID(baseDACL, sid) {
migrated, err := windowsMigrateDenyWriteInDACL(baseDACL, sid)
if err != nil {
return nil, nil, err
}
baseDACL = migrated
continue
}

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 Legacy descendant ACEs remain

On upgraded hosts with legacy DenyWrite ACEs propagated to descendants, migration only processes exact paths represented in the current plan. Those descendant ACEs retain SYNCHRONIZE, so directory access and synchronization can continue failing after the schema refresh.

Comment on lines 575 to +577
func deniedWriteCommand(marker string) []string {
return []string{"cmd.exe", "/d", "/s", "/c", "echo leaked>" + marker + " || exit " + strconv.Itoa(deniedWriteExitCode)}
return []string{"cmd.exe", "/d", "/s", "/c", "echo leaked>" + cmdQuote(marker) + " || exit " + strconv.Itoa(deniedWriteExitCode)}
}

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 Probe command quotes are corrupted

Every denied-write probe uses /d /s /c, which misses the runner's raw /d /c handling and causes syscall.EscapeArg to backslash-escape cmdQuote's inner quotes. The redirect then fails on a malformed target and returns the expected denial code even when sandbox confinement is broken, producing a false-positive smoke test.

Suggested change
func deniedWriteCommand(marker string) []string {
return []string{"cmd.exe", "/d", "/s", "/c", "echo leaked>" + marker + " || exit " + strconv.Itoa(deniedWriteExitCode)}
return []string{"cmd.exe", "/d", "/s", "/c", "echo leaked>" + cmdQuote(marker) + " || exit " + strconv.Itoa(deniedWriteExitCode)}
}
func deniedWriteCommand(marker string) []string {
return []string{"cmd.exe", "/d", "/c", "echo leaked>" + cmdQuote(marker) + " || exit " + strconv.Itoa(deniedWriteExitCode)}
}

Comment on lines +602 to +605
}
p := &sharedDirectoryProbe{path: probePath}
t.Cleanup(func() {
p.cleanup(t)

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 Probe cleanup lacks ownership

The allocator only observes that a shared-directory path is absent; cleanup later removes anything that occupies that path and ignores removal errors. This can delete another process's file created after allocation or silently leave the test's own probe behind.

Context Used: AGENTS.md (source)

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: e5c719b6-b332-4834-af73-460dcc04a7a6

📥 Commits

Reviewing files that changed from the base of the PR and between 1b5db17 and a84626f.

📒 Files selected for processing (15)
  • internal/sandbox/manager_test.go
  • internal/sandbox/profile.go
  • internal/sandbox/runner_windows_integration_test.go
  • internal/sandbox/windows_acl.go
  • internal/sandbox/windows_acl_apply_windows.go
  • internal/sandbox/windows_acl_apply_windows_test.go
  • internal/sandbox/windows_acl_test.go
  • internal/sandbox/windows_command_runner.go
  • internal/sandbox/windows_command_runner_test.go
  • internal/sandbox/windows_command_runner_windows.go
  • internal/sandbox/windows_runner.go
  • internal/sandbox/windows_setup.go
  • internal/sandbox/windows_setup_test.go
  • internal/sandbox/windows_setup_windows.go
  • internal/sandbox/windows_unelevated.go

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


Walkthrough

The Windows sandbox now rejects DenyRead profiles for both restricted-token tiers before setup, SID creation, or process launch. Windows ACL application migrates legacy deny-write entries, preserves read-deny entries, and avoids shared-path restrictions. Tests cover planning, setup, integration smoke behavior, and marker versions.

Changes

Windows sandbox enforcement

Layer / File(s) Summary
DenyRead validation and rejection
internal/sandbox/manager_test.go, internal/sandbox/profile.go, internal/sandbox/windows_command_runner*, internal/sandbox/windows_runner.go, internal/sandbox/windows_setup*, internal/sandbox/runner_windows_integration_test.go
Restricted-token command, planning, and setup paths reject profiles with DenyRead before provisioning or launch. Tests cover both elevated and unelevated tiers.
Windows ACL migration
internal/sandbox/windows_acl.go, internal/sandbox/windows_acl_apply_windows.go, internal/sandbox/windows_acl_apply_windows_test.go
ACL application filters experimental write-deny ACEs, narrows legacy deny-write masks, preserves read-deny ACEs, and supports non-inheritable entries.
ACL plan invariants
internal/sandbox/windows_acl_test.go
Tests verify that shared system paths receive no deny-write entries or capability-SID revocations and that inheritance variants remain distinct.
Integration probes and marker versions
internal/sandbox/runner_windows_integration_test.go, internal/sandbox/windows_setup.go, internal/sandbox/windows_setup_test.go, internal/sandbox/windows_unelevated.go
Windows smoke tests cover shared-directory write denial and probe cleanup. Setup marker versions advance for the updated ACL behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: ⚪ Minimal · up to a8462

The Windows sandbox changes have no substantiated merge-blocking issue; the reported lint diagnostics are advisory only.

Sequence Diagram(s)

sequenceDiagram
  participant SandboxManager
  participant WindowsCommandRunner
  participant WindowsSetup
  participant CapabilitySIDState
  SandboxManager->>WindowsCommandRunner: validate restricted-token DenyRead profile
  WindowsCommandRunner-->>SandboxManager: return unsupported-profile error
  WindowsSetup->>WindowsCommandRunner: validate permission profile
  WindowsCommandRunner-->>WindowsSetup: return status 1 before setup
  WindowsCommandRunner->>CapabilitySIDState: load or create state only after validation
Loading

Possibly related PRs

  • Gitlawb/zero#640: Modifies Windows restricted-token behavior and ACL handling for shared paths and capability SIDs.

Suggested reviewers: anandh8x, vasanthdev2004

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 55 functions across 15 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main changes: keeping Windows restricted-token SIDs narrow and rejecting unsupported DenyRead profiles.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

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.

1 participant