docs: propose gateway containment via Windows Isolation Sessions - #1
Closed
paulcam206 wants to merge 15 commits into
Closed
docs: propose gateway containment via Windows Isolation Sessions#1paulcam206 wants to merge 15 commits into
paulcam206 wants to merge 15 commits into
Conversation
Adds RFC 0032, which argues that the OpenClaw Gateway process itself should be containable rather than running with the signed-in user's full identity, and proposes a platform-agnostic GatewayContainmentProvider seam with Windows Isolation Sessions as the first provider. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Addresses the ClawSweeper P2 finding: the file was committed with CRLF, unlike every other RFC in the repository. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Reconciles the proposal with openclaw/openclaw#42026 (control plane and per-agent runtime split), which occupies the same decision space: that issue partitions which component holds which secrets, while this RFC changes what principal a component runs as. The seam is now written against a unit of containment so it survives that split. Adds the sections the openclaw-rfc readiness gates required: - Threat model and residual risk, including the loopback reachability that survives containment and was previously stated as a fact without drawing the conclusion. - Compatibility, migration, and rollback. - A staged implementation plan. - An explicit decision requested from maintainers. - Ownership and the core-versus-plugin justification, replacing the unresolved question about who owns the provider. - Capability descriptor versioning and a closed failure taxonomy, so policy rejection can never fall back to a weaker boundary. Unresolved questions are now split into blocking and non-blocking, and record that the RFC number needs maintainer confirmation because the repository's allocation is not sequential in practice. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Corrects the RFC's central factual claim. It said the provider has "no host-folder-sharing primitive"; the public MXC documentation actually describes a per-sandbox staging directory shared between the calling user and the isolated user, with asymmetric visibility, created at provision and deleted at deprovision. That distinction drives several design changes: - Split the capability descriptor's host-path capability into hostPathProjection and stagingChannel, so a provider that can hand a file across the boundary is not mistaken for one that can give an agent the user's working tree. - Settle where durable state lives: outside the boundary, host-side, because both the account and the staging directory are destroyed at deprovision. The host-side store then holds the credentials containment is meant to protect, which the threat model now says plainly. - Treat anything the host reads back from the staging channel as untrusted input, and add that to the readiness bar. - Require that the Gateway is materialized inside the boundary rather than run from the ephemeral, caller-writable staging directory. - Document addressing and reuse: provision returns an identifier the caller persists, reports whether it reused an identity, and the environment outlives the process that started it, so orphan reconciliation is an obligation rather than an edge case. - Make the link to the MSIX packaging RFC mechanical rather than thematic: package identity is an input to provisioning, accepted so a future OS contract can act on the calling application's identity. - Reserve workloadIdentity, since the provider can give the contained unit an identity of its own instead of the caller's. All claims remain sourced to the public microsoft/mxc documentation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
The previous revision had OpenClaw core owning the seam that contains the Gateway, which contradicts the RFC's own threat model: if the Gateway is the component treated as compromised, a containment decision it makes is a decision the attacker makes. A check inside the process can be patched, configured away, or never reached, and by the time it would run the untrusted code is already executing with the identity the check was supposed to remove. The same applies to plugins, which load into the process being contained. The boundary is now established before the Gateway exists, by a small trusted launcher outside it: - The launcher owns the ContainmentProvider seam, provider selection, the fail-closed rule, and posture reporting. It probes, provisions, stages, and starts the Gateway inside the boundary. - The Gateway is unchanged and unaware. It has no uncontained mode to reach because it is never started outside the boundary, so phase 1 requires no change to OpenClaw core at all. - On Windows the launcher is the packaged host app already proposed in openclaw#58 rather than a new component: openclaw#58 defines it as the packaged entry point behind an openclaw.exe execution alias whose job is launching or stopping the packaged Gateway. - Adds bypass resistance. openclaw#58 already requires that native `openclaw gateway` commands cannot bypass staged-payload activation; the same requirement extends to containment. Because the Gateway cannot be trusted to refuse to start, this comes from the deployment shape rather than a check inside it. - The threat model now names the launcher as the trusted computing base, so anything added to it is added to the TCB, and package integrity becomes a dependency of this design. - Posture reported by the Gateway is explicitly not evidence, since a compromised Gateway can claim to be contained. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Full rewrite. Same argument and same design, ~35% shorter: drops the repetition and hedging that accumulated across revisions, uses plainer language, and merges the migration, phasing, and rollback material into a single Rollout section instead of three. Adds crosslinks throughout to openclaw#58, #42026, openclaw#55, RFC 0025, the public microsoft/mxc backend docs, and the Windows Insider build that gates the primitive. Fixes two defects found while rewriting: a typo in the summary, and a capability named hostPathSharing in the Windows section that no longer matched the descriptor table after it was split into hostPathProjection and stagingChannel. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Fixes several factual errors, verified against microsoft/mxc at 0aaa2afa (2026-08-18). The earlier draft was written against a clone that was eight days stale, and the preview API had changed underneath it. Corrections: - Lifecycle was wrong. The real phases are provision, start, exec, stop, deprovision. The draft omitted exec and invented an attach phase the provider explicitly defers. - "Every operation is idempotent" was false and dangerous: provision mints a fresh identity on every call, so a naive retry leaks accounts. Replaced with per-operation semantics and a durable pre-provision record. - Detached execution is deferred upstream and a spawned process stays tethered to its caller, so the launcher cannot exit. It is a long-lived supervisor, not a bootstrapper, and that cost is now stated. - The caller-supplied identity bundle was removed upstream. Dropped the workloadIdentity capability and the claim built on it; appId is now described as inert forward-compatibility metadata rather than a mechanical link to package identity. - PR openclaw#55 was mischaracterized. It also contains the long-lived Gateway, and brokers model credentials so neither Gateway nor worker holds provider values, which solves a residual risk this RFC cannot. Now positioned as complementary and the better answer where available, differing by deployment context rather than by scope. - openclaw#58 proposes a launcher, it does not already build one. #42026 would make the unit of containment a live question, not a drop-in swap. - AppContainer dismissal was too glib. It is MXC's default Windows backend; the real objection is that it is an allowlist over the same token, and its universally available tier enforces filesystem policy by mutating DACLs on host paths. - The downgrade requirement was impossible as written. An older launcher cannot be taught to reject config it never knew; this needs a minimum version enforced by packaging. - Fallback no longer contradicts fail-closed: it means another provider of equal or greater strength, never uncontained. Threat model restructured around the right baseline. Several residual risks are unchanged from an uncontained Gateway rather than regressions - shared-ACL reach, local IPC, egress, and persistence, which is now confined to a throwaway account instead of the user's profile. Separated from the risks containment actually shifts or introduces: the host-side state store, the staging channel, and the launcher. Added a TCB table covering the SDK, the SYSTEM service, and the kernel, and noted that a compromised Gateway commanding an authorized desktop node is not contained by this boundary. Also drops the claim that the Gateway is unaffected. It needs no containment code, but a new principal means a different profile, registry hive, and credential store, and whether the Docker and SSH sandbox backends survive that is now an explicit phase 2 question. All MXC citations pinned to a commit with a verification date, since a load-bearing claim went stale within a week. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Follow-up pass on the re-check: - Clarify that probe is the launcher's own step, not a provider phase, and drop the suggestion that the launcher could reattach later, since no such API exists. - Admit the orphan case a write-ahead record cannot fix: a crash during provisioning can leave an identity that no launcher can address, and reclaiming it is an out-of-band operator task. Added to the readiness bar. - Fix a stale Non-Goals line that still described openclaw#55 as containing only a session's work, and restate the boundary as deployment context. - Correct the AppContainer argument again: the ACEs it needs are removable by a matching unprepare step, so the real objection is the elevated, machine-wide host-prep required before a per-user agent can run, not permanent damage. - Stop calling the launcher "the trusted computing base" where the TCB table already shows it is only the part we own. - Pin the README citation, show exec and supervision in Figure 1, and add readiness items for sandbox-backend survival and downgrade prevention. - Decision requested now states what a yes actually accepts: a second always-on process, a larger TCB, and an identity-only boundary. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Two clarifications that change the design rather than just the wording. Name it. The OS-minted account is the Agent User, matching the provider's own agentUserName/agentUserSid vocabulary. Used consistently now instead of "fresh account" or "throwaway account". More importantly, the account is not per-run. It is explicitly provisioned and explicitly deprovisioned, and it survives everything short of that -- including the launcher exiting and the launcher binary being replaced. Keeping it between runs is the intended shape, because that is what lets multi-session OpenClaw state, memory, and pairing persist across runs and reboots. That removes an entire invented problem. Earlier drafts concluded that nothing inside the boundary was durable and therefore state had to live in a host-side store staged in at startup, which then held the very credentials containment was supposed to protect. There is no such store. State lives in the Agent User profile, inside the boundary, where the user's own token cannot reach it, and the staging channel goes back to being setup-time payload delivery rather than a runtime data plane. New costs that come with a long-lived account, now stated: - Deprovision is the reset button and it is expensive: remediating a suspected compromise and destroying the agent's accumulated memory are the same operation, so export has to exist before deprovision can be recommended. - The sandbox identifier is what addresses the Agent User, and an identifier a newer build cannot decode strands the account along with all persisted state. Identifier compatibility across launcher upgrades is now a hard requirement. - The profile accrues months of credentials and history and is not covered by the user's own backup or credential protection. - Migration is one-time rather than per-run, and DPAPI-protected secrets must be re-established under the new principal rather than copied. Threat model updated accordingly: persistence is relocated rather than removed, and better scoped, but it is no longer described as dying at teardown by default. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Three corrections, all about not overstating. The provider does support per-run provisioning; this design just doesn't use it. Say that instead of claiming the account is never per-run. Backup and restore is not an open problem. The launcher can expose it through the staging directory, so exporting before a reset and reinstating afterwards is a straightforward implementation rather than a blocking unknown. Downgraded from a scary caveat to a phase 3 work item, and the residual question narrowed to protecting an export once it is outside the boundary. Dropped the sandbox-identifier stranding warning entirely. Keeping an identifier addressable across provider versions is MXC's problem, not something this RFC should impose on the launcher as a hard requirement. Also removes a duplicated orphan-reconciliation bullet in the readiness bar. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
The motivation implied the per-session sandbox does nothing, which is unfair and weakens the argument. It does real work: a sandboxed tool call reaching for SSH keys or a startup entry doesn't get there. The full profile blast radius is still reachable, but by three specific routes rather than by the sandbox being useless: a tool call that isn't sandboxed because the session selected no backend or the operation doesn't route through one, a container escape, and the Gateway process itself, which is never sandboxed. Tool sandboxing narrows the first and does nothing about the third, which is what this RFC addresses. Naming escape as a route also surfaces a benefit worth claiming: with the Gateway contained, escaping a per-session sandbox lands the attacker in the Agent User rather than on the host as the user, so the two seams compose instead of overlapping. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
The RFC claimed the launcher must stay alive supervising the Gateway because detached execution is deferred upstream. Checking the existing implementation shows that is wrong, and the real design is better. MXC's deferral is about the SDK exec call handing back a process that outlives it. It does not prevent the workload from detaching on the inside: the script the host execs starts the gateway as a detached child so it outlives the setup script, then returns. The host verifies liveness in a bounded settle window and exits. The pieces that make it work: - The MXC session persists across host invocations and is only stopped explicitly, so the environment is still there next time. - Supervision lives inside the session -- a small in-session script owns the gateway process, records its PID and status, and captures its exit. - Persistence across logoff and reboot comes from a scheduled task that re-runs the host at logon, not from a resident process. - Health is observational: a later invocation reads the status file and probes the port rather than holding a handle. Consequences for the RFC: - No second always-on process. Removed from the Decision requested, which no longer asks maintainers to accept a permanent supervisor. - Smaller TCB. The launcher is short-lived, so the standing targets are its binary and its autostart registration rather than a resident daemon. The autostart registration is now named in the TCB. - Orphan reconciliation reframed: rehydrating and adopting the existing environment is the normal path that makes the transient model work, not crash-recovery machinery. - detachedExecution capability replaced with sessionLifetime, which is the property that actually matters -- whether the environment dies with the calling process or persists until stopped. - Figure 1 shows the logon task, the in-session supervisor, and the launcher exiting after exec. - The autostart unresolved question narrows to headless operation, since the logon case is answered. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Compared against the accepted RFCs on main and the review comments on prior RFC PRs. Three gaps worth closing. Accepted RFC 0027 already defines a SandboxDriver with close to the same invariants: verify the driver supports the complete policy and reject when enforcement is unsupported or ambiguous, establish and verify containment before the Harness starts, and treat an implementation that cannot verify enforcement as ineligible rather than substituting another. Not citing it was a real omission -- reviewers on prior PRs have asked for exactly this kind of alignment by number. It also strengthens the motivation: 0027's driver explicitly does not manage the Namespace's OpenClaw gateway, so even in the accepted enterprise design the Gateway is the component left outside the boundary. Named the configuration owner. The RFC referred to configuring a provider without ever saying where that configuration lives. It lives with the launcher, not in openclaw.json, because Gateway-side settings governing Gateway containment is the same inversion the Motivation rejects -- and it keeps the promise that core gains no containment surface. Added a Security properties section, following accepted RFC 0011, which states its invariants as a checkable list rather than leaving them implied by prose. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
The RFC is returning to openclaw#61 after iterating in the fork. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ccc616c5-8e0a-4098-b8ef-c19695e5c27b
Owner
Author
|
Superseded by the upstream RFC PR: openclaw#61 — same branch, so the work carried over. Closing to keep one live PR. |
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.
Iterating on this RFC here in the fork before proposing it upstream. The
upstream draft (openclaw#61) is closed.
RFC file:
rfcs/0032-gateway-containment-windows-isolation-session.mdLifecycle:
status: draft,issueintentionally blank until acceptance.What Problem This Solves
OpenClaw contains the work an agent does, but not the process that decides to do
it. On Windows the Gateway runs as the signed-in user and inherits that user's
full filesystem, registry, and token reach, so a prompt-injection, a hostile
skill, or a compromised plugin acts with the operator's identity. The existing
SandboxBackendseam is per-session and workspace-scoped: it contains thecommands an agent runs, not the process that holds the credentials and chooses
the commands.
Why This Change Was Made
Adds RFC 0032, an argument-first proposal for a platform-agnostic
GatewayContainmentProviderseam — versioned capability descriptor, aprobe -> provision -> start -> attach -> stop -> deprovisionlifecycle, aclosed failure taxonomy, and fail-closed selection — with Windows Isolation
Sessions as the first provider.
It is deliberately capability-honest. The Windows primitive is preview-quality,
so the RFC defines a readiness bar rather than asking for adoption, and states
the provider's limits plainly: no host-folder-sharing primitive, an unrestricted
localhost-reachable network, and no reach to the user's desktop. The boundary on
offer is an identity boundary, and the RFC says so.
Decision Requested
One decision: is an opt-in, capability-honest Gateway containment seam a
direction OpenClaw wants to own in core, given that the first available provider
offers identity isolation only and is preview-gated?
A "yes" authorizes phase 1 only — the contract, with no provider and no default
change. It does not commit OpenClaw to the Windows provider, to a default
posture, or to a timeline.
Related Work
Current Code and Contracts Inspected
src/agents/sandbox/(SandboxBackend, Docker and SSH backends) to establishwhat OpenClaw already contains and at what scope.
openclaw/rfcsat
af708f0ddb6f.microsoft/mxcREADME anddocs/isolation-session/for every Windows behavioral claim.Evidence
Design-only change, so the proof is conformance and sourcing rather than runtime
behavior:
constraints that weaken the case, which are quoted rather than omitted.
<br/>segment over the clipbudget, subgraph titles narrower than contents, and
mmdcparses the blockunder
securityLevel: strict/htmlLabels: false.git diff --checkclean.Review History
Reviewed against the
openclaw-rfcreadiness gates; the first draft passed 3 of10. Addressed since:
boundary without analysing what an attacker retains. Loopback reachability was
stated as a fact but the conclusion was never drawn.
question.
policy_rejectednever falls back.Known Gaps
0032was the lowest unclaimed integer, butthe repository has four open PRs sharing
0009and four sharing0010, soallocation is not sequential in practice. Recorded as an unresolved question.
acceptance.
openclaw/openclaw, which the RFC routingguidance expects to precede an architecture RFC.