Skip to content

feat(mxc): pass driver_config denied_paths to MXC deniedPaths - #5033

Merged
shailendra-nv merged 3 commits into
NVIDIA:windowsfrom
jquesnelle:feat/5032-mxc-denied-paths/jquesnelle
Oct 30, 2026
Merged

shailendra-nv merged 3 commits into
NVIDIA:windowsfrom
jquesnelle:feat/5032-mxc-denied-paths/jquesnelle

Conversation

@jquesnelle

@jquesnelle jquesnelle commented Oct 29, 2026 •

Copy link
Copy Markdown

Summary

The MXC driver accepts an optional denied_paths list in driver_config.mxc and passes it to MXC as filesystem.deniedPaths, so a caller can hide files inside a filesystem grant, such as an agent profile's .env or auth.json. Preflight refuses entries MXC would resolve differently than requested, and with denied paths the driver tells MXC not to fall back to changing host file ACLs. Without the field, the MXC request is unchanged.

Related Issue

Closes #5032

Changes

  • MxcSandboxConfig (crates/openshell-driver-mxc/src/driver.rs) gains denied_paths: Vec<String> with #[serde(default)]; deny_unknown_fields stays, so an older driver rejects the field instead of ignoring it.
  • Each entry is normalized (backslashes, no repeated or trailing separators, case-insensitive duplicates removed). InvalidArgument for an entry that is not an absolute drive path (C:\...), has a ./.. component or a component ending in a dot or space, or contains a character Windows does not allow in file names.
  • check_denied_paths() runs in validate_sandbox_create() before any state is created:
    • InvalidArgument for an entry that equals or contains a filesystem grant MXC receives (policy read_write/read_only paths, the cwd\.openshell-runtime staging directory, the sandbox executable);
    • FailedPrecondition for an entry whose parent directory does not exist, naming the outermost missing directory to deny instead.
  • The grant check compares resolved paths (std::fs::canonicalize: junctions, symbolic links, 8.3 short names, on-disk case; an absent path resolves through its deepest existing ancestor), ignoring Unicode letter case, because MXC applies its overlap rules to resolved paths. Unresolvable parts are compared as written. The driver sends MXC the entries as written.
  • oneshot_config_json() (mxc.rs) adds "fallback": {"allowDaclMutation": false} when there are denied paths. MXC 1.0 defaults it to true, so on a host whose base container cannot enforce deniedPaths it would otherwise write deny entries into host file ACLs.
  • boundary_filesystem() builds the ProcessContainer filesystem rules that run_lifecycle_inner() built inline, now with the denied paths instead of an empty list.
  • Docs: denied_paths in the driver README and in docs/how-it-works/gateways/configuration.mdx next to the existing MXC --driver-config-json example.

The check runs at create time only; a policy update that adds filesystem paths reaches the workload through a new sandbox, which runs preflight again.

Why these rules

Measured with MXC 1.0.1 one-shot ProcessContainers (--probe tier base-container, baseContainerSupportsDenyPaths: true):

deniedPaths entry MXC 1.0.1
file inside a read-write grant read, overwrite, append and copy refused; a sibling stays readable
absent path, parent exists the workload cannot create it
absent path, parent missing launch fails: CreateProcessSecurityEnvironment failed (HRESULT = 0x80070003)
equal to a grant (any letter case, the grant's 8.3 name, a host junction to it) the whole grant is denied
directory containing a grant (also by 8.3 name) the nested grant keeps its access
file inside a grant, as listed by the workload name, size, timestamps and attributes stay visible; a hard link made on the host at another path stays readable
file inside a grant, opened by the workload through another spelling (8.3, case, ::$DATA, trailing dot, \\?\, \\.\, GLOBALROOT, volume GUID, a symlink it creates) refused

The two overlap shapes resolve in opposite directions and neither hides part of a grant, so preflight refuses both.

Why driver config

This follows RFC 0006: the MXC driver owns the nested schema and validation, the gateway forwards the mxc block unchanged, and the sandbox policy format does not change. Like command and cwd, it needs allow_driver_config = true, and it only removes access. #3221 put UI controls in a portable policy section with a driver capability; the deny list stays in driver config because its rules are MXC's and the Linux drivers' Landlock enforcement cannot remove a path below a grant. If you prefer the #3221 shape, the field can move to filesystem_policy with a capability and the validation stays as is. On Linux, the Docker and Podman drivers already cover this case by bind-mounting an empty read-only file over the path through their own driver config (gateway opt-in enable_bind_mounts). MXC has no mount primitive, so deniedPaths is its equivalent.

Testing

Windows 11 x64 build 26340.9767, MXC 1.0.1, base windows 5d1bfbf92; Windows checks through tasks/scripts/windows-msvc.ps1.

  • Checks appropriate to the affected code and behavior pass: windows:lint:x64, windows:test:x64 (6639 passed, 41 skipped), windows:build:x64; cargo fmt --check; Linux cargo clippy -p openshell-driver-mxc --all-targets -- -D warnings; markdownlint, license headers, check_docs_nav.py, fern check.
  • Unit tests added: nine in driver.rs (absolute-path and spelling rejections, grant overlap, missing parent, 8.3 names, junctions, Unicode case, unresolvable paths, and the MXC request JSON including fallback). The eight that compile on windows fail there; cargo test -p openshell-driver-mxc --lib passes 70.
  • E2E tests added: none; examples/run-mxc-e2e.ps1 default scenarios pass 7/7 before and after (with two local shims for this host, not part of this PR).
  • Live, gateway + CLI + MXC 1.0.1, workload granted work\ read-write running type profile\.env, echo x> profile\.env, echo x> profile\auth.json: all succeed without denied_paths; with denied_paths: [work\profile\.env, work\profile\auth.json] all print Access is denied., the host files are untouched and profile\notes.txt stays readable. denied_paths: [work], a missing parent, the 8.3 name of work's parent, and a host junction to work all fail sandbox create with the errors above. The MXC request carries "fallback": {"allowDaclMutation": false} only when there are denies.
  • Not run: ARM64.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable): not applicable; the driver README and gateway configuration page are updated.

