Skip to content

refactor(backends): unify the library backends on SandboxBackend + route binaries via Runner#555

Merged
MGudgin merged 6 commits into
microsoft:mainfrom
caarlos0:unify-backends
Jun 24, 2026
Merged

refactor(backends): unify the library backends on SandboxBackend + route binaries via Runner#555
MGudgin merged 6 commits into
microsoft:mainfrom
caarlos0:unify-backends

Conversation

@caarlos0

@caarlos0 caarlos0 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

📖 Description

Part 2 of 3 splitting #524. This PR migrates the three in-process backends — Seatbelt (macOS), Bubblewrap (Linux), and Windows ProcessContainer (AppContainer + BaseContainer) — onto the SandboxBackend / SandboxProcess interfaces from #554, and routes the executor binaries (wxc-exec, lxc-exec, mxc-exec-mac) through the generic Runner<B> adapter. The old per-backend run-to-completion logic is removed; each backend now exposes only spawn(), and Runner provides the single ScriptRunner the binaries dispatch on.

Each backend gains a streaming handle with whole-process-tree termination (Unix process-group SIGKILL; Windows job-object terminate) and a uniform io::ErrorKind::TimedOut on scriptTimeout.

Intentional behavior changes to the existing binaries (please review):

  • Seatbelt now always env_clear()s the child (was: only when process.env was non-empty), aligning the binary with the SDK's documented "host env is not inherited" contract.
  • Seatbelt resolves an empty process.cwd to a policy read-write path (or /) instead of the launcher's cwd.
  • Seatbelt/Bubblewrap inherit the executor's own stdio (StdioMode::Inherit) — Seatbelt no longer allocates a private pty, and Bubblewrap no longer forces stdin to /dev/null or post-exit-captures stdout/stderr (it streams live).
  • BaseContainer now places the child in a UiJobObject for tree-kill (it had none); the child is created suspended, assigned to the job, then resumed so no descendant can escape the kill window.
  • kill() is a no-op once the child has been reaped, so it never signals a recycled pid/process-group.

The macOS Seatbelt characterization suite is updated to assert the new behavior; the LXC and Seatbelt backend docs are updated to match. The default LXC path keeps its native pty and is unaffected.

🔗 References

🔍 Validation

  • macOS (toolchain 1.93): seatbelt_common 36 unit tests pass; the 7 Seatbelt e2e characterization tests pass (env-clear, cwd, exit code, streaming, timeout tree-kill).
  • cargo clippy --all-targets -- -D warnings clean for appcontainer_common + wxc (Windows target) and bwrap_common (Linux target); mxc_darwin builds.
  • cargo fmt --all -- --check clean.

✅ Checklist

📋 Issue Type

  • Bug fix
  • Feature
  • Task
Microsoft Reviewers: Open in CodeFlow

caarlos0 and others added 2 commits June 24, 2026 09:43
…d / SandboxProcess / Runner)

Introduce the shared, no-pty execution surface that the containment backends
and a future in-process library build on. Purely additive: no existing code
path uses these yet, so behavior is unchanged.

- `SandboxProcess` — a handle to a running sandboxed child: `take_stdin` /
  `take_stdout` / `take_stderr`, `try_wait`, `id`, `kill` (process-tree), and
  `wait` (drains any untaken stdio, honors `scriptTimeout`), plus stdout/stderr
  closers for abandoning a backgrounded-descendant-held read without a kill.
- `SandboxBackend` — `validate` + `spawn(request, logger, StdioMode) ->
  SandboxProcess` + a `diagnose_exit` hook; `StdioMode::{Pipes, Inherit}`.
- `Runner<B>` — the generic adapter bridging any `SandboxBackend` to the
  run-to-completion `ScriptRunner` (spawn `Inherit`, then `wait`).
