Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:57:50.484Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

wsl_sandbox_test_helper.rs

881 lines · 34.2 KB · rust
1//! Behavior test helper for the Windows WSL Bubblewrap sandbox — the Windows
2//! analog of `bwrap_test_helper`.
3//!
4//! Where the Linux helper is the sandboxed process itself (it re-execs under
5//! the launcher), here the sandboxed process is a *Linux* program inside WSL
6//! while this helper runs on Windows. So instead of a status channel and a
7//! launcher, the helper drives the real [`sandbox::Sandbox`] (`new` + `wrap`),
8//! spawns the command line it produces, and inspects exit codes and
9//! host-side filesystem effects to confirm every grant the sandbox makes and
10//! every restriction it imposes actually holds — including the Windows-specific
11//! one: that a sandboxed process cannot escape via WSL interop by exec'ing a
12//! Windows binary.
13//!
14//! It targets the **default** WSL distro (matching real Zed usage for native
15//! Windows paths); provision that distro before running (see
16//! `script/test-wsl-sandbox.ps1`). Like the Linux helper, it **skips** (rather
17//! than fails) the enforcement assertions when the environment can't actually
18//! enforce a sandbox, so a misconfigured WSL doesn't masquerade as a sandbox
19//! regression. Set `ZED_TEST_SANDBOX_REQUIRE_ENFORCED=1` to turn that skip into
20//! a failure once you've provisioned an environment that *should* enforce.
21//!
22//! Run it with `cargo xtask wsl-sandbox-tests` or `script/test-wsl-sandbox.ps1`.
23
24#![allow(
25    clippy::disallowed_methods,
26    reason = "a single-threaded test helper that intentionally blocks on child processes"
27)]
28
29#[cfg(not(target_os = "windows"))]
30fn main() {
31    eprintln!("wsl_sandbox_test_helper is only supported on Windows");
32    std::process::exit(1);
33}
34
35#[cfg(target_os = "windows")]
36fn main() {
37    imp::main();
38}
39
40#[cfg(target_os = "windows")]
41mod imp {
42    use std::collections::HashMap;
43    use std::ffi::OsStr;
44    use std::net::TcpListener;
45    use std::os::windows::process::CommandExt as _;
46    use std::path::{Path, PathBuf};
47    use std::process::{Command, Output};
48
49    use anyhow::{Context as _, Result, bail, ensure};
50    use sandbox::{
51        CommandAndArgs, HostFilesystemLocation, Sandbox, SandboxError, SandboxFsPolicy,
52        SandboxNetPolicy, SandboxPolicy,
53    };
54
55    /// Network access for a helper run, translated into a `SandboxNetPolicy` in
56    /// `drive_sandbox`. Only the all-or-nothing cases the helper exercises are
57    /// represented.
58    #[derive(Clone, Copy, Default)]
59    enum NetworkAccess {
60        #[default]
61        None,
62        All,
63    }
64
65    /// The per-run permission knobs the helper varies, translated into a
66    /// `SandboxPolicy` in `drive_sandbox`.
67    #[derive(Clone, Copy, Default)]
68    struct SandboxPermissions {
69        network: NetworkAccess,
70        allow_fs_write: bool,
71    }
72
73    /// Tag prefixed to every result line, matching `bwrap_test_helper` so both
74    /// helpers' output reads the same.
75    const RESULT_TAG: &str = "[sandbox_test]:";
76
77    /// `CREATE_NO_WINDOW`: keep `wsl.exe` (a console-subsystem binary) from
78    /// flashing a console window when spawned.
79    const CREATE_NO_WINDOW: u32 = 0x0800_0000;
80
81    pub fn main() {
82        if let Err(error) = run() {
83            eprintln!("{RESULT_TAG} FAILED: {error:#}");
84            std::process::exit(1);
85        }
86    }
87
88    fn run() -> Result<()> {
89        let require_enforced = env_flag("ZED_TEST_SANDBOX_REQUIRE_ENFORCED");
90        let wsl = Wsl::detect();
91        println!("{RESULT_TAG} starting (require_enforced={require_enforced})");
92
93        // `wrap_invocation` performs the real environment probe (locate `bwrap`,
94        // reject setuid-root, smoke-test the exact namespaces) before it builds
95        // any command. So a default-permissions run of `true` doubles as our
96        // enforcement probe: `Ok` means the sandbox is enforceable here, an
97        // `Unavailable` error means it is not.
98        let probe = run_in_sandbox("true", &[], SandboxPermissions::default())?;
99        match &probe {
100            Outcome::Ran {
101                command_succeeded: true,
102                ..
103            } => {}
104            Outcome::Unavailable(message) => return not_enforced(require_enforced, message),
105            other => {
106                return not_enforced(
107                    require_enforced,
108                    &format!("the sandbox probe did not run cleanly: {other:?}"),
109                );
110            }
111        }
112
113        run_enforced(&wsl)
114    }
115
116    /// The environment can't enforce a sandbox. Skip the enforcement checks
117    /// unless the caller asserted (via `ZED_TEST_SANDBOX_REQUIRE_ENFORCED`) that
118    /// it should be able to, in which case this is a real failure.
119    fn not_enforced(require_enforced: bool, reason: &str) -> Result<()> {
120        if require_enforced {
121            bail!(
122                "ZED_TEST_SANDBOX_REQUIRE_ENFORCED is set, but the WSL sandbox could not be \
123                 enforced: {reason}"
124            );
125        }
126        println!(
127            "{RESULT_TAG} SKIP: this environment cannot enforce a WSL bwrap sandbox: {reason}"
128        );
129        Ok(())
130    }
131
132    /// Enforced scenario: `bwrap` is present and a sandbox can be set up, so the
133    /// sandbox must actually be enforced. Assert every grant and every
134    /// restriction end-to-end against the real WSL distro.
135    fn run_enforced(wsl: &Wsl) -> Result<()> {
136        let mut checks = Checks::new();
137        let pid = std::process::id();
138
139        // The core filesystem checks use a scratch tree on the WSL distro's own
140        // rootfs (under `/var/tmp`, which the sandbox leaves read-only rather
141        // than overlaying like `/tmp`). This mirrors the Linux helper and is
142        // robust: it doesn't depend on how drvfs `/mnt/<drive>` submounts behave
143        // under bwrap's recursive root bind. The Windows-drive translation path
144        // (the realistic Zed-on-`C:` case) gets its own dedicated check below.
145        let root_base = format!("/var/tmp/zed-wsl-sandbox-test-{pid}");
146        let writable_wsl = format!("{root_base}/writable");
147        let forbidden_wsl = format!("{root_base}/forbidden");
148        let readable_wsl = format!("{root_base}/readable");
149        let mkdir = wsl.run_sh(&format!(
150            "mkdir -p {} {} {}",
151            shell_quote(&writable_wsl),
152            shell_quote(&forbidden_wsl),
153            shell_quote(&readable_wsl),
154        ))?;
155        ensure!(
156            mkdir.status.success(),
157            "failed to create the WSL scratch tree{}",
158            failure_details(&mkdir)
159        );
160        let _root_cleanup = WslCleanup {
161            exe: wsl.exe.clone(),
162            path: root_base,
163        };
164
165        let default = SandboxPermissions::default();
166        let fs_write_all = SandboxPermissions {
167            network: NetworkAccess::None,
168            allow_fs_write: true,
169        };
170        let network_allowed = SandboxPermissions {
171            network: NetworkAccess::All,
172            allow_fs_write: false,
173        };
174
175        // GRANT: writing into a writable bind succeeds and lands on the host.
176        let writable_file = format!("{writable_wsl}/from-sandbox.txt");
177        let write_writable = run_in_sandbox(
178            &format!("echo omega > {}", shell_quote(&writable_file)),
179            &[PathBuf::from(writable_wsl)],
180            default,
181        )?;
182        checks.expect_succeeded("GRANT: write into a writable dir succeeds", &write_writable);
183        checks.expect(
184            "GRANT: write into a writable dir lands on the host",
185            wsl.exists(&writable_file)?,
186        );
187
188        // RESTRICT: writing outside any writable bind is denied by the read-only
189        // root, and must not leak to the host.
190        let forbidden_file = format!("{forbidden_wsl}/escaped.txt");
191        let write_forbidden = run_in_sandbox(
192            &format!("echo omega > {}", shell_quote(&forbidden_file)),
193            &[],
194            default,
195        )?;
196        checks.expect_blocked(
197            "RESTRICT: write outside writable dirs is denied",
198            &write_forbidden,
199        );
200        checks.expect(
201            "RESTRICT: denied write did not leak to the host",
202            !wsl.exists(&forbidden_file)?,
203        );
204
205        // GRANT: the whole filesystem is readable (root is bound read-only), so
206        // a host file outside every writable dir can still be read.
207        let readable_file = format!("{readable_wsl}/host-data.txt");
208        let seed = wsl.run_sh(&format!(
209            "printf 'host data' > {}",
210            shell_quote(&readable_file)
211        ))?;
212        ensure!(
213            seed.status.success(),
214            "failed to seed the readable file{}",
215            failure_details(&seed)
216        );
217        let read_host = run_in_sandbox(
218            &format!("cat {}", shell_quote(&readable_file)),
219            &[],
220            default,
221        )?;
222        checks.expect_succeeded(
223            "GRANT: host files outside writable dirs are still readable",
224            &read_host,
225        );
226
227        // GRANT + RESTRICT: `/tmp` is a writable tmpfs, but ephemeral — it must
228        // not leak to the WSL distro's real `/tmp`.
229        let tmp_path = format!("/tmp/zed-sandbox-ephemeral-{pid}");
230        let write_tmp = run_in_sandbox(
231            &format!("echo omega > {}", shell_quote(&tmp_path)),
232            &[],
233            default,
234        )?;
235        checks.expect_succeeded("GRANT: writing to /tmp succeeds", &write_tmp);
236        checks.expect(
237            "RESTRICT: /tmp writes are ephemeral (do not leak to the WSL host /tmp)",
238            !wsl.exists(&tmp_path)?,
239        );
240
241        // RESTRICT + GRANT: outbound TCP is denied by the network namespace, but
242        // works when network access is explicitly granted. We discover a peer
243        // reachable from WSL first; that same reachability check proves the
244        // denial below is the sandbox's doing.
245        match discover_peer(wsl)? {
246            Some(peer) => {
247                let connect = connect_script(&peer);
248                let net_denied = run_in_sandbox(&connect, &[], default)?;
249                checks.expect_blocked(
250                    "RESTRICT: outbound TCP is blocked when network is denied",
251                    &net_denied,
252                );
253                let net_allowed = run_in_sandbox(&connect, &[], network_allowed)?;
254                checks.expect_succeeded(
255                    "GRANT: outbound TCP works when network is allowed",
256                    &net_allowed,
257                );
258                // RESTRICT: permissions are independent — granting filesystem
259                // writes must not also grant network access.
260                let net_with_fs_write = run_in_sandbox(&connect, &[], fs_write_all)?;
261                checks.expect_blocked(
262                    "RESTRICT: allow_fs_write does not also grant network access",
263                    &net_with_fs_write,
264                );
265            }
266            None => println!(
267                "{RESULT_TAG} SKIP: no TCP peer reachable from WSL; skipping network checks"
268            ),
269        }
270
271        // GRANT: local AF_UNIX IPC keeps working even while IP networking is
272        // denied. Needs python3 to create the socket; skip if it isn't present.
273        if wsl.has_program("python3")? {
274            let unix_ok = run_in_sandbox(
275                "python3 -c 'import socket; socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)'",
276                &[],
277                default,
278            )?;
279            checks.expect_succeeded(
280                "GRANT: AF_UNIX sockets still work while network is denied",
281                &unix_ok,
282            );
283        } else {
284            println!("{RESULT_TAG} SKIP: no python3 in WSL; skipping AF_UNIX check");
285        }
286
287        // GRANT (escape hatch): `allow_fs_write` lets the command write
288        // anywhere, and the write reaches the host.
289        let escape_file = format!("{forbidden_wsl}/escape-hatch.txt");
290        let write_escape = run_in_sandbox(
291            &format!("echo omega > {}", shell_quote(&escape_file)),
292            &[],
293            fs_write_all,
294        )?;
295        checks.expect_succeeded(
296            "GRANT: allow_fs_write lets the command write outside writable dirs",
297            &write_escape,
298        );
299        checks.expect(
300            "GRANT: allow_fs_write write lands on the host",
301            wsl.exists(&escape_file)?,
302        );
303
304        // GRANT + RESTRICT (Windows-specific): a writable directory given as a
305        // native `C:\` path is translated into WSL and bound read-write, and the
306        // write lands back on the Windows filesystem.
307        check_windows_drive_writable(wsl, &mut checks)?;
308
309        // RESTRICT (Windows-specific): WSL interop must be blocked, so a
310        // sandboxed process can't exec a Windows binary and escape bwrap.
311        check_interop_blocked(wsl, &mut checks)?;
312
313        // GRANT + RESTRICT: the caller's environment is forwarded into the
314        // command, but Windows-specific values like PATH are not (which would
315        // otherwise shadow WSL's PATH and break the shell).
316        check_env_forwarding(&mut checks)?;
317
318        // Degraded (bad request): a non-existent writable path is the model's
319        // mistake, not a broken sandbox environment, so it must be reported
320        // *without* the "sandboxing is unavailable" marker (which would wrongly
321        // prompt the user to disable sandboxing globally).
322        let missing = std::env::temp_dir().join(format!("zed-wsl-missing-{pid}"));
323        let bad_request = drive_sandbox(
324            "true",
325            &[],
326            std::slice::from_ref(&missing),
327            default,
328            &HashMap::new(),
329        )?;
330        checks.expect(
331            "a non-existent writable path is a bad request, not an unavailable-environment error",
332            matches!(bad_request, Outcome::BadRequest(_)),
333        );
334        if !matches!(bad_request, Outcome::BadRequest(_)) {
335            println!("{RESULT_TAG}   (got {bad_request:?})");
336        }
337
338        checks.finish()
339    }
340
341    /// Windows-specific GRANT: a writable directory passed as a native `C:\`
342    /// path is translated into WSL with `wslpath`, bound read-write, and a write
343    /// inside the sandbox lands back on the Windows filesystem. Exercises the
344    /// native-drive path translation end-to-end (the realistic case of Zed on
345    /// Windows sandboxing a command in a project under `C:\`).
346    fn check_windows_drive_writable(wsl: &Wsl, checks: &mut Checks) -> Result<()> {
347        let base =
348            std::env::temp_dir().join(format!("zed-wsl-sandbox-drive-{}", std::process::id()));
349        let writable = base.join("writable");
350        std::fs::create_dir_all(&writable)
351            .with_context(|| format!("failed to create scratch dir `{}`", writable.display()))?;
352        let _cleanup = Cleanup(base);
353
354        let mapped = wsl.wsl_paths(&[&writable]).context(
355            "failed to translate the scratch dir into a WSL path (is the C: drive automounted?)",
356        )?;
357        let Some(writable_wsl) = mapped.into_iter().next() else {
358            bail!("wslpath returned no result for the scratch dir");
359        };
360
361        let write = run_in_sandbox(
362            &format!(
363                "echo omega > {}",
364                shell_quote(&format!("{writable_wsl}/from-sandbox.txt"))
365            ),
366            std::slice::from_ref(&writable),
367            SandboxPermissions::default(),
368        )?;
369        checks.expect_succeeded(
370            "GRANT: write into a writable C:\\ dir succeeds (native path translated into WSL)",
371            &write,
372        );
373        checks.expect(
374            "GRANT: write into a writable C:\\ dir lands on the Windows host",
375            writable.join("from-sandbox.txt").exists(),
376        );
377        Ok(())
378    }
379
380    /// Assert that a sandboxed process cannot reach the Windows host through WSL
381    /// interop. Without the sandbox's interop block, a command could exec a
382    /// Windows binary (e.g. `cmd.exe`), which the WSL binfmt handler runs on the
383    /// host — fully outside bwrap.
384    fn check_interop_blocked(wsl: &Wsl, checks: &mut Checks) -> Result<()> {
385        // `$WSL_INTEROP` must be unset inside the sandbox (the variable `/init`
386        // uses to find the interop socket).
387        let interop_env = run_in_sandbox(
388            "[ -z \"$WSL_INTEROP\" ]",
389            &[],
390            SandboxPermissions::default(),
391        )?;
392        checks.expect_succeeded(
393            "RESTRICT: $WSL_INTEROP is unset inside the sandbox",
394            &interop_env,
395        );
396
397        // Resolve cmd.exe's path inside WSL; skip the exec check if we can't
398        // (e.g. a non-standard automount root).
399        let cmd = match wsl.wsl_paths(&[Path::new(r"C:\Windows\System32\cmd.exe")]) {
400            Ok(mut paths) => paths.pop(),
401            Err(_) => None,
402        };
403        let Some(cmd) = cmd else {
404            println!(
405                "{RESULT_TAG} SKIP: could not resolve cmd.exe inside WSL; skipping interop exec check"
406            );
407            return Ok(());
408        };
409
410        // Control: unsandboxed, interop should let WSL exec a Windows binary. If
411        // even this fails, interop isn't available here, so the sandboxed denial
412        // below would prove nothing — skip.
413        let control = wsl.run(&cmd, ["/C", "exit"])?;
414        if !control.status.success() {
415            println!(
416                "{RESULT_TAG} SKIP: WSL interop is unavailable in this environment (the unsandboxed \
417                 control run failed); skipping interop exec check"
418            );
419            return Ok(());
420        }
421
422        // Sandboxed: exec'ing the same Windows binary must fail, because interop
423        // is blocked.
424        let escape = run_in_sandbox(
425            &format!("{} /C exit", shell_quote(&cmd)),
426            &[],
427            SandboxPermissions::default(),
428        )?;
429        checks.expect_blocked(
430            "RESTRICT: cannot exec a Windows binary via WSL interop (sandbox escape blocked)",
431            &escape,
432        );
433        Ok(())
434    }
435
436    /// Assert the caller's environment is forwarded into the sandbox, while
437    /// Windows-specific values like PATH are dropped rather than overriding
438    /// WSL's own.
439    fn check_env_forwarding(checks: &mut Checks) -> Result<()> {
440        let mut env = HashMap::new();
441        env.insert("ZED_TEST_FORWARDED".to_string(), "yes".to_string());
442        // If PATH were forwarded it would replace WSL's PATH with this bogus
443        // value; it must not be.
444        env.insert(
445            "PATH".to_string(),
446            "/omega-sentinel-should-not-win".to_string(),
447        );
448        let outcome = drive_sandbox(
449            "/bin/sh",
450            &[
451                "-c",
452                "[ \"$ZED_TEST_FORWARDED\" = yes ] && [ \"$PATH\" != /omega-sentinel-should-not-win ]",
453            ],
454            &[],
455            SandboxPermissions::default(),
456            &env,
457        )?;
458        checks.expect_succeeded(
459            "GRANT: caller env is forwarded into the sandbox; RESTRICT: PATH is not overridden",
460            &outcome,
461        );
462        Ok(())
463    }
464
465    /// The outcome of asking the sandbox to run a command.
466    #[derive(Debug)]
467    enum Outcome {
468        /// `wrap_invocation` succeeded and the wrapped `wsl.exe` command ran;
469        /// `command_succeeded` is its exit success.
470        Ran {
471            command_succeeded: bool,
472            stdout: String,
473            stderr: String,
474        },
475        /// `wrap_invocation` reported the sandbox *environment* as unavailable
476        /// (carried the shared unavailable-prefix marker).
477        Unavailable(String),
478        /// `wrap_invocation` reported a bad request (a mappable-path / distro
479        /// problem); no unavailable-prefix marker.
480        BadRequest(String),
481    }
482
483    /// Run `/bin/sh -c <script>` under the sandbox with the given writable paths
484    /// and permissions.
485    fn run_in_sandbox(
486        script: &str,
487        writable_paths: &[PathBuf],
488        permissions: SandboxPermissions,
489    ) -> Result<Outcome> {
490        drive_sandbox(
491            "/bin/sh",
492            &["-c", script],
493            writable_paths,
494            permissions,
495            &HashMap::new(),
496        )
497    }
498
499    /// Drive the real sandbox the way Zed's terminal integration does: wrap the
500    /// invocation, then spawn the resulting `wsl.exe` command and collect its
501    /// result. `wrap_invocation` errors are classified into [`Outcome`] by the
502    /// shared unavailable-prefix marker rather than bubbling up, so callers can
503    /// assert on the classification.
504    fn drive_sandbox(
505        program: &str,
506        args: &[&str],
507        writable_paths: &[PathBuf],
508        permissions: SandboxPermissions,
509        env: &HashMap<String, String>,
510    ) -> Result<Outcome> {
511        let policy = SandboxPolicy {
512            fs: if permissions.allow_fs_write {
513                SandboxFsPolicy::Unrestricted {
514                    protected_paths: Vec::new(),
515                }
516            } else {
517                SandboxFsPolicy::Restricted {
518                    // On Windows the location only records the requested path;
519                    // the real capture happens WSL-side in the helper.
520                    writable_paths: writable_paths
521                        .iter()
522                        .filter_map(|path| HostFilesystemLocation::new(path).ok())
523                        .collect(),
524                    protected_paths: Vec::new(),
525                }
526            },
527            network: match permissions.network {
528                NetworkAccess::None => SandboxNetPolicy::Blocked,
529                NetworkAccess::All => SandboxNetPolicy::Unrestricted,
530            },
531        };
532        let command = CommandAndArgs {
533            program: program.to_string(),
534            args: args.iter().map(|arg| arg.to_string()).collect(),
535            env: env.clone(),
536            cwd: None,
537        };
538        let prepared =
539            Sandbox::new(policy).and_then(|mut sandbox| smol::block_on(sandbox.wrap(&command)));
540
541        let prepared = match prepared {
542            Ok(prepared) => prepared,
543            Err(error) => {
544                let message = error.to_string();
545                return Ok(match error {
546                    SandboxError::WslUnavailable(_) => Outcome::Unavailable(message),
547                    _ => Outcome::BadRequest(message),
548                });
549            }
550        };
551
552        let output = Command::new(&prepared.program)
553            .args(&prepared.args)
554            .creation_flags(CREATE_NO_WINDOW)
555            .output()
556            .context("failed to spawn the wrapped wsl.exe sandbox command")?;
557        Ok(Outcome::Ran {
558            command_succeeded: output.status.success(),
559            stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
560            stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
561        })
562    }
563
564    /// Find a TCP peer reachable from inside WSL so the network checks have a
565    /// real endpoint. Honors `ZED_TEST_ECHO_ADDR` (a `host:port`) if set;
566    /// otherwise binds a local listener on the Windows side and finds an address
567    /// WSL can use to reach the Windows host. Returns `None` (so the caller
568    /// skips, rather than fails, the network checks) when nothing is reachable.
569    fn discover_peer(wsl: &Wsl) -> Result<Option<String>> {
570        if let Some(address) = std::env::var("ZED_TEST_ECHO_ADDR")
571            .ok()
572            .filter(|address| !address.is_empty())
573        {
574            ensure_host_port(&address)?;
575            return Ok(Some(address));
576        }
577
578        // A listener on the Windows side that accepts and immediately drops
579        // connections, so an in-WSL connect succeeds. It lives for the rest of
580        // the process (the thread owns it and never closes it).
581        let listener =
582            TcpListener::bind(("0.0.0.0", 0)).context("failed to bind a TCP listener")?;
583        let port = listener
584            .local_addr()
585            .context("local listener has no address")?
586            .port();
587        std::thread::spawn(move || {
588            for stream in listener.incoming() {
589                match stream {
590                    Ok(stream) => drop(stream),
591                    Err(_) => break,
592                }
593            }
594        });
595
596        // Addresses WSL might use to reach the Windows host: the default
597        // gateway (WSL2 NAT mode) and loopback (mirrored networking mode).
598        let mut candidates = Vec::new();
599        if let Ok(output) =
600            wsl.run_sh("ip route show default 2>/dev/null | awk '/default/{print $3; exit}'")
601        {
602            let gateway = String::from_utf8_lossy(&output.stdout).trim().to_string();
603            if !gateway.is_empty() {
604                candidates.push(gateway);
605            }
606        }
607        candidates.push("127.0.0.1".to_string());
608
609        for host in candidates {
610            let address = format!("{host}:{port}");
611            if wsl.run_sh(&connect_script(&address))?.status.success() {
612                return Ok(Some(address));
613            }
614        }
615        Ok(None)
616    }
617
618    /// A shell snippet that attempts an outbound TCP connection to `host:port`,
619    /// exiting 0 on success. Uses bash's `/dev/tcp` (the most widely available
620    /// option in default WSL distros) under `timeout` so a blocked connect can't
621    /// hang. If bash or timeout is missing the snippet simply fails, which the
622    /// caller treats as "unreachable" and skips.
623    fn connect_script(address: &str) -> String {
624        format!(
625            "timeout 5 bash -c 'exec 3<>/dev/tcp/{}'",
626            address.replace(':', "/")
627        )
628    }
629
630    fn ensure_host_port(address: &str) -> Result<()> {
631        let (host, port) = address
632            .rsplit_once(':')
633            .with_context(|| format!("ZED_TEST_ECHO_ADDR must be host:port, got {address:?}"))?;
634        ensure!(
635            !host.is_empty() && port.parse::<u16>().is_ok(),
636            "ZED_TEST_ECHO_ADDR must be host:port, got {address:?}"
637        );
638        Ok(())
639    }
640
641    /// Thin wrapper over `wsl.exe` for the helper's own (unsandboxed) WSL calls:
642    /// path translation, the interop control run, and peer discovery. Targets
643    /// the default distro, matching `wrap_invocation`.
644    struct Wsl {
645        exe: PathBuf,
646    }
647
648    impl Wsl {
649        fn detect() -> Self {
650            let exe = std::env::var_os("SystemRoot")
651                .map(PathBuf::from)
652                .unwrap_or_else(|| PathBuf::from(r"C:\Windows"))
653                .join("System32")
654                .join("wsl.exe");
655            Self { exe }
656        }
657
658        /// Run a Linux `program` (with `args`) unsandboxed via `wsl.exe --exec`.
659        fn run<I, S>(&self, program: &str, args: I) -> Result<Output>
660        where
661            I: IntoIterator<Item = S>,
662            S: AsRef<OsStr>,
663        {
664            Command::new(&self.exe)
665                .arg("--exec")
666                .arg(program)
667                .args(args)
668                .creation_flags(CREATE_NO_WINDOW)
669                .output()
670                .with_context(|| format!("failed to run `wsl.exe --exec {program}`"))
671        }
672
673        /// Run `/bin/sh -c <script>` unsandboxed.
674        fn run_sh(&self, script: &str) -> Result<Output> {
675            self.run("/bin/sh", ["-c", script])
676        }
677
678        /// Whether `name` resolves to a program inside WSL.
679        fn has_program(&self, name: &str) -> Result<bool> {
680            Ok(self.run_sh(&format!("command -v {name}"))?.status.success())
681        }
682
683        /// Whether `linux_path` exists in the (unsandboxed) WSL distro — used to
684        /// confirm that sandboxed writes did or didn't reach the host.
685        fn exists(&self, linux_path: &str) -> Result<bool> {
686            Ok(self
687                .run_sh(&format!("[ -e {} ]", shell_quote(linux_path)))?
688                .status
689                .success())
690        }
691
692        /// Translate Windows paths to their WSL paths with `wslpath -u`, in one
693        /// round-trip. Paths are passed as argv (not interpolated) so quoting is
694        /// never a concern.
695        fn wsl_paths(&self, paths: &[&Path]) -> Result<Vec<String>> {
696            let mut args: Vec<std::ffi::OsString> = vec![
697                "-c".into(),
698                "for path; do wslpath -u \"$path\" || exit 9; done".into(),
699                "zed-wslpath".into(),
700            ];
701            args.extend(paths.iter().map(|path| path.as_os_str().to_os_string()));
702
703            let output = self.run("/bin/sh", args)?;
704            ensure!(
705                output.status.success(),
706                "wslpath translation failed{}",
707                failure_details(&output)
708            );
709            let stdout = String::from_utf8_lossy(&output.stdout);
710            let resolved: Vec<String> = stdout
711                .lines()
712                .map(|line| line.trim_end_matches('\r').to_string())
713                .collect();
714            ensure!(
715                resolved.len() == paths.len(),
716                "expected {} wslpath results, got {}: {stdout:?}",
717                paths.len(),
718                resolved.len()
719            );
720            Ok(resolved)
721        }
722    }
723
724    /// Tracks assertion results so the helper can report a summary and a single
725    /// exit code, mirroring `bwrap_test_helper`.
726    struct Checks {
727        passed: usize,
728        failed: usize,
729    }
730
731    impl Checks {
732        fn new() -> Self {
733            Self {
734                passed: 0,
735                failed: 0,
736            }
737        }
738
739        fn expect(&mut self, description: &str, condition: bool) {
740            if condition {
741                self.passed += 1;
742                println!("{RESULT_TAG} PASS: {description}");
743            } else {
744                self.failed += 1;
745                println!("{RESULT_TAG} FAIL: {description}");
746            }
747        }
748
749        /// Expect the wrapped command to have run and succeeded.
750        fn expect_succeeded(&mut self, description: &str, outcome: &Outcome) {
751            let ok = matches!(
752                outcome,
753                Outcome::Ran {
754                    command_succeeded: true,
755                    ..
756                }
757            );
758            self.report(description, ok, outcome);
759        }
760
761        /// Expect the wrapped command to have run but been blocked (non-zero
762        /// exit) — the sandbox imposed a restriction.
763        fn expect_blocked(&mut self, description: &str, outcome: &Outcome) {
764            let ok = matches!(
765                outcome,
766                Outcome::Ran {
767                    command_succeeded: false,
768                    ..
769                }
770            );
771            self.report(description, ok, outcome);
772        }
773
774        fn report(&mut self, description: &str, ok: bool, outcome: &Outcome) {
775            if ok {
776                self.passed += 1;
777                println!("{RESULT_TAG} PASS: {description}");
778            } else {
779                self.failed += 1;
780                println!("{RESULT_TAG} FAIL: {description}");
781                match outcome {
782                    Outcome::Ran {
783                        command_succeeded,
784                        stdout,
785                        stderr,
786                    } => println!(
787                        "{RESULT_TAG}   (command {}, stdout: {:?}, stderr: {:?})",
788                        if *command_succeeded {
789                            "succeeded"
790                        } else {
791                            "failed"
792                        },
793                        truncate(stdout),
794                        truncate(stderr),
795                    ),
796                    Outcome::Unavailable(message) => {
797                        println!("{RESULT_TAG}   (sandbox reported unavailable: {message})");
798                    }
799                    Outcome::BadRequest(message) => {
800                        println!("{RESULT_TAG}   (sandbox reported bad request: {message})");
801                    }
802                }
803            }
804        }
805
806        fn finish(self) -> Result<()> {
807            println!(
808                "{RESULT_TAG} summary: {} passed, {} failed",
809                self.passed, self.failed
810            );
811            if self.failed > 0 {
812                bail!("{} sandbox assertion(s) failed", self.failed);
813            }
814            Ok(())
815        }
816    }
817
818    fn failure_details(output: &Output) -> String {
819        let stderr = String::from_utf8_lossy(&output.stderr);
820        let stderr = stderr.trim();
821        let status = match output.status.code() {
822            Some(code) => format!("exit code {code}"),
823            None => "terminated by signal".to_string(),
824        };
825        if stderr.is_empty() {
826            format!(" ({status})")
827        } else {
828            format!(" ({status}; stderr: {stderr})")
829        }
830    }
831
832    /// Single-quote a value for safe interpolation into an `sh -c` script.
833    fn shell_quote(value: &str) -> String {
834        format!("'{}'", value.replace('\'', "'\\''"))
835    }
836
837    fn truncate(value: &str) -> String {
838        const LIMIT: usize = 200;
839        let trimmed = value.trim();
840        match trimmed.char_indices().nth(LIMIT) {
841            // `index` is a char boundary, so this slice can't split a code point.
842            Some((index, _)) => format!("{}…", &trimmed[..index]),
843            None => trimmed.to_string(),
844        }
845    }
846
847    fn env_flag(name: &str) -> bool {
848        std::env::var(name)
849            .map(|value| value == "1")
850            .unwrap_or(false)
851    }
852
853    /// Removes the Windows-side scratch tree on drop, so it doesn't pile up
854    /// across runs.
855    struct Cleanup(PathBuf);
856
857    impl Drop for Cleanup {
858        fn drop(&mut self) {
859            let _ = std::fs::remove_dir_all(&self.0);
860        }
861    }
862
863    /// Removes a WSL-side scratch tree on drop (best effort).
864    struct WslCleanup {
865        exe: PathBuf,
866        path: String,
867    }
868
869    impl Drop for WslCleanup {
870        fn drop(&mut self) {
871            let _ = Command::new(&self.exe)
872                .arg("--exec")
873                .arg("rm")
874                .arg("-rf")
875                .arg(&self.path)
876                .creation_flags(CREATE_NO_WINDOW)
877                .status();
878        }
879    }
880}
881
Served at tenant.openagents/omega Member data and write actions are omitted.