Repository navigation
policy_fn: allow asynchronous approval without stalling supervision - #298
Closed
congwang-mk wants to merge 11 commits into
Closed
congwang-mk wants to merge 11 commits into
congwang-mk wants to merge 11 commits into
Conversation
Policy callbacks return a verdict synchronously, so an embedder waiting for human input occupies the callback worker and prevents delivery of later events. Add an owned Approval handle and a deferred callback constructor. Adapt synchronous callbacks to the same delivery path while preserving their return-value API. Observation events receive no handle, and the callback context remains borrowed for the duration of the call. Use the existing verdict channel and cancellation token to reject late or repeated answers. Dropping an unanswered handle denies the request. Add focused coverage for handle lifetime, cancellation and delivery of multiple outstanding approvals. Signed-off-by: Cong Wang <cwang@multikernel.io>
The notification loop awaits each policy verdict before receiving another syscall. Moving an approval to an application queue therefore still stalls unrelated sandbox tasks. Split notification preparation from response completion and retain pending approvals in the supervisor. Poll verdicts alongside notification readiness, bound outstanding work and finalize responses serially. Keep the notification descriptor alive until pending resources have been released. Retain the argv freeze while an exec approval is pending and queue conflicting exec or process-creation preparations. Deny pending requests and release freeze state during teardown. Add a regression where a second connection completes while the first waits for approval. Signed-off-by: Cong Wang <cwang@multikernel.io>
C embedders can only return a verdict from the policy callback. They need an owned handle to answer after the callback returns without retaining borrowed event or context pointers. Add a deferred callback registration entry point and opaque approval handles with resolve, cancellation and release functions. Reuse event conversion for synchronous and deferred callbacks and preserve the existing callback ABI and integer verdict encoding. Document exclusive handle access and the requirement to release every handle after resolution or cancellation. Regenerate the C header for the added entry points. Signed-off-by: Cong Wang <cwang@multikernel.io>
Python applications currently have to wait inside the policy callback to obtain a decision. That prevents the callback worker from delivering another approval request. Add PolicyFn.deferred and an Approval wrapper with allow, deny, cancellation inspection and explicit close methods. Serialize native handle access and release it after a decision or abandonment. Keep the existing synchronous constructor and verdict conversion. Close unanswered approvals when a callback raises, including handles already retained by the application. Add focused tests for configuration, decisions from another thread, repeated answers, abandonment, callback exceptions and timeout. Signed-off-by: Cong Wang <cwang@multikernel.io>
Go policy callbacks can only return an immediate decision. Embedders need to hand pending approvals to another goroutine while the callback worker continues processing events. Add PolicyFn.Deferred and require exactly one callback mode. Wrap native approvals with serialized Resolve, Close and IsCancelled methods. Release the handle after resolution, close it on callback panic and retain finalization as a fallback for abandoned handles. Use the explicit destructor function pointer in the deferred C registration signature so cbindgen emits a prototype usable by cgo. Add focused coverage for deferred decisions and callback configuration. Signed-off-by: Cong Wang <cwang@multikernel.io>
A prepared operation can outlive the policy state used to authorize it. Approving it after another callback tightens restrictions would execute the stale decision. An unpolled verdict future also starts its timeout too late. Recheck captured network destinations and pending open targets against current restrictions before completion. Check pinned file identities before injection and route continued opens through on-behalf handling when dynamic denials require it. Preserve existing live-network precedence and the filesystem namespace boundaries. Capture the verdict deadline when constructing the wait and deny immediately when it has expired. Cancel pending waits if the callback worker exits, and keep observation-only notifications flowing when the approval limit is reached. Add regressions for network and path restrictions, shutdown cancellation and delayed polling. All 19 focused policy unit tests and four deferred supervisor integration tests pass locally. Leave the complete test suites to remote CI. Fixes #289 Signed-off-by: Cong Wang <cwang@multikernel.io>
The policy guide describes approvals as blocking callback loops and does not explain how applications can retain a decision without retaining the callback context. Document deferred approval APIs for Python, Rust, Go and C, including queue handoff, one-shot decisions, timeout, cancellation and explicit release. Explain the pending-request bound and the sandbox-wide argv freeze retained during exec approval. Update the architecture guide to describe supervisor-owned pending verdicts and sequential response completion. Signed-off-by: Cong Wang <cwang@multikernel.io>
The exec freeze treats tasks already in a kernel wait as held, but a task blocked on a pending approval is held by the supervisor, not the kernel. Answering its approval while an execve approval was outstanding released an unfrozen task that could rewrite argv policy_fn had already inspected. Only the freeze holder's verdict is now polled until the exec is decided. Signed-off-by: Cong Wang <cwang@multikernel.io>
congwang-mk
marked this pull request as draft
October 14, 2026 16:50
A sibling seized by the argv freeze is still traced when execve's de_thread kills it, so it stays a zombie until the supervisor reaps it, and de_thread waits for it forever. Any exec under policy_fn from a process with a running sibling thread hung. Reap those siblings after Continue, and release any still stopped at the deadline since the exec must have failed. Signed-off-by: Cong Wang <cwang@multikernel.io>
Holding the sandbox-wide argv freeze across a deferred approval stopped every sandboxed task for as long as a human took to answer. policy_fn now decides on an argv snapshot read without freezing. After an allow, the supervisor freezes, re-reads argv and the path, and continues only if they still match; the freeze then lasts only until the kernel copies argv. No freeze outlives a single notification, so overlapping exec and process creation no longer need to queue, and other approvals no longer need to wait for a pending exec. Signed-off-by: Cong Wang <cwang@multikernel.io>
The post-approval network recheck reconstructed what to check from the prepared operation's event metadata: it parsed the protocol back from a string, special-cased bind, and guessed which hosts had been vetted. Any handler change would silently drift from it. check_ip_destination now returns the destination it approved, and handlers attach that to the prepared operation, so the recheck reruns exactly the original verdict against the live policy. Signed-off-by: Cong Wang <cwang@multikernel.io>
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.
Policy callbacks waiting for an approval currently stall the notification loop. Add owned approval handles so embedders can return from a callback and answer later while supervision continues.
The supervisor retains bounded pending operations and finalizes them serially alongside new notifications. Timeout, abandonment, and shutdown fail closed. Completion rechecks mutable network and filesystem restrictions; exec approval retains the existing argv freeze. Existing synchronous callbacks remain supported.
Expose the API through Rust, C, Python (
PolicyFn.deferred), and Go, with lifecycle documentation and regression coverage.Validation: 19 focused Rust policy unit tests with minimal features, four supervisor integration regressions, five Python deferred-policy tests, and two Go policy tests passed locally. Full testing is left to GitHub CI.
Closes #289.