A read-write grant covers everything below it, so a file the workload
must not see inside a granted folder, such as an agent profile's .env or
auth.json, is readable, and an absent one can be created there. The MXC
driver already builds MXC's filesystem.deniedPaths but always sends an
empty list.

Add an optional denied_paths list to the driver-owned driver_config.mxc
block and pass it to MXC. Preflight normalizes each entry (backslash
separators, no repeated or trailing separator, case-insensitive
duplicates removed) and rejects, before any sandbox state is created:

- entries that are not absolute paths starting with a drive letter, or
  that have a '.' or '..' component, a component ending in a dot or
  space, or a character Windows does not allow in file names;
- entries that equal or contain a filesystem grant (a policy grant, the
  generation staging directory under cwd, or the sandbox executable):
  MXC drops a grant equal to a denied path and keeps access to a grant
  nested inside a denied directory. MXC matches paths after resolving
  8.3 short names and junctions and ignoring letter case, so both sides
  are resolved with std::fs::canonicalize (through the deepest existing
  ancestor for absent paths) and compared ignoring Unicode letter case;
  a path that cannot be resolved is compared as written;
- entries whose parent directory does not exist, which MXC refuses at
  launch (CreateProcessSecurityEnvironment, HRESULT 0x80070003). The
  error names the missing directory to deny instead.

When the list is not empty, also send fallback.allowDaclMutation=false.
MXC 1.0 defaults it to true, and on a host whose base container cannot
enforce deniedPaths it would otherwise add deny entries to host file
ACLs.

The list is empty by default, so existing requests are unchanged.
Document the field in the driver README and the gateway configuration
page.

Signed-off-by: Jeffrey Quesnelle <emozilla@nousresearch.com>
@copy-pr-bot

copy-pr-bot Bot commented Oct 29, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@jquesnelle

Copy link
Copy Markdown
Author

@shailendra-nv could you review this one? It needs #5032 accepted and linked in the Development sidebar (Closes doesn't link on windows), and the test:windows label for the Windows lanes.

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

All contributors have signed the DCO ✍️ ✅
Posted by the DCO Assistant Lite bot.

@jquesnelle

Copy link
Copy Markdown
Author

I have read the DCO document and I hereby sign the DCO.

@shailendra-nv shailendra-nv 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.

Requesting changes for two reproduced cases where an accepted deny-list configuration still exposes the intended protected contents: renaming a writable parent directory, and a device-namespace junction grant that bypasses overlap validation. Before this PR, requests containing denied_paths were rejected; these findings concern the newly accepted configurations.

Validation on native Windows ARM64:

Suite Total Passed Failed Ignored
PR MXC library unit tests 70 70 0 0
Additional overlap regression 1 0 1 0
Total test executions 71 70 1 0

The library suite ran with cargo test --offline --locked --target aarch64-pc-windows-msvc -p openshell-driver-mxc --lib -- --test-threads=1. All nine new deny-path tests passed, so please clarify or correct the PR description's statement that eight fail.

Separate direct real-MXC probes reproduced both findings using dummy files, fallback.allowDaclMutation=false, and the driver's default ProcessContainer privilege setting. These probes used MXC 1.0.0+7bf21024; the author's MXC 1.0.1 environment was not verified. These were filesystem enforcement probes, not full gateway E2E tests. If a newer MXC release fixes either behavior, the driver needs to enforce that minimum version or capability before accepting the configuration.

