Skip to content

feat(shell): wire the Landlock fs_write L3 boundary into the engine (#35)#53

Merged
hartsock merged 1 commit into
mainfrom
issue-35/landlock-l3
Jun 26, 2026
Merged

feat(shell): wire the Landlock fs_write L3 boundary into the engine (#35)#53
hartsock merged 1 commit into
mainfrom
issue-35/landlock-l3

Conversation

@hartsock

Copy link
Copy Markdown
Member

Part of #35 — wires the Landlock fs_write L3 boundary into the engine, turning the shell from purely-advisory into actually kernel-confined on Linux. Per ADR 0005 (L3 is the boundary) / ADR 0006 (per-OS backends). Left open for review — please test on your Mac first.

What changed

  • OsSpawner now applies the OS sandbox: when an L3 backend will actually confine the run, it executes the pipeline on a dedicated thread that calls best_available_sandbox().apply(caveats) before spawning, so the kernel ruleset is inherited by every child. (restrict_self is per-thread/irreversible/inherited — hence the throwaway thread, never the shared blocking pool.)
  • The Spawner seam gains the effective &Caveats; the mock ignores it.
  • intended_sandbox_kind() computes the kind that will truly be enforced (Landlock iff a capable Linux build has fs_write restricted, else None) and invoke reports exactly that — never overclaiming (I9 / ADR 0006 D3).
  • apply() stays fail-closed (ADR 0006 D4): if the kernel won't enforce, the run errors rather than proceeding unconfined.

This closes the ADR-0001 gap on the fs_write axis: a permitted program's own out-of-scope write (e.g. find -exec/touch writing outside fs_write) is now denied by the kernel — which L2 cannot see once the child has spawned.

Apple Silicon / macOS

All Landlock code is cfg(all(target_os = "linux", feature = "linux-landlock")). On macOS the engine builds clean and runs advisory with honest SandboxKind::None. The real macOS boundary is Seatbelt (#50) — it must be authored/verified on a Mac (can't be built/tested from the Linux CI box). Design: ADR 0006.

How to test on the MacBook (Apple Silicon)

just check                              # fmt + clippy + tests, all green
cargo test -p agent-bridle-tool-shell   # the Landlock test is cfg'd out on macOS

Expected: everything passes; the engine works; any run reports "sandbox_kind": "none" (honest — no Seatbelt yet). Nothing Landlock-specific compiles. This PR's job on macOS is "don't break Apple Silicon + keep the seam ready for #50."

On Linux (what CI exercises)

New integration test real_landlock_confines_a_spawned_childs_own_write (self-skips if the kernel lacks Landlock): touch within fs_write succeeds and reports sandbox_kind: landlock; touch outside is kernel-denied and the file is not created. Verified locally on a 6.8 kernel.

Scope / follow-ups (still on #35 and friends)

Test plan

just check green (fmt + clippy --all-features & --no-default-features + workspace tests; the --all-features run executed the real Landlock enforcement on a Landlock-capable kernel). All prior tests pass.

🤖 Generated with Claude Code

)

WHAT: The engine now APPLIES the L3 OS sandbox, not just reports it. OsSpawner, when an OS sandbox will actually confine the run, executes the pipeline on a dedicated thread that calls best_available_sandbox().apply(caveats) before spawning — so the kernel ruleset is inherited by every child. The Spawner seam gains the effective &Caveats; the mock ignores it. intended_sandbox_kind() computes the kind actually enforced (Landlock iff a capable Linux build has fs_write restricted, else None) and invoke reports exactly that in the envelope.

WHY: ADR 0005 says L3 is the boundary; until now the engine was purely advisory (LandlockSandbox existed in core but nothing applied it). This closes the ADR-0001 gap on the fs_write axis: a PERMITTED program's own out-of-scope write (e.g. touch outside fs_write) is now denied by the kernel — something L2 cannot see once the child has spawned. apply() is fail-closed (ADR 0006 D4): if the kernel won't enforce, the run errors rather than proceeding unconfined. restrict_self is per-thread/irreversible/inherited, so it runs on a throwaway thread, never the shared blocking pool.

MACOS / APPLE SILICON: all Landlock code is cfg(all(target_os=linux, feature=linux-landlock)); on macOS the engine builds clean and runs advisory with honest SandboxKind::None. The real macOS boundary is Seatbelt (#50), which must be authored/verified on a Mac. Per-OS backend design: ADR 0006.

TEST: new Linux+feature integration test (self-skips without Landlock) proves the engine confines a spawned child's OWN write — touch within fs_write succeeds (sandbox_kind=landlock), touch outside is kernel-denied and the file is not created. All prior unit/mocked/integration tests still pass; macOS path is the None branch. just check green (incl. --all-features Landlock run on a 6.8 kernel). Part of #35 — fs_read/exec/net axes (#31) remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01HMGPEApE4XfwgMhgFbRn6c
@hartsock hartsock added the risk:high High-risk change label Jun 25, 2026
@hartsock hartsock merged commit a9c2e05 into main Jun 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high High-risk change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant