feat(shell): wire the Landlock fs_write L3 boundary into the engine (#35)#53
Merged
Conversation
) 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #35 — wires the Landlock
fs_writeL3 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
OsSpawnernow applies the OS sandbox: when an L3 backend will actually confine the run, it executes the pipeline on a dedicated thread that callsbest_available_sandbox().apply(caveats)before spawning, so the kernel ruleset is inherited by every child. (restrict_selfis per-thread/irreversible/inherited — hence the throwaway thread, never the shared blocking pool.)Spawnerseam gains the effective&Caveats; the mock ignores it.intended_sandbox_kind()computes the kind that will truly be enforced (Landlockiff a capable Linux build hasfs_writerestricted, elseNone) andinvokereports 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/touchwriting outsidefs_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 honestSandboxKind::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)
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):touchwithinfs_writesucceeds and reportssandbox_kind: landlock;touchoutside is kernel-denied and the file is not created. Verified locally on a 6.8 kernel.Scope / follow-ups (still on #35 and friends)
fs_read+executeaxes (need a loader/system-path base allow-list) andnet(netns / Landlock-net) — ADR 0004 D2: un-stub gate — no confinement regression below the fail-closed stub #31.os-sandboxmeta-feature / per-backendSandboxKindvariants per ADR 0006.Test plan
just checkgreen (fmt + clippy--all-features&--no-default-features+ workspace tests; the--all-featuresrun executed the real Landlock enforcement on a Landlock-capable kernel). All prior tests pass.🤖 Generated with Claude Code