OpenClaw / Software Protected Execution

Audience: operators governing an AI-agent tool executor (OpenClaw or any untrusted proposer) with ADK — #1310 Phase 4. This is the software instance of the domain-neutral Protected Execution Boundary model: the same manifest contract that isolates a ROS actuator or a Kubernetes mutation isolates a privileged local executor.

OpenClaw agent               ADK              Governed tool executor        shell / fs / API
 (proposal-only, its own user)  ──▶  authorizes  ──▶  (owns a private socket)  ──▶  side effect

The agent process must NOT directly hold shell/filesystem/deployment credentials or a privileged RPC endpoint. The governed executor listens on a private Unix domain socket it owns; the agent reaches it only by proposing to ADK, which authorizes one action the executor performs.

Validate-only

The builtin.unix_permissions provider never chowns, chmods, creates sockets, or mints credentials (§26). It stats the declared socket and validates its ownership + mode against the manifest — the security property is that the socket is owner/group-only (no world access), so the agent’s user cannot connect.

Step 1 — Run the executor under its own user, owning a private socket

The governed tool executor runs as a dedicated user (e.g. autonomyops-enforcer). The simplest provably-safe posture is an owner-only socket — no group or world access, so no other user can connect:

# executor process, as autonomyops-enforcer:
umask 077            # 0600 socket: owner-only
# … listen on /run/autonomyops/shell-executor.sock …

The OpenClaw agent runs as a different user with no access to that socket and no shell/deployment credentials of its own.

If you need group access (0660 — e.g. so a sidecar in a shared group can connect), you MUST also declare proposer_identity so the provider can prove the agent’s user is outside the socket’s group. Group access without that proof fails closed: a group member could otherwise be the agent itself.

Step 2 — Declare the boundary (deployment manifest, schema v1.8)

schema_version: "1.8"
execution_boundaries:
  - id: shell_execution
    action_kinds: [tool.shell.exec]
    adapter:
      id: governed_shell_executor
      identity: autonomyops-enforcer          # the socket's owner
    target:
      kind: unix.execution_socket
      resource: /run/autonomyops/shell-executor.sock
    protection:
      provider: builtin.unix_permissions
      provider_version: "1"
      configuration:
        socket_path: /run/autonomyops/shell-executor.sock
        socket_owner: autonomyops-enforcer
        mode: "0600"                              # owner-only: provably safe
        # For 0660 (group access) instead, also declare:
        #   socket_group: autonomyops-runtime
        #   proposer_identity: openclaw-agent     # proven to be OUTSIDE the group
    requirements:
      exclusive_enforcement_principal: true
      proposer_has_no_direct_access: true
      preflight_validation: required
      runtime_attestation:
        mode: periodic
        interval: 30s

Step 3 — Verify (activation + attestation)

At activation the provider checks, fail-closed:

  • the socket exists and is a socket (socket_unreadable / not_a_socket);

  • it is not world-accessible — no world permission bit lets the agent’s user connect (world_accessible), so the proposer is blocked;

  • if it grants group access, a declared proposer_identity is proven to be outside the socket’s group (group_access_unverified when none is declared, group_bypass when the proposer is in the group);

  • it is owned by the declared enforcer (adapter_not_authorized otherwise);

  • its mode matches the declaration (mode_mismatch catches drift).

A boundary that fails refuses activation with an execution-boundary: reason. If runtime_attestation is set, the provider re-stats the socket periodically and marks the boundary unhealthy on drift (e.g. someone chmod o+rw’d it) — which stops authorizing executable actions until it is corrected and re-attested.

Failure modes

Symptom

Cause

Fix

bypass_detected (world_accessible)

The socket has world permission bits — any user (the agent) can connect.

chmod 0600 the socket; run the executor with umask 077.

bypass_detected (group_access_unverified)

The socket grants group access (0660) but no proposer_identity is declared to prove the agent is outside the group.

Use owner-only 0600, or declare proposer_identity.

bypass_detected (group_bypass)

The declared proposer is in the socket’s group and can connect directly.

Remove the proposer from the group, or use 0600.

identity_mismatch (adapter_not_authorized)

The socket is owned by a different user than the declared enforcer.

chown the socket to the executor’s user, or fix socket_owner.

invalid (group_unresolvable)

The declared socket_group doesn’t exist.

Fix socket_group, or drop it.

invalid (mode_mismatch)

The socket mode drifted from the declared value.

Restore the declared mode, or update the manifest.

invalid (socket_unreadable / not_a_socket)

The executor isn’t running, or the path isn’t a socket.

Start the governed executor so it creates its private socket.

Note on enforcement

ADK validates this boundary; the OS enforces it. The private-socket pattern (a dedicated executor user + an owner/group-only socket + a separate agent user) is what actually prevents the agent from executing directly. ADK’s role is to prove that isolation is in place, bind each governed decision to it, and fail closed when it is not — it does not itself own the credential or the socket.