Skip to content

feat(executor): opt-in OS-level FS confinement for ctx_execute* (#852)#858

Closed
ousamabenyounes wants to merge 1 commit into
mksglu:nextfrom
ousamabenyounes:fix/issue-852-landlock-fs-confinement
Closed

feat(executor): opt-in OS-level FS confinement for ctx_execute* (#852)#858
ousamabenyounes wants to merge 1 commit into
mksglu:nextfrom
ousamabenyounes:fix/issue-852-landlock-fs-confinement

Conversation

@ousamabenyounes

@ousamabenyounes ousamabenyounes commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

What

Opt-in OS-level filesystem confinement for the polyglot executor, addressing Fix #852. Works on Linux and macOS; Windows fails closed.

ctx_execute / ctx_execute_file run arbitrary code in an out-of-process subprocess that does not inherit the harness filesystem sandbox, so an agent can read files outside the project even when the user enabled sandboxing. In-process checks cannot stop this — arbitrary code can call fs.readFileSync directly. The only real boundary is an OS-level one.

Enabled via CONTEXT_MODE_CONFINE_FS=1 (off by default).

Why

Reproduces with (real, before the fix): ctx_execute_file reading /etc/os-release and listing $HOME from a project at /home/.../context-mode — both succeed, no confinement. That is the escape #852 describes.

The existing deny-firewall (security.ts) only blocks paths/commands matching the user's explicit deny globs; default settings block nothing, and it is not a confinement boundary against arbitrary code.

How — one boundary per OS (no single cross-platform primitive exists)

Platform Mechanism Status
Linux ≥ 5.13 Landlock — a tiny embedded C launcher, compiled on first use (cc/gcc/clang) and cached; unprivileged, irreversible, inherited supported
macOS built-in sandbox-exec (Seatbelt) — profile: (allow default)(deny file-read*)(allow file-read* <subpaths>) supported
Windows no lightweight per-process FS-read jail exists unsupported — flag errors, use a container / Windows Sandbox
  • src/sandbox/confine.ts — platform dispatch + pure builders (buildLandlockArgs, buildMacProfile) + lazy launcher compile/cache. Returns null when the platform primitive is unavailable.
  • src/executor.tsconfineFs resolver (env-default, test override); #spawn wraps the command and fails closed if confinement is requested but cannot be applied.

The allow-list is the project root + per-exec sandbox tmp + the runtime's install dir + the world-readable system dirs a runtime needs to start. Everything else — $HOME outside the project, other repos, secrets — is denied.

Test verification (RED → GREEN)

tests/executor.test.ts. Pure-builder unit tests run on every platform; the RED→GREEN integration test runs where the primitive exists (Linux locally + CI ubuntu-latest; macOS on CI macos-latest).

RED — prod fix neutered (cmd = confined removed), GREEN test fails:

× OS FS confinement (issue #852) > GREEN: with confinement, the outside read is denied
AssertionError: expected 'IN:INSIDE_SECRET\nOUT:OUTSIDE_SECRET\n' not to contain 'OUTSIDE_SECRET'
 Tests  1 failed | 145 skipped (146)

GREEN — fix applied:

✓ FS confinement builders (issue #852) > isConfineRequested parses the opt-in flag
✓ FS confinement builders (issue #852) > buildLandlockArgs emits --allow pairs then -- then the command
✓ FS confinement builders (issue #852) > buildMacProfile denies reads then re-grants the allow-list as subpaths
✓ OS FS confinement (issue #852) > RED: without confinement, code reads a file outside the project
✓ OS FS confinement (issue #852) > GREEN: with confinement, the outside read is denied; in-project still works

The GREEN integration test confirms the runtime still works in-project (INSIDE_SECRET read) while the project-external read is blocked (OUT_ERR:EACCES).

Note: the RED→GREEN revert proof above was run locally on Linux (Landlock). The macOS path is exercised by the same integration test on CI's macos-latest runner — I do not have a Mac to run it locally, so the Seatbelt half is verified by CI, not a local revert.

Full suite + typecheck

npm run build      # OK
npm run typecheck  # OK
npx vitest run     # 4459 passed | 43 skipped  (baseline before change: 4454 passed | 43 skipped)

No regression — pass count grew by the new tests, skip count unchanged, no green→non-green flips. Auto-rebuilt bundles reverted, not committed (CI rebuilds them).

Scope (honest limits, in SECURITY.md)

Files changed

File Change
src/sandbox/confine.ts new — platform-dispatched confinement (Landlock + sandbox-exec), pure builders, lazy compile
src/executor.ts confineFs resolver + fail-closed confinement wrapping in #spawn
tests/executor.test.ts pure-builder unit tests + RED→GREEN integration (Linux + macOS gated)
SECURITY.md new — threat model, per-OS mitigation, limitations
README.md Security section: confinement (per-OS table) + CONTEXT_MODE_CONFINE_FS

…lu#852)

ctx_execute / ctx_execute_file run arbitrary code in an out-of-process
subprocess that does not inherit the harness filesystem sandbox, so an agent
can read files outside the project even when the user enabled sandboxing.
In-process checks cannot stop this (arbitrary code can call fs.readFileSync
directly) — only an OS-level boundary can.

Add opt-in confinement via CONTEXT_MODE_CONFINE_FS=1. On Linux >= 5.13 with a
C compiler, a tiny embedded launcher applies a Landlock ruleset that denies
reads outside the project (plus the system dirs a runtime needs), then execs
the runtime. The restriction is unprivileged, irreversible and inherited, so
even arbitrary ctx_execute code cannot read project-external files. Fails
closed: refuses to run if confinement is requested but cannot be applied.

- src/sandbox/landlock.ts: lazy-compile + cache launcher, build confined cmd
- src/executor.ts: confineFs resolver + fail-closed wrapping in #spawn
- tests: RED (unconfined reads outside) -> GREEN (Landlock denies it),
  Linux-gated; env-knob unit test runs everywhere
- docs: SECURITY.md threat model + README security/env-var section

Linux only for now; macOS (sandbox-exec) and Windows are follow-ups. Governs
reads only. Off by default.
@ousamabenyounes ousamabenyounes added the enhancement New feature or request label Jun 22, 2026
@mksglu mksglu closed this Jun 22, 2026
@ousamabenyounes ousamabenyounes changed the title feat(executor): opt-in Landlock FS confinement for ctx_execute* (#852) feat(executor): opt-in OS-level FS confinement for ctx_execute* (#852) Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants