Repository navigation
Conversation
Squash of the MXC backend branch (#427) replayed onto current main, so the change can run under the current CI workflow. Two things differ from the original branch: - The MXC path computes masked credential-file denies the same way the srt-win path now does, since the access-set helper requires them. - CI runs the boundary suite (mxc.test.ts) in its own process, then the Windows suites main lists. Not ported: per-command proxy attribution (commandId) is not threaded through the MXC wrap.
Two tests failed only because main moved under the branch: - The checked-in list of public exports gains the five MXC names the backend adds to the package entry point. - The boundary suite's TLS block treats "sandbox user is not provisioned" from the dependency check as the expected srt-win miss. Main reports that case from the dependency check now, as a plain error.
@microsoft/mxc-sdk 0.9 declares Node >=24. On Node 22 the Windows legs logged that the SDK was not installed, so backend selection never reached the MXC probe. The other legs stay on 22, the oldest line `engines` allows.
Emit the exact MXC 1.0.0 one-shot request directly: the 1.x SDK has no compile-only buildSandboxPayload. Policy shape is unchanged from #640; hostLoopback stays explicit in both branches as 1.0 requires.
1.0.0 still refused `network.ingress.hostLoopback: 'allow'` unless the host advertised PSEC 1.1 native ingress, which no shipping Windows 11 24H2/25H2 build does, so the mxc backend was never selected on a host with a proxy port and every session fell back to srt-win. mxc 1.0.1 (microsoft/mxc#1490) honors host-loopback allow on PSEC 1.0 hosts as policy-permitted outbound loopback, which is all srt needs to reach its own proxy. The 1.0.0 request schema is unchanged, so only the package pin and lock entry move.
…unners The selection probe's error does not say whether the OS build, a feature override or the API itself is the cause.
The MXC backend now runs only when requested, and a configuration it cannot enforce is an error rather than a silent switch to srt-win. Unset means srt-win.
A temporary step that shows whether the SDK's API loads and runs under Bun; it cannot fail the job.
The temporary step now shows whether the SDK's streaming API works on a Bun that reports a newer Node version.
Temporary step: checks process tree kill, stdin, large output and environment handling under Node.
The quoted script paths made cmd.exe exit before the scripts ran.
ig-ant
marked this pull request as ready for review
October 27, 2026 15:35
The probe's helper scripts exit at once, so its process tree results are not valid yet.
Scripts in the writable folder could not run in the container. Also checks which folder grants allow listing and running files.
Matches the real policy, so folder listing and process launches behave as they would in use.
Under windows.backend 'mxc', commands now start with SandboxManager.spawnSandboxed(), which calls the SDK's spawn() in process instead of running the wxc-exec runner. wrapWithSandboxArgv() throws under this backend, and windows.mxc.path and MXC_BIN_DIR are gone. Each spawn runs the SDK's probe on its own request first and refuses to start unless the host offers BaseContainer and can enforce every part of it: denied paths, the host proxy, and clipboard and input injection blocking. UI is on for every command, because with it off most programs fail at startup with 0xC0000142. The child environment stays an explicit list, now with the Windows variables ordinary programs expect.
An error's message, cause and stack can carry what the failing call held, such as a proxy URL with credentials in it. Log the name, code, errno, syscall, address and port only.
Adds rows for cmd, whoami, git and node. Git Bash is a known failure under mxc (bash.exe exits with 0xC0000005), so its row is marked failing and turns red once it works. B1/B2 under srt-win find a curl with its own TLS stack instead of assuming its path.
The boundary suite now covers what it measured. Under mxc the suite runs on a Bun canary, whose fs streams accept windowsHandle, until a release does. The diagnostics step reports the SDK's own probe.
A per-command credential file with mode 'mask' never reached the request's denied paths, so the child could read it. Deny it as the srt-win path does, including under filesystem.disabled, and expand per-command denies in deny mode so a missing literal path is kept.
Also marks the Bun canary step as temporary until a release has the windowsHandle fix.
The canary did not yet have the windowsHandle fix, so every command failed with EISDIR. Until a Bun release has it, the boundary suite runs on srt-win on both Windows legs.
…ands spawnSandboxed() now takes commandId and commandText, registers the command text, and puts the command's attribution key in the proxy username, as srt-win does, so per-command network lists and violation attribution apply. Under mxc, initialize() refuses filesystem.disabled (MXC is allow-list both ways, so the nearest request would restrict) and allowLocalBinding: true (MXC cannot promise it), and every spawn refuses a grant beneath a denied path, which srt ranks above the deny and MXC has no way to express. checkDependencies() takes the Windows backend, so a caller checking before initialize() is not told srt-win is missing. Selection state now lives in one variable.
process.exit() ran as soon as the command exited, dropping output still buffered on its way to an asynchronous Windows pipe.
Every MSYS2 program crashes in a BaseContainer, not only Git Bash; native git.exe works.
MXC resolves overlapping paths by specificity, whatever the order: a grant beneath a denied path is accessible and the rest of the deny stays denied, which matches srt's ranking. Drop the refusal. Also state the measured loopback behaviour with a proxy: no inbound from loopback or the LAN, no outbound to other loopback ports, on Windows 11 25H2, though MXC documents no guarantee and requires ingress 'allow' whenever a proxy is set.
The working directory was always sent as a read-only path. MXC lets the most specific entry win, so when a command ran inside an allowWrite folder, or beneath one, that folder became read-only and every write was denied. A read-only entry governed by a write grant is now left out, and the working directory is no longer granted for reading beneath a deny, where it overrode the deny.
MXC points TEMP, TMP and LOCALAPPDATA at its own folder for each command and deletes it afterwards. The host's temp folder stays writable by its full path.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Microsoft's MXC BaseContainer as an opt-in second Windows sandbox backend, where the OS enforces filesystem and network policy with no install step. Callers choose it with
windows.backend: 'mxc'; unset meanssrt-win, so existing callers see no change.Status
Commands under
'mxc'now start through the MXC SDK in process, withSandboxManager.spawnSandboxed(). Thewxc-execrunner is no longer used, and the temporary CI probe step is gone. This lands so it is clear what works on MXC today and what doesn't.What works: the network fence (only the proxy is reachable; raw TCP, other loopback ports and direct DNS are blocked), reads except the deny-list, writes only inside
allowWriteand the host's temp folder (by its full path), per-commandallowRead/allowWrite, env var masking, kill of the whole process tree, and cmd, PowerShell, git, node and whoami.What doesn't:
msys-2.0.dll) crashes at startup with0xC0000005in a BaseContainer, eventrue.exe, and Windows writes no crash record. That covers Git Bash, the Unix tools in Git for Windows'usr\bin, and git hooks or aliases that run throughsh. Nativegit.exe, x64 programs under emulation, cmd and PowerShell all start. Callers should use cmd or PowerShell as the shell with this backend. The Git Bash test row is marked failing, so it turns red once it works.user32/gdi32fails with0xC0000142. Clipboard and input injection stay blocked.backend_unsupported), because MXC cannot express them with the meaning they have elsewhere:tlsTerminate(so request-header credential masking is unavailable), session-levelfilesystem.disabled, andallowLocalBinding: true(nothing outside the container can connect in).ingress.defaultisallow(and one without an app identity needs host loopback allowed too). On Windows 11 25H2 (build 26200.9711) that request admitted no inbound connection from loopback or the LAN, and blocked outbound to loopback ports other than the proxy's, but MXC documents no guarantee.Grants beneath a denied path are sent as they are: on the same build MXC resolves overlapping paths by specificity, whatever the list order, so a nested grant is accessible and the rest of the deny stays denied, matching srt's ranking. Because the most specific entry wins, a read-only entry must never sit at or beneath a write grant: until
84147e4the working directory was always sent read-only, so a command run inside anallowWritefolder could not write to it (found on the VM). Read-only entries governed by a write grant are now left out, and the working directory is no longer granted for reading beneath a deny. On the VM at84147e4, writes inside anallowWritefolder reach the host with the working directory inside or outside it, and task 66's deny-parent / write-child / nested-denyWrite config works fully.%TEMP%,%TMP%and%LOCALAPPDATA%are per-command scratch: MXC ignores the values srt sends and points them at its own folder for each command (…\AppData\Local\Packages\sandbox.{GUID}\AC), deleted when the command ends. A write there succeeds but is not seen by the next command or on the host, and caches underLOCALAPPDATA(npm, pip and similar) do not persist. The raw SDK behaves the same.spawn()needsfsstreams that acceptwindowsHandle(fs: add windowsHandle option to file streams oven-sh/bun#47087, merged, not yet in a release). A temporary CI step ran the boundary suite on mxc under Bun canary1.4.4-canary.1+da867c60b, built before that merge:initialize()and the probe passed on the arm64 runner, and every row that starts a command failed withEISDIR, as expected without the fix. The step is removed, so CI runs the boundary suite on srt-win on both Windows legs until the pinned Bun has the fix; then setSRT_BOUNDARY_BACKENDtomxcon arm64.Moving downstream callers to
spawnSandboxed(), with PowerShell as the shell under'mxc', is a separate follow-up.Summary
spawnSandboxed(command, { binShell, customConfig, cwd, timeoutMs, commandId, commandText })returns the running process:stdout,stderr,stdin,wait()(exit code andtimedOut),kill()anddispose().wrapWithSandboxArgv()throws under'mxc', and thesrtCLI usesspawnSandboxed()there.probe()on its own request. It refuses unless the tier isbase-containerand the host can enforce every part of the request: denied paths, the host proxy, and clipboard and input-injection blocking. Nothing retries with a weaker request or falls back tosrt-win; refusals throwWindowsSandboxErrorwith codebackend_unsupported.windows.mxc.pathandMXC_BIN_DIRare removed.@microsoft/mxc-sdkstays an optional dependency (Node 24+).spawnSandboxed()puts the command's attribution key in the proxy username and registers its text, as srt-win does, so per-command network lists and violation attribution apply.checkDependencies()/checkDependenciesAsync()take an optional{ windowsBackend }, so a caller checking beforeinitialize()gets the mxc check, not srt-win's.srtCLI waits for the command's output to be written before it exits.mxc.test.tsruns on srt-win on both Windows legs until the pinned Bun has thewindowsHandlefix; only the arm64 image has the BaseContainer API.mode: 'mask'credential files are denied in the request, as on srt-win (also underfilesystem.disabled), and per-command denies keep missing literal paths.windows.backendshould become required in the next breaking release; a TODO marks it.Test plan
Typecheck and lint pass. The request builder and the probe gate have unit tests on every platform, including a refusal test for each unsupported capability and for a probe that throws; the request types were checked against the SDK's own
ContainerRequestandProbeOutput. Platform-spy tests check that a masked credential file lands in the request's denied paths (and fail without the fix), that the refused settings are refused before the SDK loads, that the command text is registered under the command id, and the dependency check's backend option; unit tests check the command id in the proxy username, that a grant beneath a deny is sent in both lists and not refused, and that a deny beneath a grant lands in the denied paths. On7da4d4c, CI passed on every job except Windows arm64, which failed only on the canary mxc rows described above (the srt-win path, including the reworked B1/B2 on x64, passed). A platform-spy test sends the write-grant case throughinitialize()andspawnSandboxed()and checks the grant is in neither the read-only nor the denied list (it fails before84147e4); the boundary suite gains an mxc-only row that writes from inside anallowWritefolder and reads it back on the host. On80e3c06andbafe1a7every CI job passed; on84147e4every job passed too. The current head (b0f9008) changes only docs and comments.Supersedes #765.