OpenClaw EnterpriseDOCSGitHub

Agent sandbox policy flow

Overview

OCC persists governed network additions for one Agent, freezes them at deployment, and optionally applies a later version through its Sandbox Driver. Denial logs and provider recommendations supply independent context for a user review. This trace describes the implemented source; runtime proof has separate limits.

Entry Points

Flow

graph TD
  U["User opens Agent settings"] --> Cfg["Discover authorized<br/>owner-bound schemas"]
  Cfg --> Draft["Edit local additions"]
  Draft --> Preview["Preview draft<br/>without writes"]
  Preview -->|errors| Draft
  Preview -->|valid| U2["Review proposal"]
  U2 --> A["OCC save admission<br/>IAM, schema and ceiling"]
  A --> P["PostgreSQL<br/>desired policy version"]
  P --> D["Explicit deployment<br/>immutable snapshot"]
  P --> W["Live application work<br/>exact running revision"]
  W --> C["Persist provider CAS<br/>before mutation"]
  C --> S["Sandbox Driver<br/>apply canonical policy"]
  S --> V["Confirm matching<br/>loaded version"]
  V --> R["Record applied<br/>or unconfirmed failure"]

The solid arrows trace source paths, not a claim that all providers are verified.

Execution Trace

1. Admit and persist desired additions

internal/occcli/agent_policy.go:agentPolicyCommand declares JSON and YAML output for each policy subcommand. internal/occcli/cli.go:validateOptions selects JSON only when --output is omitted and refuses unsupported explicit formats before any request. It then prints the acknowledged result through internal/occcli/output.go:printStructured. A successful mutation therefore returns its result without a later table-format error.

OpenClawController.discoverConfigurations filters registry contributions by the server-selected Driver, scope, and resource-owner read authority. Installation inspection exposes Driver-projected startup values. Agent discovery and policy GET use the same descriptor with owner-generated facet action URLs and current availability. The registry never selects a Driver from submitted additions.

validateAgentSandboxPolicy authorizes the exact Agent and policy, checks the current editing flag, expected version, and pending application, then runs shared schema/semantic validation, the Driver contribution (which can only narrow it), and current ceiling/provider admission. An empty or reversed port range is never inside the ceiling. It returns a configuration result without entering a mutation transaction. Schema and semantic refusals carry safe error.details; stale or unauthorized requests retain their resource errors. Every later mutation repeats these checks. Ceiling refusal keeps its Installation-delegation cause; unsupported wildcard/range refusal names the Driver capability limit. Both retain field details. internal/occclient/transport.go:APIError.Error prints up to 32 public detail records as escaped JSON and omits records with Unicode control characters, so CLI callers retain the same field and cause without terminal controls.

packages/occ/src/index.ts:updateAgentSandboxPolicy independently authorizes Agent manage_policy and SandboxPolicy update, locks the Namespace and Agent, and checks editing, expected version, pending work, complete ceiling coverage, and registry-resolved Driver admission. The saved additions come from the admitted snapshot. While the active or next revision runs under protected execution (the Installation selects the Credential Proxy), additions are refused with 409 SANDBOX_POLICY_PROTECTED_EXECUTION, as are live application to a protected revision and a protected deploy carrying them; protected revisions admit no ordinary Agent policy snapshot. Protected, dedicated Codex repository admission can carry the Sandbox Driver's explicitly acknowledged artifact subset. checkedProtectedCodexArtifactPolicy verifies its purpose, declared keys, Driver identity, version and empty additions before runtime credential provisioning. That immutable snapshot remains ineligible for live apply. The policy view reports protectedExecution.

Protected Codex repository preparation compiles the revision's qualified artifact snapshot through openshell-codex-artifacts.ts; it never merges ambient defaults. Both paired compilers validate the complete map. The Sandbox separately compares the final workspace enrollment rule with its validated Agent-owned setup binding, then returns the full immutable policy in SandboxResourceRef.proxyWiring. The proxy checks the complete artifact map and bounded auxiliary rule before comparing the complete loaded policy. For an Agent with repository bindings (or a Git-bound active revision), Driver admission gets repositoryCredentials: the OpenShell Driver with its Git route refuses a grant or default reaching github.com or a name under it, or naming an IP address, with a 409 naming it. Adding a binding beside such a saved grant, deploy, and provisioning refuse it too. With saved additions, deploy and an Agent update that leaves repository bindings authorize SandboxPolicy read first, so only a caller who may read the grant sees it named (findings 2151, 2881). apps/controller/src/http/sandbox-policy.ts records the nonsecret approved additions with their version in the same transaction. The required facet argument currently admits networking only. Other facets fail before network policy access. The temporary TEMPORARY_API_WILL_BREAK_IN_FUTURE_setAgentSandboxPolicyEditing requires Installation and Agent administration; Guardrails will replace this switch.

Both state adapters return sandboxPolicyEditing: false at Agent creation, matching the PostgreSQL column default and subsequent reads. The separately authorized editing operation changes this flag.

packages/occ/src/state/postgres-state.ts performs versioned updates. Agent creation creates its one policy through a database trigger. Constraints enforce ownership, document shape, immutable application identity, version transitions, and application revision ownership. The in-memory adapter and the PostgreSQL update trigger reject saves that remove a pending application, including saves that keep the current policy version. The worker can record the expected provider resource version and set the same application to applied or failed. Agent deletion cascades policy removal and cleans its exact IAM access.

2. Freeze deployment or enqueue live application

packages/occ/src/index.ts:deployAgent freezes the Driver's defaults and additions into AgentRevision.sandboxPolicy. The OpenShell Driver provisions from this snapshot rather than re-reading changed defaults.

