feat(sandbox): Landlock kernel sandbox for the agent process (in-container)#443
feat(sandbox): Landlock kernel sandbox for the agent process (in-container)#443dpup wants to merge 4 commits into
Conversation
…ainer) First cut of issue #396: isolation.kernel_sandbox applies a Landlock filesystem write-allowlist to the agent process via the embedded moat-sandbox helper, routed through moat-init's final exec chain.
…dbox - config: isolation block validation both directions (accept/reject) - sandbox: policy construction, JSON transport, and a subprocess test asserting real Landlock enforcement (allowed writes pass, denied writes fail, reads intact) - deps: helper COPY + context file with companion absence case, image tag divergence, needsInit/NeedsCustomImage forcing - e2e: full-stack docker run proving enforcement against a moatuser-owned probe dir, plus the unsandboxed companion run
|
Claude finished @dpup's task in 14m 46s —— View job Review: Landlock kernel sandbox (in-container)
Verification performed: This is a well-executed, carefully-reasoned feature. The write-allowlist design, fail-closed posture (missing helper, missing policy env, stub binary all hard-error), the single-goroutine restrict-then-exec (sidesteps go-landlock's multi-thread caveat), and the content-hash-based image invalidation are all sound. Docs match code ( One gap worth addressing before merge:
Nothing else rose to the level of a blocking issue. Nice work on the design doc and the deliberate mirroring of the |
|
Claude finished @dpup's task in 5m 58s —— View job Review: Landlock kernel sandbox (#443)Todo list
SummaryThis is a well-built, carefully reasoned first cut. I read the full diff against
Highlights worth calling out:
Minor observations (non-blocking)
Both are minor; I don't see any correctness, security, or race issues in the sandbox enforcement path itself. The design doc, docs, and CHANGELOG entry are accurate and match the implementation I read (verified the write-allowlist description, the ABI probe/degrade behavior, and the |
|
Closing after a threat-model + runtime-coverage review (full write-up going to #396). Short version: the in-container Landlock sandbox is a no-op on both of moat's default runtimes — Linux defaults to gVisor (which doesn't implement the landlock syscalls), and Apple containers ship a minimal kernel with the entire LSM framework compiled out ( The work isn't lost: the branch, design doc, the cross-platform policy abstraction, and the binary-embedding pattern are the natural starting point for kernel sandboxing in a future containerless/local execution mode, where the agent is a native process and Landlock (Linux) / Seatbelt (macOS) actually become the primary wall. Keeping the branch Detailed findings in #396. |
First cut of #396: OS-native kernel sandboxing, in-container mode.
What this does
isolation.kernel_sandbox: trueinmoat.yaml(ormoat run --kernel-sandbox) applies a Landlock filesystem sandbox to the agent process inside the container — an inner wall behind the container boundary. The restriction is applied after the entrypoint's privilege drop, is inherited by every child process, and cannot be widened once applied: even code with arbitrary execution inside the run stays behind it.The policy is a write allowlist (reads work everywhere):
/workspace, agent$HOME,/tmp,/var/tmp,/dev(+ioctl),/proc,/run, read-write mount targets,isolation.sandbox.allow_writeentriesHow it works
internal/sandbox— policy computed host-side from the final mount set (pure, unit-tested), JSON-shipped viaMOAT_SANDBOX_POLICY, applied in-container with go-landlock v0.9.0 (pure Go, by the Landlock maintainer). Best-effort ABI downgrade; a probe distinguishes "downgraded" from "unavailable" so degradation is always announced.cmd/moat-sandbox— tiny static helper that self-restricts and execs the agent. Restrict-then-exec on one goroutine sidesteps go-landlock's multi-thread caveat. Fails closed if the policy is missing; logskernel sandbox active (Landlock ABI vN)(or a warning) into the run's logs.internal/sandboxbin— ships the helper into run images by embedding prebuilt linux/amd64+arm64 blobs in the moat binary, deliberately mirroring theinternal/initbinpattern from feat(moatinit): rewrite the container entrypoint in Go and remove the shell #441 (committed fail-closed stubs,go generatewired intomake build-cli/goreleaser, checksums + commit-guard tests) so the two merge cleanly — once feat(moatinit): rewrite the container entrypoint in Go and remove the shell #441 lands, this can fold into the Go moat-init.moat-init.sh— final exec becomesexec [gosu moatuser] moat-sandbox "$@"when the policy env is set; fails closed if the helper is missing from the image.ImageSpec.NeedsKernelSandboxforces the init entrypoint + custom image and content-hashes the helper into the image tag, so toggling the sandbox (or a rebuilt helper) rebuilds the image. The run manager fail-fasts with rebuild instructions when the embedded helper is a stub (go installbuilds) or the arch is unsupported.Design decisions (documented in
docs/plans/2026-07-23-kernel-sandbox-design.md)allowWritemodel; domain-level read-secrecy stays with the credential proxy.deny_pathsis rejected, not ignored — Landlock is allowlist-only; a user who wrotedeny_pathsand got no error would believe those paths are protected. Same forisolation.mode: local(the containerless mode is a Add OS-native kernel sandboxing (Landlock/Seatbelt): in-container defense-in-depth + lightweight local mode #396 follow-up).moat logs(per the issue's "warn + degrade" requirement). Honest constraint: Landlock'sno_new_privsdisablessudoinside sandboxed runs;pre_runhooks run before the sandbox and are unaffected.Testing
internal/sandbox/apply_linux_test.go): a sacrificial subprocess self-restricts and asserts allowed writes pass, denied writes fail, reads stay open (skips on non-Landlock kernels).internal/e2e/kernel_sandbox_test.go): full docker run with a build-timemoatuser-owned probe dir outside the allowlist — write denied only under the sandbox (companion run proves it's Landlock, not DAC),allow_writehonored,kernel sandbox activeline asserted. Both pass against real Docker (Landlock ABI v6).moat run --kernel-sandbox . -- sh -c ...→kernel sandbox active (Landlock ABI v6),/workspaceok,/etcdenied, reads ok.golangci-lint run: 0 issues. Full unit suite green except the pre-existing network-dependentTestRegistryGithubBinaryURLsExist(fails on main in this environment too).Follow-ups tracked in #396
macOS Seatbelt, containerless
isolation.mode: local,deny_paths(needs masking mounts), Landlock TCP rules scoped to the proxy, audit-store surfacing of kernel denials.🤖 Generated with Claude Code