Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
38a1ff0
Align Linux sandbox with managed permission profiles
t-kalinowski Jun 26, 2026
4fce351
Fix Linux bwrap override handling
t-kalinowski Jun 26, 2026
5add220
Gate Linux env override helper
t-kalinowski Jun 26, 2026
0ec2cb3
Fail closed for root deny globs
t-kalinowski Jun 26, 2026
8561d1a
Fix Linux glob deny future path enforcement
t-kalinowski Jun 26, 2026
15ab09e
Preserve missing bwrap writable roots
t-kalinowski Jun 26, 2026
af2dc14
Preserve symlinked bwrap writable roots
t-kalinowski Jun 26, 2026
75b7f33
Fail closed on Landlock writable root carveouts
t-kalinowski Jun 26, 2026
184192d
Fix Linux minimal deny runtime roots
t-kalinowski Jun 26, 2026
9f75a4b
Fix managed sandbox review findings
t-kalinowski Jun 26, 2026
b8c51a6
Fix Linux bwrap restricted-read mounts
t-kalinowski Jun 26, 2026
fb67f02
Restrict Linux platform default mounts
t-kalinowski Jun 26, 2026
ebdc287
Fix Linux project-only sandbox runtime mounts
t-kalinowski Jun 26, 2026
1e2d2d3
Restore bwrap after non-legacy Codex metadata
t-kalinowski Jun 26, 2026
35dce31
Honor explicit Linux bwrap disable
t-kalinowski Jun 26, 2026
eac8b03
Fix cross-platform sandbox clippy warnings
t-kalinowski Jun 26, 2026
8b31ed0
Install bubblewrap in Linux CI
t-kalinowski Jun 26, 2026
eee4f19
Enable bwrap in Linux CI
t-kalinowski Jun 26, 2026
a9acf3b
Retry Linux bwrap without proc on probe failure
t-kalinowski Jun 26, 2026
94508d8
Use ro-bind for synthetic bwrap metadata dirs
t-kalinowski Jun 26, 2026
a3e68f4
Quiet old bwrap proc probe fallback
t-kalinowski Jun 26, 2026
c5e9035
Stabilize macOS UTF-8 settle tests
t-kalinowski Jun 26, 2026
9c5f993
Trim unrelated UTF-8 timing changes
t-kalinowski Jun 26, 2026
5e9cbd3
Merge remote-tracking branch 'origin/main' into linux-sandbox-update
t-kalinowski Jun 26, 2026
b390963
Fix CI setup and bwrap probe noise
t-kalinowski Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,33 @@ jobs:
python-version: "3.x"

- name: Set up R
if: matrix.os != 'windows-2022'
uses: r-lib/actions/setup-r@v2

- name: Set up R (windows)
if: matrix.os == 'windows-2022'
uses: r-lib/actions/setup-r@v2
with:
r-version: "4.5.3"

- name: Install Linux sandbox dependencies
if: matrix.os == 'ubuntu-22.04'
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y bubblewrap
if sysctl kernel.unprivileged_userns_clone >/dev/null 2>&1; then
sudo sysctl -w kernel.unprivileged_userns_clone=1
fi
if sysctl user.max_user_namespaces >/dev/null 2>&1; then
sudo sysctl -w user.max_user_namespaces=28633
fi
if sysctl kernel.apparmor_restrict_unprivileged_userns >/dev/null 2>&1; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
bwrap --version
bwrap --die-with-parent --new-session --unshare-user --unshare-pid \
--ro-bind / / -- /usr/bin/true

- name: cargo check
run: cargo check
Expand Down
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
toml_edit = "0.25.5"
url = "2.5.8"
[target.'cfg(target_os = "linux")'.dependencies]
globset = "0.4"
landlock = "0.4.4"
seccompiler = "0.5.0"

Expand Down
87 changes: 87 additions & 0 deletions docs/plans/active/linux-sandbox-managed-profiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Linux Sandbox Managed Permission Profiles

## Summary

