Summary
We're building an MCP (Model Context Protocol) tool-call proxy on top of sandlock-core that wants to run a tool call inside a Sandbox, capture what it would change, hold that off disk pending an external approval decision (e.g. a human or policy approving/denying the effect), and only then commit or discard it. Today there's no supported way to do this once the sandboxed process has exited.
What exists today
Sandbox::on_exit / Sandbox::on_error (BranchAction::{Commit, Abort, Keep}) are consulted synchronously in Sandbox::drop, based on the child's exit status (crates/sandlock-core/src/sandbox.rs:2443). The decision has to be picked before the sandbox is dropped, and dropping is the only mechanism that fires it.
BranchAction::Keep doesn't hand back a resumable handle — it calls cow.keep() (sandbox.rs:2456), which is SeccompCowBranch::keep (cow/seccomp.rs:2215, pub(crate)), so the branch's own Drop backstop leaves the upper directory on disk instead of deleting it as an undisposed leak. The SeccompCowBranch itself is then dropped along with the rest of the Sandbox's internal Runtime when Sandbox::drop returns.
recovery::{list_preserved, read_preserved} can find and read that leftover PreservedBranch later (paths, a deleted list, pid), but the module's own doc comment says it's "report-only today — the supported route to finish an interrupted merge is to re-run commit() on the branch." That commit() is SeccompCowBranch::commit (cow/seccomp.rs:1760, pub fn), but it's unreachable from outside the crate because the containing module is pub(crate) mod cow; (lib.rs:21) and SeccompCowBranch is never re-exported anywhere.
Net effect: once a Sandbox producing a COW branch has exited, there's no public path to hold its change set open and later programmatically commit or abort it based on a decision made after that point — only report on where it landed on disk.
What would help
Something like a way to take the pending branch out of a Sandbox before/instead of letting Drop decide — e.g. a method that hands back a Send-safe handle exposing commit() / abort(), so the decision can be resolved later (after an async wait for external input) instead of being required synchronously at process-exit time. Even a minimal version scoped to the single-sandbox case (independent of the multi-stage transaction work in #65) would unblock our use case.
Happy to sketch a concrete API proposal or take a swing at a PR if there's appetite — wanted to check feasibility/direction first, since this touches Drop semantics and the COW internals directly.
Context
We depend on sandlock-core (pinned by commit) from KYOKAOS, a capability sandbox for MCP agents. The full investigation (including the source references above) is written up in ADR-0012 if useful context.
Summary
We're building an MCP (Model Context Protocol) tool-call proxy on top of
sandlock-corethat wants to run a tool call inside aSandbox, capture what it would change, hold that off disk pending an external approval decision (e.g. a human or policy approving/denying the effect), and only then commit or discard it. Today there's no supported way to do this once the sandboxed process has exited.What exists today
Sandbox::on_exit/Sandbox::on_error(BranchAction::{Commit, Abort, Keep}) are consulted synchronously inSandbox::drop, based on the child's exit status (crates/sandlock-core/src/sandbox.rs:2443). The decision has to be picked before the sandbox is dropped, and dropping is the only mechanism that fires it.BranchAction::Keepdoesn't hand back a resumable handle — it callscow.keep()(sandbox.rs:2456), which isSeccompCowBranch::keep(cow/seccomp.rs:2215,pub(crate)), so the branch's own Drop backstop leaves the upper directory on disk instead of deleting it as an undisposed leak. TheSeccompCowBranchitself is then dropped along with the rest of theSandbox's internalRuntimewhenSandbox::dropreturns.recovery::{list_preserved, read_preserved}can find and read that leftoverPreservedBranchlater (paths, adeletedlist,pid), but the module's own doc comment says it's "report-only today — the supported route to finish an interrupted merge is to re-runcommit()on the branch." Thatcommit()isSeccompCowBranch::commit(cow/seccomp.rs:1760,pub fn), but it's unreachable from outside the crate because the containing module ispub(crate) mod cow;(lib.rs:21) andSeccompCowBranchis never re-exported anywhere.Net effect: once a
Sandboxproducing a COW branch has exited, there's no public path to hold its change set open and later programmatically commit or abort it based on a decision made after that point — only report on where it landed on disk.What would help
Something like a way to take the pending branch out of a
Sandboxbefore/instead of lettingDropdecide — e.g. a method that hands back aSend-safe handle exposingcommit()/abort(), so the decision can be resolved later (after an async wait for external input) instead of being required synchronously at process-exit time. Even a minimal version scoped to the single-sandbox case (independent of the multi-stage transaction work in #65) would unblock our use case.Happy to sketch a concrete API proposal or take a swing at a PR if there's appetite — wanted to check feasibility/direction first, since this touches
Dropsemantics and the COW internals directly.Context
We depend on
sandlock-core(pinned by commit) from KYOKAOS, a capability sandbox for MCP agents. The full investigation (including the source references above) is written up in ADR-0012 if useful context.