@shailendra-nv
shailendra-nv self-requested a review October 29, 2026 18:13
MXC applies deniedPaths and filesystem grants to paths. The workload can
rename a directory when the directory and its parent are both writable
to it, and everything below that directory then sits at a path that no
denied path or read-only grant names. Measured with MXC 1.0.0 and 1.0.1:
with C:\work granted read-write and C:\work\profile\.env denied, the
workload renames profile and reads the file under the new name.

Preflight now rejects a denied path below such a directory: two or more
levels below a read-write grant root, inside a read-write grant nested in
another one, or below a read-only grant whose root is two or more levels
inside a read-write grant. The most specific grant or denied path decides
a directory's access. The error names the directory and says to deny it
or to move the entry directly into the read-write grant root. Entries
directly inside a read-write grant root whose parent the workload cannot
write, and entries below a read-only grant root that cannot be renamed,
are still accepted.

Grants written as \\.\C:\, \\?\C:\, \??\C:\, \\?\GLOBALROOT\Device\...
or \\?\Volume{...}\, which MXC resolves, are now resolved like drive
paths before the overlap and rename checks, so a device-path grant
through a junction no longer hides an overlap with a denied directory.
While denied_paths is not empty, a UNC grant (MXC grants no access
through UNC paths) or a grant that does not resolve to a local drive is
rejected.

wxc_exec_real gains a probe-gated test that passes seven layouts through
preflight and, for each one it accepts, renames each denied path and every
directory above it in a real ProcessContainer and checks that the denied
contents stay unreadable.

The docs state that MXC does not check the target of a rename against
deniedPaths: in a writable directory the workload can rename its own
file onto a denied name, which replaces a denied file or creates an
absent denied path without exposing the original contents.

Signed-off-by: Jeffrey Quesnelle <emozilla@nousresearch.com>
@jquesnelle

Copy link
Copy Markdown
Author

Thanks for the review and the repros. Both findings are fixed in e01a7cf59, a new commit on top of 934fc4b0f, so the change is easy to see. This comment covers what changed since the description, which describes the first commit.

  • Renames: reproduced on MXC 1.0.1 and 1.0.0, and neither version prevents it. On both, a directory can be renamed when it and its parent are writable to the workload. Preflight now rejects any denied path that has such a directory above it.
  • Device-namespace grants: the spellings MXC resolves are resolved before comparing. With denies present, UNC grants and grants that don't resolve are rejected.
  • What still isn't covered: MXC doesn't check rename targets against deniedPaths. In a writable directory, the workload can replace a denied file, or create an absent denied path, by renaming its own file onto the name. It still can't read the original. So the description's "cannot write a denied file or create a denied path" holds only for writes by name; the README and configuration page now say this and suggest denying the directory instead.
  • Read-only grants, outside this PR: the same path-based behaviour makes a read-only grant nested two or more levels inside a read-write grant ambiguous. With C:\work read-write and C:\work\team\shared read-only, the workload can rename team, and shared's files are then writable at the new path (measured with MXC 1.0.1 and 1.0.0 directly, with or without denied_paths). This PR rejects denied paths below such a read-only grant but leaves read-only grants themselves as they are, since that would reject policies that work today. Should preflight apply the same rule to read-only grants, or warn, or should this wait for MXC? I'm happy to follow up in whichever direction you prefer.
  • Tests:
    • 14 denied-path unit tests, 5 of them new, plus a probe-gated real-MXC test. The real-MXC test fails on the first commit and passes now, on 1.0.1 and 1.0.0.
    • Each new rule has a mutation check.
    • Locally on x64: Windows lint, test and build, Linux clippy and the default E2E scenarios pass. windows:test:mxc-real passes 12 of 14; the two network tests that run run-mxc-e2e.ps1 fail on this host on windows too, because the harness doesn't see the gateway's listener here. ARM64 isn't tested.
  • "Eight fail": that sentence was about the base branch. On windows, where the field doesn't exist, the request-JSON test doesn't compile and the other tests all fail. On the first commit, 5 of the 14 fail; on this head all 14 pass.

#5032 still needs state:accepted and a link from this PR's Development sidebar for the acceptance check, plus test:windows for the Windows lanes.

Copy link
Copy Markdown
Collaborator

/ok to test

Copy link
Copy Markdown
Collaborator

/windows-ci

@copy-pr-bot

copy-pr-bot Bot commented Oct 30, 2026

Copy link
Copy Markdown

/ok to test

@shailendra-nv, there was an error processing your request: E1

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/1/

Copy link
Copy Markdown
Collaborator

/ok to test e01a7cf

@shailendra-nv shailendra-nv added the test:windows Run native Windows x64 and ARM64 lint/tests on PR mirrors label Oct 30, 2026 — with ChatGPT Codex Connector
@sam-india-007

sam-india-007 commented Oct 30, 2026 •

Copy link
Copy Markdown

Windows ARM64 CI: ✅ passed