- Rework the Linux sandbox service around permission-profile driven policy
resolution, bubblewrap-first filesystem isolation, seccomp for
network/process restrictions, and legacy Landlock only as an explicit
fallback.
- Keep the existing `mcp-repl` public sandbox API surface where practical:
`SandboxPolicy`, per-tool-call sandbox metadata parsing for `--sandbox
inherit`, session temp directory ownership, and worker restart semantics
remain server-owned.

## Status

- State: active
- Last updated: 2026-06-26
- Current phase: follow-up scoping

## Current Direction

- Replace the Linux-only legacy projection path with direct enforcement of `FileSystemSandboxPolicy` in the Linux helper.
- Default Linux filesystem sandboxing to bubblewrap when a sandbox is required, with `useLegacyLandlock` and `features.use_linux_sandbox_bwrap=false` acting as the explicit legacy Landlock path.
- Keep using the in-process `mcp-repl` helper entrypoint rather than trusting a client-provided helper executable path.

## Long-Term Direction

- Linux, macOS, and Windows should consume the same managed permission profile
semantics from inherited sandbox metadata.
- Linux should support restricted reads, `:minimal`, project-root subpaths,
temp special paths, deny paths, deny globs, and protected metadata roots.
- A future slice can add a bundled bubblewrap binary; the current repository only has system `bwrap` available.

## Phase Status

- Phase 0: completed - inspected current `mcp-repl` Linux sandbox and the
managed permission-profile metadata contract.
- Phase 1: completed - implemented bwrap-first managed filesystem enforcement in `src/sandbox.rs`.
- Phase 2: completed - broadened Linux tests from legacy-only behavior to
managed-profile parity.
- Phase 3: completed - full required verification passed.

## Locked Decisions

- Do not treat the inherited `codexLinuxSandboxExe` helper path as trusted
executable input. `mcp-repl` continues to launch its own helper.
- Missing or malformed inherited sandbox metadata remains fail-closed.
- Managed filesystem profiles should not be rejected on Linux merely because they cannot be projected to legacy `workspace-write`.

## Open Questions

- Whether to vendor or bundle bubblewrap later so Linux parity does not depend on system `bwrap`.
- Whether managed-network domain allowlists should be fully proxy-routed on Linux in this slice or remain a follow-up after filesystem parity.

## Next Safe Slice

- Pick one follow-up capability and keep it narrow: bundled `bwrap`, Linux
managed-network proxy routing, protected-create monitoring, or explicit
feature probes for older `bwrap`.

## Stop Conditions

- Stop and ask for direction if system bubblewrap limitations make the default bwrap path unusable for normal R/Python worker startup.
- Stop and update this plan if full protected-create monitoring cannot be
implemented in a bounded patch.

## Decision Log

- 2026-06-25: Chose a clean Linux reimplementation path because the current
helper rejects managed profiles and uses Landlock as the primary filesystem
sandbox, while the target managed-profile model is bwrap-first on Linux.
- 2026-06-25: Deferred bundled bubblewrap because `mcp-repl` has no existing resource bundling path for Linux helper binaries.
- 2026-06-26: Kept the helper internal rather than trusting inherited helper
paths; inherited `useLegacyLandlock` now selects the legacy path.
- 2026-06-26: Added server-owned runtime read grants for the current executable and embedded R home under restricted-read Linux profiles so `:minimal` can start the worker without widening user-data reads.
- 2026-06-26: Synthetic bubblewrap parent mount targets are non-writable, so a writable session temp child does not imply ambient `/tmp` writes.
- 2026-06-26: Linux bwrap-backed R interrupts target sandbox descendants instead of the bwrap monitor, preserving persistent REPL interrupt behavior.
- 2026-06-26: Full required verification passed for this slice: `cargo check`, `cargo build`, integration runner, clippy with warnings denied, `cargo test --quiet`, and `cargo +nightly fmt`.

## Remaining Follow-ups

