fix(workspace): bwrap mount ordering + AppArmor fallback - #651
Conversation
Two compounding bugs prevented OpenClaw Tier 2 capsules from starting on Linux: 1. Mount ordering: --tmpfs for hidden paths (e.g. ~/.astrid) was applied BEFORE writable --bind mounts, causing the tmpfs to erase the capsule directory. In bwrap, later mounts override earlier ones — reordered so hidden tmpfs comes first and writable bind-mounts punch through after. This mirrors the ancestor check already present in the macOS Seatbelt path (PR #534). 2. AppArmor: Ubuntu 24.04+ blocks unprivileged user namespaces by default (kernel.apparmor_restrict_unprivileged_userns=1), silently killing bwrap. Added a cached probe (bwrap --unshare-user -- /bin/true) that detects this at startup and falls back to unsandboxed execution with a clear warning, rather than producing a misleading 'connection closed' error. Closes #648
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses critical issues preventing OpenClaw Tier 2 capsules from starting on Linux systems, specifically concerning bwrap sandboxing. By refining the mount ordering and introducing a robust detection mechanism for bwrap availability, the changes ensure better compatibility with modern Linux security configurations like AppArmor. Additionally, the PR improves the user experience by providing actionable installation instructions when dependencies are missing. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. The sandbox was tight and quite deep, Where secrets and files were to keep. But bwrap would fail, On a Linux-based trail, Now logic has woken from sleep. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request fixes a Linux sandboxing issue where bubblewrap mount ordering could hide capsule directories and adds detection for AppArmor restrictions on unprivileged user namespaces. It introduces a cached probe to verify sandbox availability and provides distro-specific installation hints. Review feedback suggests increasing the robustness of /etc/os-release parsing by trimming whitespace and improving error diagnostics in the bwrap probe by checking specific io::ErrorKind variants.
sandbox.rs exceeded the CI line limit (1241 lines). Split into: - sandbox/mod.rs (629) — shared types, validation, dispatch, cross-platform tests - sandbox/bwrap.rs (385) — Linux bubblewrap: probe, install hint, prefix builder, tests - sandbox/seatbelt.rs (189) — macOS Seatbelt: profile builder, tests No functional changes — pure file reorganization.
When bwrap is unavailable at runtime (e.g. AppArmor blocks user namespaces in CI), sandbox_prefix() returns None and the command runs unsandboxed. Updated 4 server tests to accept both sandboxed and unsandboxed command shapes.
- Trim whitespace in /etc/os-release parsing for robustness - Differentiate NotFound vs other errors in bwrap probe diagnostic
#655) #651 introduced a silent fallback when bwrap can't create user namespaces — which is the default state on Ubuntu 24.04+ because `kernel.apparmor_restrict_unprivileged_userns=1` ships enabled. The fallback returned `Ok(None)` from `sandbox_prefix()` and the MCP caller launched the subprocess unsandboxed with a single `tracing::warn!` line as the only signal. This contradicted the README's "subprocess capsules are sandboxed" promise: a Node.js MCP server or OpenClaw Tier 2 plugin could read `~/.ssh/id_rsa` or write to `~/.bashrc` and the 5-layer security flow would never fire because none of those calls touched the Astrid host ABI. New `SandboxPolicy` enum with three values: - `Required` (new default) — `sandbox_prefix()` errors if the OS sandbox is unavailable. The error message names the most likely cause (apparmor sysctl on Ubuntu 24.04+) and the remediation. MCP server startup propagates the error and refuses to launch the subprocess. - `Preferred` — old behaviour: try the sandbox, fall back to unsandboxed with a `warn`. Retained for environments that explicitly accept the reduced isolation. - `Off` — always launch without a sandbox, no warn. Trusted dev only. Operators can override via `ASTRID_SANDBOX_POLICY=required|preferred|off` or per-call via `ProcessSandboxConfig::with_policy(...)`. Malformed env values warn loudly and fall back to `Required`. This is a breaking change for Ubuntu 24.04+ deployments that were tacitly relying on the silent fallback. Migration: flip the sysctl (`sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0`) or set `ASTRID_SANDBOX_POLICY=preferred` if the unsandboxed launch was intentional. Six new unit tests cover the policy enum, env-var parsing, the default non-`Off` invariant, and the `Off` short-circuit return.
Linked Issue
Closes #648
Summary
Two compounding bugs prevented OpenClaw Tier 2 (Node.js) capsules from starting on Linux. The macOS Seatbelt path was already fixed in PR #534 — this ports the fix to the Linux bwrap path and adds AppArmor detection.
Changes
build_bwrap_prefix()so hidden--tmpfsoverlays come before writable--bindmounts. In bwrap, later mounts override earlier ones — capsule bind-mounts now punch through the~/.astridtmpfs overlay instead of being erased by it.bwrap_available()with a cached probe (bwrap --unshare-user --ro-bind / / -- /bin/true). When user namespaces are blocked (Ubuntu 24.04+ default),sandbox_prefix()returnsNoneand the existing unsandboxed fallback inserver.rsruns with a clear warning.bwrap_install_hint()reads/etc/os-releaseand logs the correct install command for the detected package manager (apt/dnf/pacman/apk/zypper/xbps/nix).sandbox.rs(1241 lines) split intosandbox/mod.rs(629),sandbox/bwrap.rs(385),sandbox/seatbelt.rs(189) to stay within the CI line limit.astrid-mcpserver tests updated to accept both sandboxed and unsandboxed command shapes, since bwrap may be unavailable in CI containers.[Unreleased] > Fixed.Test Plan
Automated
cargo test --workspacepasses (2 pre-existing socket test failures unrelated to this PR)cargo clippy --workspace -- -D warnings)cargo +nightly fmt --all --checkpassesNew tests added:
test_bwrap_prefix_writable_inside_hidden_pathbwrap.rstest_seatbelt_prefix_writable_inside_hidden_pathseatbelt.rstest_bwrap_probe_successbwrap.rstest_bwrap_probe_namespace_deniedbwrap.rstest_bwrap_probe_not_foundbwrap.rstest_bwrap_install_hint_returns_nonemptybwrap.rsManual
cargo test -p astrid-workspace -- sandboxin Docker (ubuntu:24.04) — all 28 Linux tests pass.falsein unprivileged Docker container (simulates AppArmor restriction).truein--privilegedDocker container.Checklist
[Unreleased]