- `StreamCloser`, `group_kill` (Unix leader-first SIGKILL of the child's group),
  and `wait_with_timeout` (adaptive 1ms->50ms backoff poll).
- `interruptible_reader` (Unix self-pipe + `poll`) and the Windows pipe helpers
  in `process_util` (`InterruptiblePipeReader` / `PipeReadCanceller` /
  `create_std_pipes`) for out-of-band-cancelable streaming reads.
- `FailurePhase::Timeout` so a timeout is distinguishable from other failures.

The library backends and executor binaries are migrated onto this surface in a
follow-up PR, and the importable `mxc-sdk` crate is built on top of it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
…ute binaries via Runner

Migrate the three in-process backends — Seatbelt (macOS), Bubblewrap (Linux),
and Windows ProcessContainer (AppContainer + BaseContainer) — onto the
SandboxBackend / SandboxProcess interfaces added in the previous PR, and route
the executor binaries (wxc-exec, lxc-exec, mxc-exec-mac) through the generic
Runner<B> adapter. The old per-backend run-to-completion logic is removed; each
backend now exposes only spawn(), and Runner provides the single ScriptRunner
the binaries dispatch on (spawn StdioMode::Inherit, then wait).

Each backend gains a streaming handle with whole-process-tree termination
(Unix process-group SIGKILL; Windows job-object terminate) and a uniform
io::ErrorKind::TimedOut on scriptTimeout.

Intentional behavior changes for existing binaries (call-outs for review):
- Seatbelt now always env_clear()s the child (previously only when process.env
  was non-empty), aligning the binary with the SDK's documented "host env is
  not inherited" contract.
- Seatbelt resolves an empty process.cwd to a policy read-write path (or "/")
  instead of the launcher's cwd.
- Seatbelt/Bubblewrap inherit the executor's own stdio (StdioMode::Inherit) —
  Seatbelt no longer allocates a private pty, and Bubblewrap no longer forces
  stdin to /dev/null or post-exit-captures stdout/stderr (it streams live).
- BaseContainer now places the child in a UiJobObject for tree-kill (it had
  none before); the child is created suspended, assigned to the job, then
  resumed so no descendant can escape the kill window.
- kill() is a no-op once the child has been reaped, so it never signals a
  recycled pid/process-group.

The macOS Seatbelt characterization suite is updated to assert the new env/cwd/
streaming/timeout behavior; the LXC and Seatbelt backend docs are updated to
match. The default LXC path keeps its native pty and is unaffected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 24, 2026 13:15
@caarlos0 caarlos0 requested a review from a team as a code owner June 24, 2026 13:15
@caarlos0 caarlos0 mentioned this pull request Jun 24, 2026
7 tasks

Copilot AI left a comment

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.

Pull request overview

This PR (part 2/3 of the #524 split, stacked on #554) migrates the in-process Seatbelt (macOS), Bubblewrap (Linux), and Windows ProcessContainer (AppContainer/BaseContainer) backends to the new SandboxBackend / SandboxProcess interfaces and routes the executor binaries through the generic Runner<B> adapter in wxc_common.

Changes:

  • Introduces handle-based sandbox execution (SandboxProcess, SandboxBackend, Runner) and shared helpers for timeouts, stream draining, and process-tree termination.
  • Refactors Seatbelt, Bubblewrap, and Windows ProcessContainer backends to expose spawn() only (streaming-capable) and removes old backend-specific run-to-completion logic from binaries.
  • Updates characterization tests and docs to reflect intentional Seatbelt behavior changes (env clearing, default cwd resolution, stdio inheritance) and adjusts Windows networking/job-object logic for correctness across threads and tree-kill.
Show a summary per file
File Description
src/testing/wxc_e2e_tests/tests/e2e_seatbelt_characterization.rs Updates Seatbelt characterization assertions for env-clearing and default-cwd behavior under the unified runner model.
src/core/wxc/src/main.rs Routes Windows AppContainer execution through Runner<B>.
src/core/wxc_common/src/sandbox_process.rs Adds the new cross-backend SandboxProcess / SandboxBackend interfaces and Runner adapter plus shared helpers.
src/core/wxc_common/src/process_util.rs Adds Windows pipe reader/writer and interruptible read-canceller primitives used by streaming backends.
src/core/wxc_common/src/models.rs Adds FailurePhase::Timeout to distinguish timeouts from other failures.
src/core/wxc_common/src/lib.rs Exposes the new sandbox_process module and unix interruptible_reader module.
src/core/wxc_common/src/interruptible_reader.rs Adds Unix interruptible pipe reader + canceller used by Seatbelt/Bubblewrap streaming stdio.
src/core/wxc_common/Cargo.toml Broadens libc dependency to all unix targets (not just Linux).
src/core/mxc_darwin/src/main.rs Routes macOS Seatbelt execution through Runner<B>.
src/core/lxc/src/main.rs Routes Bubblewrap execution through Runner<B> (Linux path).
src/Cargo.lock Removes mxc_pty dependency from seatbelt_common (Seatbelt no longer allocates a private PTY).
src/backends/seatbelt/common/src/seatbelt_runner.rs Refactors Seatbelt runner to implement SandboxBackend and return a streaming SandboxProcess handle.
src/backends/seatbelt/common/src/profile_builder.rs Updates comments to reflect inherited-TTY behavior; exposes tilde expansion helper for cwd resolution.
src/backends/seatbelt/common/Cargo.toml Drops mxc_pty dependency (Seatbelt no longer uses the PTY bridge).
src/backends/bubblewrap/common/src/bwrap_runner.rs Refactors Bubblewrap to SandboxBackend/SandboxProcess, enabling live streaming and tree-kill semantics.
src/backends/appcontainer/common/src/probe.rs Makes UiCapabilitySupport cloneable.
src/backends/appcontainer/common/src/network_manager.rs Refactors firewall COM initialization to be per-call/apartment-self-contained (thread-safe teardown).
src/backends/appcontainer/common/src/job_object.rs Adds UiJobObject::terminate for whole-process-tree termination.
src/backends/appcontainer/common/src/dispatcher.rs Wraps BaseContainer/AppContainer runners in Runner<B> during dispatch.
src/backends/appcontainer/common/src/base_container_runner.rs Refactors BaseContainer to SandboxBackend/SandboxProcess, adds job-object assignment and capture pipes for streaming.
src/backends/appcontainer/common/src/appcontainer_runner.rs Refactors AppContainer to SandboxBackend/SandboxProcess, adds capture pipes for streaming and fixes COM/thread-affinity issues in teardown.
docs/macos-support/seatbelt-backend.md Documents Seatbelt env-clearing and default working-directory behavior.
docs/lxc-support/lxc-backend.md Updates LXC env behavior wording (no longer references Seatbelt’s old conditional env-clearing behavior).

Copilot's findings

  • Files reviewed: 22/23 changed files
  • Comments generated: 3

Comment thread src/core/wxc_common/src/interruptible_reader.rs
Comment thread src/backends/seatbelt/common/src/seatbelt_runner.rs
Comment thread src/backends/seatbelt/common/src/seatbelt_runner.rs
caarlos0 and others added 4 commits June 24, 2026 13:29
The self-pipe used to wake `InterruptibleReader`'s `poll` was created with
`libc::pipe`, which does not set close-on-exec. Both wake fds would then leak
into any process the thread later forks+execs (e.g. another sandbox child) and
keep the wake pipe alive unexpectedly. Mark both ends `FD_CLOEXEC` after
`pipe()`, mirroring the fixup `mxc_pty` already does for PTY fds. The data pipe
is unaffected -- Rust already sets CLOEXEC on `Child` stdio.

Addresses a Copilot review comment on #555.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
`group_kill` discarded both `kill(2)` results and always returned `Ok(())`, so
`SandboxProcess::kill()` reported success even when it never signalled the
process group. Route both signals through a `send_sigkill` helper that returns
the error instead, treating only the "already gone" outcomes as success:
`ESRCH`, and `EPERM` -- which on macOS a redundant kill of an exited-but-unreaped
child's group reports in place of `ESRCH` (observed via the double-kill test).
The caller guards with `try_wait()` first, so the pid/pgid can't be recycled and
`EPERM` here can only be that benign race, never a real permission failure.

Addresses a Copilot review comment on #556.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
# Conflicts:
#	src/core/wxc_common/src/interruptible_reader.rs
#	src/core/wxc_common/src/sandbox_process.rs
@MGudgin MGudgin merged commit f13b655 into microsoft:main Jun 24, 2026
18 checks passed
MGudgin pushed a commit that referenced this pull request Jun 24, 2026
)

* feat(wxc_common): add the sandbox execution interfaces (SandboxBackend / SandboxProcess / Runner)

Introduce the shared, no-pty execution surface that the containment backends
and a future in-process library build on. Purely additive: no existing code
path uses these yet, so behavior is unchanged.

- `SandboxProcess` — a handle to a running sandboxed child: `take_stdin` /
  `take_stdout` / `take_stderr`, `try_wait`, `id`, `kill` (process-tree), and
  `wait` (drains any untaken stdio, honors `scriptTimeout`), plus stdout/stderr
  closers for abandoning a backgrounded-descendant-held read without a kill.
- `SandboxBackend` — `validate` + `spawn(request, logger, StdioMode) ->
  SandboxProcess` + a `diagnose_exit` hook; `StdioMode::{Pipes, Inherit}`.
- `Runner<B>` — the generic adapter bridging any `SandboxBackend` to the
  run-to-completion `ScriptRunner` (spawn `Inherit`, then `wait`).
- `StreamCloser`, `group_kill` (Unix leader-first SIGKILL of the child's group),
  and `wait_with_timeout` (adaptive 1ms->50ms backoff poll).
- `interruptible_reader` (Unix self-pipe + `poll`) and the Windows pipe helpers
  in `process_util` (`InterruptiblePipeReader` / `PipeReadCanceller` /
  `create_std_pipes`) for out-of-band-cancelable streaming reads.
- `FailurePhase::Timeout` so a timeout is distinguishable from other failures.

The library backends and executor binaries are migrated onto this surface in a
follow-up PR, and the importable `mxc-sdk` crate is built on top of it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* refactor(backends): unify the library backends on SandboxBackend + route binaries via Runner

Migrate the three in-process backends — Seatbelt (macOS), Bubblewrap (Linux),
and Windows ProcessContainer (AppContainer + BaseContainer) — onto the
SandboxBackend / SandboxProcess interfaces added in the previous PR, and route
the executor binaries (wxc-exec, lxc-exec, mxc-exec-mac) through the generic
Runner<B> adapter. The old per-backend run-to-completion logic is removed; each
backend now exposes only spawn(), and Runner provides the single ScriptRunner
the binaries dispatch on (spawn StdioMode::Inherit, then wait).

Each backend gains a streaming handle with whole-process-tree termination
(Unix process-group SIGKILL; Windows job-object terminate) and a uniform
io::ErrorKind::TimedOut on scriptTimeout.

Intentional behavior changes for existing binaries (call-outs for review):
- Seatbelt now always env_clear()s the child (previously only when process.env
  was non-empty), aligning the binary with the SDK's documented "host env is
  not inherited" contract.
- Seatbelt resolves an empty process.cwd to a policy read-write path (or "/")
  instead of the launcher's cwd.
- Seatbelt/Bubblewrap inherit the executor's own stdio (StdioMode::Inherit) —
  Seatbelt no longer allocates a private pty, and Bubblewrap no longer forces
  stdin to /dev/null or post-exit-captures stdout/stderr (it streams live).
- BaseContainer now places the child in a UiJobObject for tree-kill (it had
  none before); the child is created suspended, assigned to the job, then
  resumed so no descendant can escape the kill window.
- kill() is a no-op once the child has been reaped, so it never signals a
  recycled pid/process-group.

The macOS Seatbelt characterization suite is updated to assert the new env/cwd/
streaming/timeout behavior; the LXC and Seatbelt backend docs are updated to
match. The default LXC path keeps its native pty and is unaffected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat(mxc-sdk): add the importable in-process sandbox library crate

Add `mxc-sdk` (lib `mxc_sdk`), a Rust library for starting MXC sandboxes
in-process without a pty, built on the SandboxBackend interfaces and the
unified backends from the previous PRs. Callers stream a sandboxed process
without shelling out to the executor binaries or depending on the TypeScript
SDK.

Public surface (crate-owned types — `wxc_common` stays an implementation
detail):
- `build_request(&SandboxPolicy)` -> `SandboxRequest`, then `spawn_sandbox`
  -> `Sandbox`: a handle for bidirectional stdio (`take_stdin`/`take_stdout`/
  `take_stderr`), `try_wait`, `id`, `kill` (process-tree), and `wait` ->
  `WaitOutcome` (`Exited(i32)` | `TimedOut`), plus stdout/stderr closers.
- `Error` / `ErrorCode` mirror the wire-format error one-for-one.
- `mxc_sdk::policy` ports the SDK's config building (`createConfigFromPolicy`
  plus `available_tools_policy` / `user_profile_policy` /
  `temporary_files_policy`); `platform_support` ports `getPlatformSupport`.

Backends: Bubblewrap (Linux), Seatbelt (macOS), Windows ProcessContainer
(AppContainer + BaseContainer). Other backends and LXC return
`UnsupportedContainment` (LXC has no non-pty capture path); `dry_run` is
rejected for streaming spawns.

Adds `wxc_common::config_parser::load_request_from_value` so the crate maps a
config it already holds as JSON without a base64 round-trip. The in-crate
backend dispatch (`dispatch.rs`) and host probe (`platform.rs`) are marked
provisional — a follow-up moves them into a shared `mxc` engine crate that both
this library and the executor binaries call into.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat(mxc-sdk): add Sandbox::wait_with_output to drain both streams safely

Taking both take_stdout() and take_stderr() and reading them sequentially
can deadlock an output-heavy child (one pipe fills while the reader is
blocked on the other). Add wait_with_output(): it consumes the handle,
drains stdout and stderr concurrently on separate threads, and returns
Output { outcome, stdout, stderr } -- the safe, convenient default,
mirroring std::process::Child::wait_with_output. Review CR-24.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test(mxc-sdk): cover dispatch guard branches for unsupported containment/host

The streaming dispatch in `dispatch.rs` only had direct tests for the
`dry_run` and macOS `guiAccess` rejection branches. Add two more so the
remaining guardrails are exercised in CI:

- `streaming_rejects_unsupported_containment`: drives the internal model
  with `containment = Lxc` and asserts `UnsupportedContainment` plus the
  backend name in the message.
- `host_support_ok_on_supported_platforms`: cfg-gated to Windows / Linux /
  macOS, guards against the `ensure_host_supported` cfg list dropping a
  supported platform.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* feat(mxc-sdk): take structured (key, value) pairs in SandboxRequest::set_env

set_env(Vec<String>) made the caller hand-format raw KEY=VALUE strings,
which diverged from the SDK's env channel -- injectEnvIntoConfig
(sdk/src/sandbox.ts) takes a structured { key: value } map and joins it to
the KEY=VALUE wire form internally.

Accept (key, value) pairs instead and do the formatting in the setter, so
the crate matches the SDK surface and callers can't forget the '='. The wire
representation (Vec<String> of KEY=VALUE) is unchanged, and iteration order
is preserved so a later duplicate key still wins downstream -- same as the
SDK. No eager validation is added: the SDK doesn't validate either, and
structured input already removes the malformed-entry foot-gun.

Adds a unit test asserting the pair-to-KEY=VALUE ordered mapping.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix(wxc_common): set FD_CLOEXEC on the interruptible-reader wake pipe

The self-pipe used to wake `InterruptibleReader`'s `poll` was created with
`libc::pipe`, which does not set close-on-exec. Both wake fds would then leak
into any process the thread later forks+execs (e.g. another sandbox child) and
keep the wake pipe alive unexpectedly. Mark both ends `FD_CLOEXEC` after
`pipe()`, mirroring the fixup `mxc_pty` already does for PTY fds. The data pipe
is unaffected -- Rust already sets CLOEXEC on `Child` stdio.

Addresses a Copilot review comment on #555.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix(wxc_common): propagate non-benign group_kill signal errors

`group_kill` discarded both `kill(2)` results and always returned `Ok(())`, so
`SandboxProcess::kill()` reported success even when it never signalled the
process group. Route both signals through a `send_sigkill` helper that returns
the error instead, treating only the "already gone" outcomes as success:
`ESRCH`, and `EPERM` -- which on macOS a redundant kill of an exited-but-unreaped
child's group reports in place of `ESRCH` (observed via the double-kill test).
The caller guards with `try_wait()` first, so the pid/pgid can't be recycled and
`EPERM` here can only be that benign race, never a real permission failure.

Addresses a Copilot review comment on #556.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test(mxc-sdk): move the Windows streaming test out of the macOS-gated file

`tests/streaming.rs` is `#![cfg(target_os = "macos")]`, so the
`#[cfg(target_os = "windows")]` ProcessContainer streaming test it contained
could never compile -- the intended Windows coverage was silently missing.
Move that test into its own `tests/streaming_processcontainer.rs`, gated
`#![cfg(target_os = "windows")]`, so it actually builds on Windows.

Addresses a Copilot review comment on #556.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MGudgin pushed a commit that referenced this pull request Jun 26, 2026
This PR plumbs `deniedPaths` into the BaseContainer SandboxSpec `fs_deny`
FlatBuffer field so the OS enforces denied paths natively at Tier 1, gated on the
OS-advertised `SANDBOX_CAP_FS_DENY` capability. When the capability is absent, a
denied-paths policy falls through to a DACL-enforcing AppContainer tier (T2/T3)
rather than staying on Tier 1 (where the dispatcher applies no host DACL), so
deny is never silently dropped.

Details

* BaseContainerSpecification.fbs / sandbox_spec_generated.rs: add the additive
  `fs_deny:[string]` field so deniedPaths reach Experimental_CreateProcessInSandbox.
* base_container_runner.rs: emit `fs_deny` from `denied_paths`; `validate` accepts
  deniedPaths only when the OS advertises SANDBOX_CAP_FS_DENY (via the existing
  `base_container_supports_deny_paths()` query) and otherwise fails closed -- this
  runner has no DACL fallback of its own.
* fallback_detector.rs: `detect` keeps a denied-paths policy on Tier 1 only when
  the capability is present (native fs_deny, no host DACL); otherwise it falls
  through to the AppContainer DACL tiers. Adds a process-cached, test-seamed
  `base_container_supports_deny_paths()` wrapper mirroring `is_base_container_usable()`.
* Rename the capability bit SANDBOX_CAP_DENY_PATHS -> SANDBOX_CAP_FS_DENY to match
  the OS-side capability name; correct the dispatcher's stale "opaque principal"
  comments (the child runs under a derivable AppContainer SID -- the real caveat
  is that BaseContainerRunner may pass an ephemeral identity, so a host DACL SID
  derived from the container name may not match).
* error.rs: add DENIED_PATHS_FEATURE_DISABLED_MSG for the capability-absent
  direct-caller rejection.

Reconciles onto main's capability-query (#547) and SandboxBackend/Runner refactor
(#555): drops the original commit's parallel native_fs_deny_supported machinery in
favor of main's single `base_container_supports_deny_paths()` query.

Tests

* cargo test -p appcontainer_common -p wxc_common: 129 + 347 passed, including new
  fallback-detector tests (T1 native-deny when capability present; fall-through
  when absent) and runner validate tests (accept/reject by capability) using a new
  MXC_FORCE_DENY_PATHS test seam.
* cargo fmt --all -- --check; cargo clippy -p appcontainer_common -p wxc_common
  --all-targets -- -D warnings; cargo check --workspace --all-targets: all clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generated-with: claude-opus-4.8
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.

3 participants