- Vendor or bundle bubblewrap so Linux parity does not depend on a system `bwrap`.
- Add Linux managed-network proxy routing for domain allowlists instead of the current fail-closed behavior.
- Add protected-create monitoring for missing protected metadata paths. This
slice masks and cleans synthetic metadata mount targets, but it does not yet
run the full inotify-based create monitor.
- Probe bubblewrap feature support more precisely for older system `bwrap` builds.
44 changes: 25 additions & 19 deletions docs/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ If you also need R data/config roots, add them explicitly with repeatable
For Codex `permissionProfile` metadata, writable and readable roots come from
the profile entries. `mcp-repl` still adds the server-owned per-session temp
directory as a writable root so the R and Python workers can start.
When a profile requests `:minimal`, mcp-repl appends restricted
read-only platform defaults instead of putting those broader system reads in
the always-on base policy. The local copy also includes R and Python framework
runtime roots needed by the embedded backends. Debug builds embed harp's R
module assets so startup does not require a read carve-out for Cargo's source
checkout. The always-on base policy keeps non-filesystem runtime allowances
such as Python multiprocessing and the PyTorch/libomp OpenMP registration
shared-memory carve-out.
When a Linux profile does not grant full-disk reads, mcp-repl appends
restricted read-only platform defaults instead of putting those broader system
reads in the always-on base policy. The local copy also includes R and Python
framework runtime roots needed by the embedded backends. Debug builds embed
harp's R module assets so startup does not require a read carve-out for Cargo's
source checkout. The always-on base policy keeps non-filesystem runtime
allowances such as Python multiprocessing and the PyTorch/libomp OpenMP
registration shared-memory carve-out.

Within writable roots, these subpaths are forced read-only when present:

Expand Down Expand Up @@ -166,24 +166,30 @@ mcp-repl --sandbox workspace-write \

## Linux behavior

Sandboxing is enforced by a Linux sandbox helper that applies seccomp + Landlock.
Sandboxing is enforced by the internal Linux sandbox helper. The default path
uses bubblewrap for the filesystem namespace and then applies seccomp in the
sandboxed process. Legacy Landlock remains available as an explicit fallback,
but it cannot enforce restricted-read managed profiles.

- `workspace-write` always includes the per-session temp directory in writable roots.
- `read-only` is translated to a minimal writable setup for the session temp directory only.
- `read-only` and Codex managed `:minimal` profiles add only the server-owned
session temp directory as writable runtime state.
- Linux consumes Codex managed filesystem metadata directly: restricted reads,
`:minimal`, project-root entries, `:tmpdir`, `:slash_tmp`, deny paths, deny
globs, and protected `.git`, `.codex`, and `.agents` metadata paths are
rendered into the bubblewrap mount plan.
- default Linux worker setup disables network unless explicitly enabled.
- managed domain allowlists are not enforced on Linux yet; configuring allowed
or denied domains with enabled network access currently fails closed.
- `mcp-repl` always uses its own internal Linux sandbox launcher; helper
executable paths provided by an MCP client are ignored.
- Codex sandbox metadata does not control `mcp-repl`'s optional internal
`bwrap` stage. That remains a local best-effort setting.

Optional `bwrap` stage:

- `MCP_REPL_USE_LINUX_BWRAP=1` enables a bubblewrap outer sandbox.
- `MCP_REPL_LINUX_BWRAP_NO_PROC=1` skips `/proc` mounting.
- if `bwrap` is requested but worker startup dies before backend info arrives,
`mcp-repl` retries once without `bwrap` and continues.
- `MCP_REPL_LINUX_BWRAP_NO_PROC=1` skips `/proc` mounting when the host
container does not allow bubblewrap to mount it.
- if the default bubblewrap path dies before worker readiness, `mcp-repl`
retries once with the legacy Landlock path for compatibility.
- `MCP_REPL_USE_LINUX_BWRAP=0` disables the default bubblewrap path. Codex
`useLegacyLandlock: true` inherited metadata has the same effect for that tool
call.

## Windows behavior (experimental)

Expand Down
Loading
Loading