feat: launch enclave from Windows through a WSL2 shim - #30
Draft
planger wants to merge 6 commits into
Draft
Conversation
The rolling release now ships enclave-darwin-arm64, enclave-darwin-amd64, and enclave-linux-arm64 alongside the existing linux-amd64 binary and amd64 .deb. darwin/amd64 is cross-compiled on the arm64 macOS runner and therefore not smoke-tested; the other three run the smoke test natively. No native Windows binary is published: native Windows is unsupported at runtime, so Windows users are documented onto WSL2 with the Linux artifact. The smoke test gained macOS support: bash 3.2 has no mapfile, and the Apple layout ignores the XDG_* overrides the script relied on.
The backend hardcodes qemu-system-x86_64 and its bundle builder pins --platform linux/amd64 with apk --arch x86_64, so the guest is x86-64 regardless of host. It launches with -machine microvm,accel=kvm:tcg, so only x86-64 Linux hosts get KVM; arm64 hosts and macOS fall back to TCG. This was undocumented and becomes misleading now that arm64 and macOS binaries are published.
enclave.exe forwards every argument to the Linux enclave binary inside a WSL2 distribution and parses no argv of its own. It is a launcher, not a step towards native Windows support. The working directory decides the distribution, because that is where the code lives: a \\wsl.localhost\<Distro>\... path names its own, a Windows drive path is refused unless ENCLAVE_WSL_ALLOW_WINDOWS_PATH=1, and network shares and mapped drives are refused outright. Argument quoting is the risk. Windows passes one command-line string that wsl.exe re-parses, so the shim builds that string itself and hands it to CreateProcess unchanged, driven by a golden table that round-trips through a reimplementation of CommandLineToArgvW. The empirical half cannot run in GitHub-hosted CI because windows-latest runners have no WSL2, so it splits into that unit-test table plus scripts/wsl-shim-verify.ps1 as a documented manual pre-release gate. cmd/enclave/tool_imports.go is now generated with //go:build !windows so the launcher links neither the tool extensions nor the embedded runtime assets: 3 MB instead of 14 MB.
|
🚀 Deployed preview to https://eclipse-enclave.github.io/enclave-website-previews/pr-previews/pr-30/ |
cmd.exe cannot hold a UNC working directory, so `pushd \\wsl.localhost\Ubuntu\home\p\proj` assigns a free drive letter instead. Refusing that letter refused an alias for a path the launcher already accepts, and ENCLAVE_WSL_ALLOW_WINDOWS_PATH did not apply because the remote-drive check ran first, leaving cmd.exe users with no way through. WNetGetConnection reports what the letter maps to. A WSL share is now treated as the share path it names, with the mapping's own remainder and the working directory below it appended. Everything else is still refused, and the error now names what the drive points at instead of hedging about what it might be. The distribution must be named explicitly in the resolved UNC path: a mapping to the provider root would otherwise promote the first path component to a distribution name and silently bind-mount the wrong directory. Also fixes the golden-file test on Windows, where core.autocrlf rewrote the fixture's newlines and made a byte comparison fail for a reason that had nothing to do with content drift.
The script echoed the received argv with `/usr/bin/printf %s\0`, which reads a `--` in its first operand slot as an end-of-options marker and drops it. The bare `--` case therefore failed against a launcher that had passed the argument correctly and a wsl.exe that had delivered it intact, and it did so only on coreutils versions with that behavior. A sentinel operand keeps every argument under test out of that slot; a missing sentinel now fails the case loudly rather than scoring it against the wrong argv. $PSScriptRoot is empty in Windows PowerShell for a script run by relative path from a UNC location, which is how this script is reached from \\wsl.localhost, so -GoldenFile is resolved in the body with a fallback instead of as a param default.
planger
force-pushed
the
feat/rolling-release-macos-arm64
branch
from
August 7, 2026 07:12
f7f4aaa to
71b25e0
Compare
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.
Stacked on #28 (
feat/rolling-release-macos-arm64) — review that first.enclave.exeforwards every argument to the Linuxenclavebinary inside a WSL2 distribution and returns its exit code. It parses no argv of its own, soenclave.exe --tool codex -- -p "…"behaves exactly as on Linux. This is a launcher, not a step towards native Windows support, and the error messages are written so they do not imply otherwise.The working directory decides the distribution
A distribution can only see its own filesystem, so the directory the code lives in picks it:
\\wsl.localhost\Ubuntu\home\p\proj(or\\wsl$\…)Ubuntuat/home/p/proj. The happy path.C:\Users\p\projENCLAVE_WSL_ALLOW_WINDOWS_PATH=1:/mnt/c/Users/p/proj, distro fromENCLAVE_WSL_DISTROor the WSL default.\\server\share\…Z:\mapping a WSL shareZ:\mapping anything elseIf
ENCLAVE_WSL_DISTRO=Bbut the directory is inside A, A wins with a warning.Layout
cmd/enclave/main.gobecamemain_unix.go(//go:build !windows, unchanged content) plus a 6-linemain_windows.go. All logic is ininternal/wslshim.The generator now emits
//go:build !windowsintocmd/enclave/tool_imports.go, so the launcher links neither the tool extensions nor the 14 MB of embedded assets they pull in — it never builds an image. Result: 3.3 MB instead of 14.3 MB (the plan estimated ~2 MB).make cross-buildstill compiles./...forwindows/amd64and nowwindows/arm64, so the portability guard is unaffected.Argument quoting is the risk
Enclave takes free-form arguments — prompts with spaces and quotes,
--passthrough. Windows passes a single command-line string thatwsl.exere-parses with rules that are not plain CRT rules. The shim therefore builds that string itself and hands it toCreateProcessviaSysProcAttr.CmdLinerather than lettingos/execquote.Two layers verify it:
",\,\", trailing backslash,%VAR%,$VAR, newline, tab, empty string, emoji, CJK, bare--, and the 32767-char limit including surrogate-pair counting) round-trips through a reimplementation ofCommandLineToArgvWinquote_test.go. The reference parser has its own tests, so a pass cannot come from both sides being wrong the same way. This runs in CI on every push.scripts/wsl-shim-verify.ps1feeds the launcher's actual command lines to a realwsl.exeand compares the NUL-separated argv the Linux side receives. It readsinternal/wslshim/testdata/wsl-quoting-golden.json, which a Go test generates from the escaping code and fails on if stale — so no quoting logic is duplicated in PowerShell.Honest limitation: layer 2 cannot run in GitHub-hosted CI, because
windows-latestrunners have no WSL2. It is a documented manual pre-release gate (docs/DEV.md). CI does parse the script with the PowerShell parser so syntax errors cannot land.Locating the Linux binary
~/.local/bin/enclaveis only onPATHvia~/.profile, which a non-login shell does not read. So: resolve, then exec.wsl.exe [-d D] --cd / -e /bin/sh -lc '<fixed literal probe>'— no user argv, so quoting-safe. Passing--cdhere is also how support for it is detected, at no extra cost; if it fails the shim retries without it and falls back to changing directory through a shell that forwards"\$@".wsl.exe [-d D] --cd <linuxPath> -e <absolutePath> <argv…>— no shell re-parses the user's arguments.One extra round trip (~100–300 ms cold). No caching in v1. Probe exit 127 is treated as conclusive, so a missing binary is not retried; a failed
-dgets the installed-distribution list appended to the error.Stdio, Ctrl-C, exit codes
Streams are assigned directly, never piped, so console handles are inherited and
docker attachinside WSL gets a real TTY.signal.Ignore(os.Interrupt)keeps the shim from dying before its child. Child exit codes propagate verbatim; shim preflight failures exit 125 and every shim message is prefixedenclave (windows launcher):.Environment
WSL only forwards variables named in
WSLENV. The shim auto-forwardsENCLAVE_*— minus its ownENCLAVE_WSL_*controls andENCLAVE_HOME, whose Windows path the Linux binary cannot use — plus anything inENCLAVE_WSL_FORWARD_ENV=VAR1,VAR2/p, which overrides those exclusions and acceptsWSLENVflag suffixes. An existingWSLENVis preserved and appended to. The shim stays ignorant of tool specs, so nothing couples toextensions/tools/*/spec.yaml. Docs state plainly that the normal path is configuring credentials inside the distribution.CI and packaging
ci.yml: newwindows-latestjob (build,go vet ./...,go test ./internal/wslshim/..., PowerShell parse). Green from day one.make cross-build: addswindows/arm64, anddocs/DEV.mdno longer claims the windows target is unpublished.rolling.yml:windows/amd64+windows/arm64rows withsmoke: false, zipped in the release job, plus a generated Scoop manifest (packaging/scoop/enclave.template.json→enclave.json) installable straight from the release URL.wingetis deferred: it needs a PR intomicrosoft/winget-pkgsper release.enclave completion powershellpasses through to Cobra on the Linux side.scripts/check-license-headers.shnow covers*.ps1.Testing
Everything except
GetDriveTypeis host-independent and runs on Linux: path classification, UTF-16LE distro-list parsing from byte fixtures, the quoting golden table,WSLENVconstruction, exit-code mapping, and the distro-conflict warning. Orchestration is covered end to end against an injected fake host, including the--cdfallback and the 125-vs-child-exit-code distinction.Verified:
make build,make test,make lint,make cross-build,GOOS=windows go vet ./..., andGOOS=windows go test -c ./internal/wslshimall pass.Not verified locally:
pwshis unavailable in this environment, soscripts/wsl-shim-verify.ps1has not been executed or even parsed here — the new CI step is the first thing that will parse it.Explicitly out of scope
No WSL2/distro/binary installation, no doctor, no binary-resolution cache, no MSI, no native Windows support.
Mapped drive letters
cmd.execannot hold a UNC working directory, sopushd \\wsl.localhost\Ubuntu\home\p\projassigns a free drive letter. The plan called for refusing mapped drives always; on review that was over-broad, so this resolves them:WNetGetConnectionW(bound frommpr.dll—x/sys/windowsdoes not wrap it) reports what the letter maps to. A WSL share is treated as the share path it names, with the mapping's own remainder plus the working directory below it appended, so aZ:mapped to/home/p/projmakesZ:\srcmean/home/p/proj/src.GetDriveTypeandWNetGetConnectionare both behind one-method seams, so the whole classifier stays testable on Linux;go vet'sunsafeptrcheck andgosecboth pass on theGOOS=windowspaths.Why not collapse the two calls:
WNetGetConnectioncould answer both questions, but a drive can report as remote without being a redirectednet use, soGetDriveTypestays the classifier.