Commit e01a7cf, requested by @shailendra-nv, 1h 13m, full log

Step Result Duration
if ($env:PR_NUMBER) { Import-Module .\scripts\pr-ci\OpenShellPrCi.psm1; Assert-PrCiVariables -PrN... ✅ passed 0s
& .\scripts\setup-windows-arm64-runner.ps1 -VerifyOnly ✅ passed 3s
$env:PATH = "$(Split-Path -Parent $env:RUSTC_WRAPPER);$env:PATH"; $found = (Get-Command sccache -... ✅ passed 0s
New-Item -ItemType Directory -Force (Split-Path -Parent $env:SCCACHE_ERROR_LOG) | Out-Null; & $en... ✅ passed 2s
& $env:RUSTC_WRAPPER --zero-stats ✅ passed 0s
& .\scripts\checkout-openshell.ps1 -Ref $env:OPENSHELL_REF ✅ passed 4s
& .\scripts\set-openshell-version.ps1 -Version $env:OPENSHELL_VERSION ✅ passed 0s
Set-Location $env:OPENSHELL_CHECKOUT_DIR ✅ passed 0s
mise run --skip-tools windows:check:arm64 ✅ passed 4m 38s
mise run --skip-tools windows:build:arm64 ✅ passed 22m 32s
mise run --skip-tools windows:test:arm64 ✅ passed 21m 14s
mise run --skip-tools windows:test:unsupported:arm64 ✅ passed 23m 15s
mise run --skip-tools windows:artifacts ✅ passed 2s
& "$env:CI_PROJECT_DIR\scripts\package-windows-release.ps1" ✅ passed 40s

shailendra-nv commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Validation at e01a7cf5900f4d333cb6a55aebc41f1ea08f1296:

  • Native Windows ARM64: release build, workspace check and lint passed; 6,644 workspace tests passed, 43 skipped.
  • Gateway E2E: 7 standard scenarios and 3 focused denied-path scenarios passed.
  • Real MXC: 13 tests executed successfully, 1 runtime skip (iso_lifecycle_round_trip, legacy backend unavailable). This includes the seven-layout denied-path rename regression.
  • Unsupported-driver contracts and all four selective feature combinations passed. The two original review threads are resolved with verification details.
  • Branch Checks passed after /ok to test.

CI and merge status:

  1. Windows MSVC CI failed on both architectures in persistence::tests::sqlite_concurrent_writes_return_their_own_resource_version (database is locked); an ARM64 retry reproduced it. Each run passed the other 6,643 tests. This is the existing failure from test(windows): Windows MSVC test suite fails on main #4861, fixed on main by test(windows): repair the Windows MSVC test suite #4862 but absent from the windows base. The upstream Windows-specific test adjustment needs to reach this branch.
  2. /windows-ci finished successfully for this exact head: compile check, release build, 6,644 workspace tests (43 skipped, no retry/flaky markers), all contract/feature checks, and both release packages passed. The reporting job published its log, but failed to publish the GitHub commit status with HTTP 404; the pipeline result was verified directly.
  3. Accepted issue feat(mxc): hide paths inside filesystem grants with driver_config.mxc.denied_paths #5032 is now linked, with windows retained as the target branch. PR Issue Acceptance passed.
  4. An approving review is recorded, but the listed-maintainer approval gate still fails. A review from someone listed in MAINTAINERS.md is required.

No PR source changes were made for this validation. This is a test-results update; merge readiness is still pending.

@shailendra-nv shailendra-nv linked an issue Oct 30, 2026 that may be closed by this pull request
2 of 7 tasks

@shailendra-nv shailendra-nv 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.

Approved for the MXC denied-path changes at e01a7cf. The writable-ancestor rename and device-namespace overlap findings from my earlier review are fixed, their regression coverage was verified on native Windows ARM64, and both review threads are resolved. The recorded validation includes 75 passing driver unit tests, 10 passing gateway E2E scenarios, and 13 successful real-MXC tests with one runtime skip. Accepted issue #5032 is now linked.

The documented MXC rename-target limitation remains: replacing a denied file by renaming another file onto its name can affect integrity, while the original denied contents remain unreadable; callers needing protection from replacement must deny the containing directory.

This approval supersedes my earlier change request. Merge remains pending the Windows CI failure in the unchanged SQLite concurrency test (the Windows adjustment from #4862 is absent from this base), completion of the separate Windows CI run, and the repository's required approval from a listed maintainer.

@shailendra-nv

Copy link
Copy Markdown
Collaborator

/ok to test 8b18f49

@shailendra-nv
shailendra-nv merged commit 7bc5754 into NVIDIA:windows Oct 30, 2026
72 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:windows Run native Windows x64 and ARM64 lint/tests on PR mirrors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(mxc): hide paths inside filesystem grants with driver_config.mxc.denied_paths

3 participants