applyAgentSandboxPolicy authorizes Agent manage_policy and SandboxPolicy update and read before any refusal that names saved additions, then requires the exact active revision and supporting Driver. When the caller supplies reviewed additions, the same transaction saves the new policy version, records its pending application and enqueues Agent-target work. Omitting additions retains the saved policy version. Neither saving nor enqueueing establishes enforcement.

3. Reconcile provider effects

apps/controller/src/worker/sandbox-policy.ts:reconcileSandboxPolicy locks the same lifecycle owners and rechecks current authority, ceiling, Driver identity, and Compute placement. Shared definition and provider admission are rerun before provider effects. One pass records the provider CAS version; a later pass can mutate with that fixed version and operation ID. Provider reads and effects are bounded by the claim lease. Database and audit failures roll back the pass.

apps/controller/src/drivers/sandbox/openshell.ts verifies exact provider ownership, preserves the Sandbox's static policy and generated access, and uses native status/UpdateConfig RPCs. A full candidate match plus loaded active version is required for applied. Transient or unknown outcomes retry within limits; terminal stale work becomes an unconfirmed failure. The worker calls failAbandonedSandboxPolicies inside the same transactWithQueue that recovers exhausted work, so queue failure, application failure, and audit either all commit or remain recoverable after interruption. A failed live request does not deploy another revision. Last confirmed live evidence is retained separately.

4. Read contextual evidence

openshell-log-decisions.ts:openShellLogDecision decorates recognized provider records. packages/occ/src/runtime-logs/sandbox.ts validates them; runtime-logs/sanitize.ts redacts metadata and raw text through the existing bounded log path. See runtime logs.

readAgentSandboxRecommendations independently checks exact Agent/policy read and revision ownership before its audited, limited provider read. The OpenShell Driver projects representable native chunks into untrusted suggestions. The console editor submits canonical additions through the same mutation path; native draft approval cannot bypass OCC admission.

console/driver-configurations.mts:renderAgentDriverConfigurations discovers sections and selects resource-owner controls. console/configuration-fields.mts renders schema fields and JSON fallback, with safe field/root diagnostics. String controls retain the complete draft. Length validation stays with the policy owner: JSON Schema counts Unicode code points, while HTML length attributes count UTF-16 code units and can truncate a valid executable path. console/agents/sandbox-policy.mts:renderLogs snapshots the displayed log window, filters it, and renders five denials per page initially. Selection spans pages; Select page changes only eligible visible rows. Follow retains up to 1,000 new log records without replacing inspected rows. Show newest accepts the retained window and removes selections for records no longer retained.

stageGrants builds exact executable/host/port candidates, deduplicates requests, and skips requests already covered by saved or proposed grants. It retains the complete additions array and opens the schema editor or batch preview. Missing metadata disables prefilling. An incomplete existing draft must be corrected before staging more grants. ruleList searches and pages explicit rules five at a time; row controls edit or remove local additions.

console/agents/sandbox-policy.mts:renderPolicy places policy status and suggestions above the log. It shows three suggestions initially and expands the remaining candidates on request, independently of mutation authority. openNetworkLimits uses the policy view's ceiling in a separate read-only dialog. Defaults and version details stay in the main-page disclosure. The Network policy card also enables or disables Agent policy editing through the separately authorized editing operation. Mutation feedback appears in the card while the dialog is closed; a failed policy read retains its error and read recovery action. renderEditor manages rules without an editing toggle and keeps proposed rules local until preview. Its footer compares the draft with saved additions, including removals and restored values, to offer review, application, or Done. Saving a confirmed response closes the dialog and clears the completed selection. Review shows additions, updates, and removals from the controller-validated rules, with the previously saved policy available for comparison. Save sends the complete validated array with the reviewed policy version. Superseded preview and read responses cannot overwrite a newer draft. Policy reads use a separate sequence: an earlier response cannot invalidate a newer request. A pending application re-arms polling after the latest read, including a response discarded after a draft edit. An unreadable preview retains the draft for another review. The combined request must return the reviewed rules, the next version of this Agent’s policy and a fresh pending application for the selected running revision. Starting a write invalidates earlier policy reads and pauses polling. If the combined response is lost or does not match, the Console reads the policy again. An unsuccessful read leaves Refresh policy available in the open editor. A refused request keeps the edit local. After admission, the provider outcome remains pending until confirmed. Save for later only saves. Apply saved rules requires a clean draft and targets the existing saved version. loadPolicy(true) records explicit inspection of a failed application. Refresh issues only the policy GET; a separate Retry application action invokes the existing apply descriptor after inspection. The action remains subject to current permissions, lifecycle, and draft guards.

console/agents/sandbox-policy.mts:mutate displays rejected saves as focused alerts and scrolls the reason into view after rendering. Ceiling rejection preserves the attempted additions for correction without changing saved state. console/navigation.mts:createNavigation admits the policy tab in login destinations, preserving an Agent's Policy denials link after sign-in.

The console reads the policy owner even when discovery returns no descriptor. This read establishes an unsupported capability or a permission refusal before log and suggestion reads continue under their own permissions. It cannot leave those reads waiting for a policy request that never starts. When the caller's permission answer reports sandboxPolicyRead as false, the console skips the policy and suggestion reads and says so; an unknown answer keeps the reads. A tab opened after the answer settled rereads it first, so a later grant applies. A version snapshot without sandboxDriverId runs without a Sandbox, so the tab skips its log and suggestion reads and says no denials are recorded. A RUNTIME_LOGS_SOURCE_UNAVAILABLE answer shows the same. Then, and on the draft, which reads no log, the denial count, refresh, follow and filter controls and the empty denial rows are hidden. When the permission answer refuses the policy read, only the network policy card states it; the suggestions card is hidden.

Debugging and Verification

Search documentation