openclaw policy CLI Reference: Enterprise Conformance Checks

Reference for the openclaw policy command, which provides enterprise conformance checks over OpenClaw settings. Ideal for teams needing durable assertions and drift detection.

Read this when

  • You want to check OpenClaw settings against an authored policy.jsonc
  • You want policy findings in doctor lint
  • You need a policy attestation hash for audit evidence

openclaw policy

The bundled Policy plugin supplies openclaw policy. It acts as an enterprise conformance layer over existing OpenClaw settings, not as a separate configuration system. Requirements are authored in policy.jsonc; OpenClaw treats the active workspace as evidence; drift is reported through doctor --lint. Policy neither enforces tool calls nor alters runtime behavior at request time, nor does it attest per-agent credential stores such as openclaw-agent.sqlite.

Policy examines configured channels, MCP servers, model providers, network SSRF posture, ingress/channel access, Gateway exposure and node command posture, authored message-routing probes, agent workspace access, sandbox posture, data-handling posture, secret provider/auth profile posture, and governed tool metadata (the ## Tools section of AGENTS.md). Deploy it when a workspace requires a durable, checkable assertion like "Telegram must not be enabled" or "governed tools must declare risk and owner metadata." If only local behavior is needed without attestation or drift detection, plain config suffices.

Separately, openclaw agent exec applies an isolated implicit policy config per run: the agent sandbox is off, Gateway-host execution is fully allowed, and filesystem tools are limited to --cwd.

Quick start

openclaw plugins enable policy

The plugin remains active even when policy.jsonc is absent, allowing doctor to report the missing artifact rather than silently skipping checks.

Hand-author policy.jsonc; it is never generated from current settings. Each top-level section acts as a rule namespace: a check runs only when a concrete rule exists under it (unsupported sections or keys fail as policy/policy-jsonc-invalid instead of being silently ignored). Minimal example covering every supported section:

{
  "channels": {
    "denyRules": [
      {
        "id": "no-telegram",
        "when": { "provider": "telegram" },
        "reason": "Telegram is not approved for this workspace.",
      },
    ],
  },
  "mcp": {
    "servers": {
      "allow": ["docs"],
      "deny": ["untrusted"],
    },
  },
  "models": {
    "providers": {
      "allow": ["openai", "anthropic"],
      "deny": ["openrouter"],
    },
  },
  "network": {
    "privateNetwork": {
      "allow": false,
    },
  },
  "routing": {
    "requireBindings": true,
    "requireConfiguredChannels": true,
    "probes": [
      {
        "id": "family-dm",
        "route": {
          "channel": "imessage",
          "peer": { "kind": "direct", "id": "+15555550123" },
        },
        "expect": {
          "agentId": "family",
          "matchedBy": ["binding.peer"],
        },
      },
    ],
  },
  "ingress": {
    "session": {
      "requireDmScope": "per-channel-peer",
    },
    "channels": {
      "allowDmPolicies": ["pairing", "allowlist", "disabled"],
      "denyOpenGroups": true,
      "requireMentionInGroups": true,
    },
  },
  "gateway": {
    "exposure": {
      "allowNonLoopbackBind": false,
      "allowTailscaleFunnel": false,
    },
    "auth": {
      "requireAuth": true,
      "requireExplicitRateLimit": true,
    },
    "controlUi": {
      "allowInsecure": false,
    },
    "remote": {
      "allow": false,
    },
    "http": {
      "denyEndpoints": ["chatCompletions", "responses"],
      "requireUrlAllowlists": true,
    },
    "nodes": {
      "denyCommands": ["system.run"],
    },
  },
  "agents": {
    "workspace": {
      "allowedAccess": ["none", "ro"],
      "denyTools": ["exec", "process", "write", "edit", "apply_patch"],
    },
  },
  "dataHandling": {
    "sensitiveLogging": {
      "requireRedaction": true,
    },
    "telemetry": {
      "denyContentCapture": true,
    },
    "retention": {
      "requireSessionMaintenance": true,
    },
    "memory": {
      "denySessionTranscriptIndexing": true,
    },
  },
  "secrets": {
    "requireManagedProviders": true,
    "denySources": ["exec"],
    "allowInsecureProviders": false,
  },
  "auth": {
    "profiles": {
      "requireMetadata": ["provider", "mode"],
      "allowModes": ["api_key", "token"],
    },
  },
  "execApprovals": {
    "requireFile": true,
    "defaults": { "allowSecurity": ["deny"] },
    "agents": {
      "allowSecurity": ["deny", "allowlist"],
      "allowAutoAllowSkills": false,
      "allowlist": { "expected": ["deploy", "status"] },
    },
  },
  "tools": {
    "requireMetadata": ["risk", "sensitivity", "owner"],
    "profiles": {
      "allow": ["messaging", "minimal"],
    },
    "fs": {
      "requireWorkspaceOnly": true,
    },
    "exec": {
      "allowSecurity": ["deny", "allowlist"],
      "requireAsk": ["always"],
      "allowHosts": ["sandbox"],
    },
    "elevated": {
      "allow": false,
    },
    "denyTools": ["group:runtime", "group:fs"],
  },
}

Cross-cutting notes not obvious from the rule tables below:

  • If gateway.bind is omitted while denying non-loopback binds, you accept the runtime default; set gateway.bind: "loopback" for strict conformance.
  • For a read-only agent, set sandbox mode to all or non-main on the applicable defaults/agent and workspaceAccess to none or ro. Missing or off sandbox mode does not satisfy a read-only policy.
  • agents.workspace.denyTools accepts exec, process, write, edit, apply_patch. The config tool-deny groups group:fs (file mutation) and group:runtime (shell/process) satisfy the equivalent posture.
  • Exec-approvals checks read the live SQLite approvals document only when an execApprovals rule is present; a missing or invalid artifact is unobservable evidence, not a synthetic pass.
  • Secret and auth-profile evidence records provider/source posture and SecretRef metadata only, never raw values. Policy does not read or attest per-agent credential stores such as openclaw-agent.sqlite.
  • Data-handling evidence is config-level posture (telemetry capture toggle, session maintenance mode, transcript-indexing setting) plus the always-on log redaction invariant. It does not inspect logs, telemetry exports, transcripts, or memory files, and a clean result does not prove that no personal data or secrets exist in them.
  • Routing probes reuse OpenClaw's runtime binding resolver. Routing evidence records only the probe id, resolved agent, match kind, and redacted binding metadata. It never records peer, account, guild, team, or role identifiers. Adding a routing section intentionally changes the policy and attestation hashes; policies without routing keep their existing evidence shape.

Policy rule reference

Every rule below is optional; a check runs only when the rule is present. The observed state is existing OpenClaw config or workspace metadata.

Scoped overlays

Use scopes.<scopeName> when specific agents or channels need stricter policy than the top-level baseline. The scope name is just a label; matching uses the selector inside the scope. Overlays are additive: the global rule still runs, and the scoped rule can add its own finding against the same evidence.

SelectorSupported sectionsUse when
agentIdstools, agents.workspace, sandbox, dataHandling.memory, execApprovalsOne or more runtime agents need stricter rules.
channelIdsingress.channelsOne or more channels need stricter ingress rules.

If an agentIds entry is not present in agents.entries.*, OpenClaw evaluates the scoped rule against inherited global/default posture for that runtime agent id instead of skipping it.

{
  "tools": {
    "exec": {
      "allowHosts": ["sandbox", "node"],
    },
  },
  "sandbox": {
    "requireMode": ["all", "non-main"],
  },
  "scopes": {
    "release-workspace": {
      "agentIds": ["release-agent", "review-agent"],
      "agents": {
        "workspace": {
          "allowedAccess": ["none", "ro"],
        },
      },
    },
    "release-lockdown": {
      "agentIds": ["release-agent"],
      "tools": {
        "exec": {
          "allowHosts": ["sandbox"],
          "allowSecurity": ["deny", "allowlist"],
          "requireAsk": ["always"],
        },
        "denyTools": ["exec", "process", "write", "edit", "apply_patch"],
      },
      "sandbox": {
        "requireMode": ["all"],
        "allowBackends": ["docker"],
      },
      "dataHandling": {
        "memory": {
          "denySessionTranscriptIndexing": true,
        },
      },
    },
    "shell-sandbox": {
      "agentIds": ["shell-agent"],
      "sandbox": {
        "allowBackends": ["openshell"],
        "containers": {
          "requireReadOnlyMounts": false,
        },
      },
    },
    "telegram-ingress": {
      "channelIds": ["telegram"],
      "ingress": {
        "channels": {
          "allowDmPolicies": ["pairing"],
          "denyOpenGroups": true,
          "requireMentionInGroups": true,
        },
      },
    },
  },
}

The same agent can appear in multiple scopes if each scope governs a different field, as above. A repeated scoped field for the same agent must be equally or more restrictive; a weaker duplicate claim is rejected (allow-lists are subsets, deny-lists are supersets, required booleans are fixed).

Container posture rules (sandbox.containers.*) are checked only against evidence the matched agent's sandbox backend can expose. The Docker and Podman backends expose the same sandbox.docker.* container posture settings. If a backend cannot observe a rule you enabled for it, policy reports policy/sandbox-container-posture-unobservable instead of passing; scope container rules to the agent groups that use a backend which can expose them.

Backend authorization uses the configured identity. backend: "docker" requires allowBackends: ["docker"], while backend: "podman" requires allowBackends: ["podman"].

Top-level ingress.session.requireDmScope stays global; session.dmScope is not channel-attributable evidence, so it cannot be scoped by channelIds.

Every scope present in policy.jsonc must be valid and enforceable.

Channels

Policy fieldObserved stateUse when
channels.denyRules[].when.providerchannels.* provider and enabled stateBlock channels coming from a provider like telegram.
channels.denyRules[].reasonFinding message and repair hint contextClarify the reasoning behind the provider denial.

MCP servers

Policy fieldObserved stateUse when
mcp.servers.allowmcp.servers.* idsForce every MCP server that is configured to sit on an allowlist.
mcp.servers.denymcp.servers.* idsReject particular MCP server ids that are configured.

Model providers

Policy fieldObserved stateUse when
models.providers.allowmodels.providers.* ids and selected model refsEnsure configured providers and chosen model refs stick to approved providers.
models.providers.denymodels.providers.* ids and selected model refsRefuse configured providers and chosen model refs according to provider id.

Network

Policy fieldObserved stateUse when
network.privateNetwork.allowPrivate-network SSRF escape hatchesAssign false to keep private-network access turned off.

Message routing

Policy fieldObserved stateUse when
routing.requireBindingsChannel route bindings, excluding ACP bindingsDemand a minimum of one message-routing binding.
routing.requireConfiguredChannelsBinding channel ids and configured channels.* idsSpot binding channel ids that are outdated or mistyped.
routing.probes[].routeThe public OpenClaw route resolverShow a typical inbound route without dispatching a message.
routing.probes[].expect.agentIdResolved agent idForce the route to land on the agent under review.
routing.probes[].expect.matchedByResolver match kindDemand peer, account, channel, or other reviewed binding specificity.

Probe ids must not repeat. A route accommodates channel, optional accountId, peer, parentPeer, guildId, teamId, and memberRoleIds. Peer kinds are direct, group, and channel. matchedBy can hold one or more runtime match kinds, such as binding.peer, binding.account, binding.channel, or default.

Routing checks serve as conformance checks only. They leave startup, message delivery, binding precedence, and fallback behavior untouched. Findings call for operator review, since a binding changed automatically could redirect private messages.

Ingress and channel access

Policy fieldObserved stateUse when
ingress.session.requireDmScopesession.dmScopeDemand a reviewed isolation scope for direct messages.
ingress.channels.allowDmPolicieschannels.*.dmPolicy and legacy channel DM policy fieldsPermit only reviewed direct-message channel policies.
ingress.channels.denyOpenGroupsChannel, account, and group ingress policyBlock open group ingress for the configured channels and accounts.
ingress.channels.requireMentionInGroupsChannel, account, group, guild, and nested mention gate configRequire mention gates whenever group ingress is open or gated by mentions.

Gateway

Policy fieldObserved stateUse when
gateway.exposure.allowNonLoopbackBindgateway.bindAssign false to enforce loopback Gateway binding.
gateway.exposure.allowTailscaleFunnelTailscale serve/funnel Gateway postureAssign false to block Tailscale Funnel exposure.
gateway.auth.requireAuthgateway.auth.modeAssign true to refuse disabled Gateway auth.
gateway.auth.requireExplicitRateLimitgateway.auth.rateLimitAssign true to mandate explicit auth rate-limit configuration.
gateway.controlUi.allowInsecureDevice-identity invariant and origin fallbackAssign false to mandate device identity and forbid Host-header origin fallback.
gateway.remote.allowRemote Gateway mode/configAssign false to forbid remote Gateway mode.
gateway.http.denyEndpointsGateway HTTP API endpointsReject endpoint ids like chatCompletions or responses.
gateway.http.requireUrlAllowlistsGateway HTTP URL-fetch inputsAssign true to mandate URL allowlists on URL-fetch inputs.
gateway.nodes.denyCommandsgateway.nodes.commands.denyMandate exact node command ids such as system.run to be denied in OpenClaw config.

gateway.nodes.denyCommands functions as an exact, case-sensitive policy deny-superset rule. Deploy it when policy must demonstrate that privileged node commands are explicitly denied via OpenClaw config. A deployment that deliberately permits a privileged node command should revise policy.jsonc after review rather than depending on gateway.nodes.commands.allow alone.

Agent workspace

Policy fieldObserved stateUse when
agents.workspace.allowedAccessagents.defaults.sandbox.workspaceAccess and agents.entries.*.sandbox.workspaceAccessPermit only sandbox workspace access values like none or ro.
agents.workspace.denyToolsGlobal and per-agent tool deny configRequire mutation tools (exec, process, write, edit, apply_patch) to be denied.

Sandbox posture

Policy fieldObserved stateUse when
sandbox.requireModeagents.defaults.sandbox.mode and per-agent modePermit only reviewed sandbox modes such as all or non-main.
sandbox.allowBackendsagents.defaults.sandbox.backend and per-agent backendPermit only reviewed sandbox backends such as docker or podman.
sandbox.containers.denyHostNetworkContainer-backed sandbox/browser network modeForbid host network mode.
sandbox.containers.denyContainerNamespaceJoinContainer-backed sandbox/browser network modeForbid joining another container network namespace.
sandbox.containers.requireReadOnlyMountsContainer-backed sandbox/browser mount modeRequire mounts to be read-only.
sandbox.containers.denyContainerRuntimeSocketMountsContainer-backed sandbox/browser mount targetsForbid container runtime socket mounts.
sandbox.containers.denyUnconfinedProfilesContainer security profile postureForbid unconfined container security profiles.
sandbox.browser.requireCdpSourceRangeSandbox browser CDP source rangeRequire browser CDP exposure to declare a source range.

Policy treats missing sandbox.mode as its implicit default off, so sandbox.requireMode reports a fresh or unconfigured sandbox as outside an allowlist such as ["all"].

Data Handling

Policy fieldObserved stateUse when
dataHandling.sensitiveLogging.requireRedactionRuntime invariant oc://openclaw.invariant/logging/redactionSet to true to record the requirement; OpenClaw always satisfies it.
dataHandling.telemetry.denyContentCapturediagnostics.otel.captureContentSet to true to reject telemetry content capture.
dataHandling.retention.requireSessionMaintenancesession.maintenance.modeSet to true to require effective session maintenance mode enforce.
dataHandling.memory.denySessionTranscriptIndexingmemory.search.experimental.sessionMemory, memory.search.rememberAcrossConversations, and per-agent overridesSet to true to reject session transcript indexing into memory.

Secrets

Policy fieldObserved stateUse when
secrets.requireManagedProvidersConfig SecretRefs and secrets.providers.* declarationsSet to true to require SecretRefs to point at declared providers.
secrets.denySourcesSecret provider sources and SecretRef sourcesDeny sources such as exec, file, or another configured source name.
secrets.allowInsecureProvidersInsecure secret-provider posture flagsSet to false to reject providers that opt into insecure posture.

Exec approvals

Exec-approvals checks read the runtime exec_approvals_config singleton row in ~/.openclaw/state/openclaw.sqlite by default, or the same database under $OPENCLAW_STATE_DIR/state when OPENCLAW_STATE_DIR is set. Findings keep the stable oc://exec-approvals.json/... URI scheme; it now denotes paths within the authoritative JSON document stored in that row. Posture rules under execApprovals.defaults.* or execApprovals.agents.* require readable artifact evidence; a missing or invalid artifact reports as unobservable evidence rather than a best-effort pass. Once readable, omitted fields inherit runtime defaults: missing defaults.security is full, and missing agent security inherits that default. Evidence includes defaults, agents.*, agents.*.allowlist[].pattern, optional argPattern, effective autoAllowSkills posture, and entry source, never socket path/token, commandText, lastUsedCommand, resolved paths, or timestamps.

Policy fieldObserved stateUse when
execApprovals.requireFileActive runtime exec_approvals_config rowSet to true to require the approvals document to exist and parse.
execApprovals.defaults.allowSecuritydefaults.security, defaulting to fullAllow only approved default approval security modes.
execApprovals.agents.allowSecurityagents.*.security, inheriting defaultsAllow only approved per-agent effective approval security modes.
execApprovals.agents.allowAutoAllowSkillsdefaults.autoAllowSkills and agents.*.autoAllowSkills, inheriting runtime defaultsSet to false to require strict manual allowlists without implicit skill CLI approval.
execApprovals.agents.allowlist.expectedAggregate agents.*.allowlist[] pattern and optional argPattern entriesRequire the approvals allowlist to match the reviewed pattern set.

Example: require the approvals artifact, deny permissive defaults, and allow only reviewed exec approval posture for selected agents.

{
  "execApprovals": {
    "requireFile": true,
    "defaults": {
      // Security modes: "deny", "allowlist", or "full".
      // This default permits only the locked-down deny posture.
      "allowSecurity": ["deny"],
    },
  },
  "scopes": {
    "restricted-shell": {
      "agentIds": ["family-agent", "groups-agent"],
      "execApprovals": {
        "agents": {
          // Selected agents may use reviewed allowlist posture, but not "full".
          "allowSecurity": ["allowlist"],
          // false means skill CLIs must appear in the reviewed allowlist instead of
          // being implicitly approved by autoAllowSkills.
          "allowAutoAllowSkills": false,
          "allowlist": {
            "expected": [
              // Simple entry: exact reviewed executable pattern with no argPattern.
              "travel-hub",
              // Constrained entry: pattern plus reviewed argument regex.
              { "pattern": "calendar-cli", "argPattern": "^sync\\b" },
              "/bin/date",
            ],
          },
        },
      },
    },
  },
}

Auth profiles

Policy fieldObserved stateUse when
auth.profiles.requireMetadataauth.profiles.* provider and mode metadataDemand metadata keys like provider and mode on config auth profiles.
auth.profiles.allowModesauth.profiles.*.modeRestrict auth profile modes to supported ones such as api_key, aws-sdk, oauth, or token.

Tool metadata

Policy fieldObserved stateUse when
tools.requireMetadataGoverned AGENTS.md tool declarationsMake governed tools declare metadata keys such as risk, sensitivity, or owner.

Tool posture

Policy fieldObserved stateUse when
tools.profiles.allowtools.profile and agents.entries.*.tools.profilePermit only tool profile ids such as minimal, messaging, or coding.
tools.fs.requireWorkspaceOnlytools.fs.workspaceOnly and per-agent tools.fs overridesConfigure true to enforce a workspace-only filesystem tool posture.
tools.exec.allowSecuritytools.exec.security and per-agent exec securityAllow exec security modes only like deny or allowlist.
tools.exec.requireAsktools.exec.ask and per-agent exec ask modeEnforce an approval posture such as always.
tools.exec.allowHoststools.exec.host and per-agent exec host routingLimit exec host routing modes to those like sandbox.
tools.elevated.allowtools.elevated.enabled and per-agent elevated postureSet false to keep elevated tool mode disabled.
tools.alsoAllow.expectedtools.alsoAllow and per-agent tools.alsoAllowMandate precise alsoAllow entries, flagging missing or unexpected additive tool grants.
tools.denyToolstools.deny and agents.entries.*.tools.denyEnsure configured tool deny lists cover tool ids or groups such as group:runtime and group:fs.

Run checks

Execute policy-only checks while authoring:

openclaw policy check
openclaw policy check --agent ops
openclaw policy check --json
openclaw policy check --severity-min error

With policy check, only the policy check set runs, producing evidence, findings, and attestation hashes. When the Policy plugin is enabled, those findings surface in openclaw doctor --lint as well. For a multi-agent fleet with explicit ownership, supply --agent <id> so the command pulls governed declarations and policy.jsonc from that agent's workspace. Without the flag, a sole-agent or retained legacy-owner setup still resolves; OpenClaw never picks an arbitrary first agent.

Compare an operator policy file to an authored baseline:

openclaw policy compare --baseline official.policy.jsonc
openclaw policy compare --baseline official.policy.jsonc --agent ops
openclaw policy compare --baseline official.policy.jsonc --policy policy.jsonc --json

policy compare validates policy-file syntax against itself; runtime state, evidence, credentials, and secrets are not inspected. The same rule metadata governing scoped overlays applies here: allowlists must remain identical or tighter, denylists identical or wider, required booleans unchanged, ordered strings only shifting toward the stricter end of the configured sequence, and exact lists matching exactly. The baseline may be an organization-authored policy; the checked policy can introduce stricter values or additional rules. A top-level checked rule can fulfill a scoped baseline rule when it is equally or more restrictive. Scope names across files need not align; comparison keys on selector (agentIds/channelIds) and field. For routing probes, every baseline probe id must stay attached to the same route and expected agent. A checked policy may add probes or tighten matchedBy, yet removing a probe, altering its route or agent, or broadening its accepted match kinds counts as weaker.

When the checked policy path originates from the plugin configuration and is relative, --agent <id> picks the workspace for resolution. Absolute policy paths function independently of an agent workspace.

Clean compare (--json):

{
  "ok": true,
  "baselinePath": "official.policy.jsonc",
  "policyPath": "policy.jsonc",
  "rulesChecked": 3,
  "findings": []
}

Clean policy check --json output provides stable hashes that an operator or supervisor can note down:

{
  "ok": true,
  "attestation": {
    "policy": {
      "path": "policy.jsonc",
      "hash": "sha256:..."
    },
    "workspace": {
      "scope": "policy",
      "hash": "sha256:..."
    },
    "findingsHash": "sha256:...",
    "attestationHash": "sha256:..."
  },
  "checksRun": 5,
  "checksSkipped": 0,
  "findings": []
}

Configure policy

Policy configuration sits under plugins.entries.policy.config.

{
  "plugins": {
    "entries": {
      "policy": {
        "enabled": true,
        "config": {
          "enabled": true,
          "path": "policy.jsonc",
          "workspaceRepairs": false,
          "expectedHash": "sha256:...",
          "expectedAttestationHash": "sha256:...",
        },
      },
    },
  },
}
SettingPurpose
enabledActivate policy checks even before policy.jsonc is present.
workspaceRepairsPermit doctor --fix to modify policy-managed workspace settings.
expectedHashOptional hash-lock for the approved policy artifact.
expectedAttestationHashOptional hash-lock for the last accepted clean policy check.
pathWorkspace-relative path to the policy artifact.

Assign plugins.entries.policy.config.enabled the value false to turn off policy checks for a workspace while keeping the plugin installed.

Accept policy state

Example JSON output:

{
  "ok": true,
  "attestation": {
    "checkedAt": "2026-05-10T20:00:00.000Z",
    "policy": {
      "path": "policy.jsonc",
      "hash": "sha256:..."
    },
    "workspace": {
      "scope": "policy",
      "hash": "sha256:..."
    },
    "findingsHash": "sha256:...",
    "attestationHash": "sha256:..."
  },
  "evidence": {
    "channels": [
      {
        "id": "telegram",
        "provider": "telegram",
        "source": "oc://openclaw.config/channels/telegram",
        "enabled": false
      }
    ],
    "mcpServers": [
      {
        "id": "docs",
        "transport": "stdio",
        "source": "oc://openclaw.config/mcp/servers/docs",
        "command": "npx"
      }
    ],
    "modelProviders": [
      {
        "id": "openai",
        "source": "oc://openclaw.config/models/providers/openai"
      }
    ],
    "modelRefs": [
      {
        "ref": "openai/gpt-5.6-sol",
        "provider": "openai",
        "model": "gpt-5.6-sol",
        "source": "oc://openclaw.config/agents/defaults/model"
      }
    ],
    "network": [
      {
        "id": "browser-private-network",
        "source": "oc://openclaw.config/browser/ssrfPolicy/dangerouslyAllowPrivateNetwork",
        "value": false
      }
    ],
    "gatewayExposure": [
      {
        "id": "gateway-bind",
        "kind": "bind",
        "source": "oc://openclaw.config/gateway/bind",
        "value": "loopback",
        "nonLoopback": false,
        "explicit": true
      }
    ],
    "agentWorkspace": [
      {
        "id": "agents-defaults-workspace-access",
        "kind": "workspaceAccess",
        "source": "oc://openclaw.config/agents/defaults/sandbox/workspaceAccess",
        "scope": "defaults",
        "value": "ro",
        "sandboxMode": "all",
        "sandboxModeSource": "oc://openclaw.config/agents/defaults/sandbox/mode",
        "sandboxEnabled": true,
        "explicit": true
      },
      {
        "id": "agents-defaults-tool-exec",
        "kind": "toolDeny",
        "source": "oc://openclaw.config/tools/deny",
        "scope": "defaults",
        "tool": "exec",
        "denied": true,
        "explicit": true
      }
    ],
    "secrets": [
      {
        "id": "vault",
        "kind": "provider",
        "source": "oc://openclaw.config/secrets/providers/vault",
        "providerSource": "env"
      },
      {
        "id": "oc://openclaw.config/models/providers/openai/apiKey",
        "kind": "input",
        "source": "oc://openclaw.config/models/providers/openai/apiKey",
        "provenance": "secretRef",
        "refSource": "env",
        "refProvider": "vault"
      }
    ],
    "authProfiles": [
      {
        "id": "github",
        "source": "oc://openclaw.config/auth/profiles/github",
        "validMetadata": true,
        "provider": "github",
        "mode": "token"
      }
    ],
    "tools": [
      {
        "id": "deploy",
        "source": "oc://AGENTS.md/tools/deploy",
        "line": 12,
        "risk": "critical",
        "sensitivity": "restricted",
        "capabilities": ["IRREVERSIBLE_EXTERNAL"]
      }
    ]
  },
  "checksRun": 30,
  "checksSkipped": 0,
  "findings": []
}

attestation.policy.hash points to the authored rule artifact. evidence captures the OpenClaw state observed during checks, and workspace.hash references that evidence payload. findingsHash identifies the exact finding set. checkedAt logs the check timestamp. attestationHash identifies the stable claim (policy hash, evidence hash, findings hash, and clean/dirty status) and intentionally omits checkedAt, so identical policy state always yields the same attestation hash. These four values together form the audit tuple for a single policy check.

When a gateway or supervisor uses policy to block, approve, or annotate a runtime action, it should log the attestation hash from the most recent clean check. checkedAt remains in JSON output for audit logs but falls outside the stable hash.

Lifecycle for accepting policy state:

  1. Author or review policy.jsonc.
  2. Execute openclaw policy check --json.
  3. If clean, save attestation.policy.hash as expectedHash.
  4. Save attestation.attestationHash as expectedAttestationHash.
  5. Re-run openclaw doctor --lint in CI or release gates.

If policy rules change deliberately, refresh both accepted hashes from a clean check. If only workspace settings shift (policy unchanged), typically just expectedAttestationHash updates.

Enabling or upgrading agents.workspace rules injects agentWorkspace evidence into the workspace hash and attestation hash; review the new evidence and refresh accepted attestation hashes after enabling. Enabling or upgrading tool posture rules adds toolPosture evidence in the same manner.

openclaw policy watch re-executes the check and reports when current evidence no longer aligns with expectedAttestationHash:

openclaw policy watch --json
openclaw policy watch --agent ops --json

Use --once in CI or scripts needing a single drift evaluation. Without --once, it polls every two seconds by default; use --interval-ms to adjust the interval.

Findings

Check idFinding
policy/policy-jsonc-missingThe policy is active, yet policy.jsonc has not been supplied.
policy/policy-jsonc-invalidParsing of the policy failed, or its rule entries are malformed.
policy/policy-hash-mismatchThe policy fails to align with the configured expectedHash.
policy/attestation-hash-mismatchThe accepted attestation no longer corresponds to the current policy evidence.
policy/policy-conformance-invalidComparison syntax in a baseline or checked policy file is invalid.
policy/policy-conformance-missingA rule mandated by the baseline policy file is absent from the checked file.
policy/policy-conformance-weakerA checked policy file specifies a value weaker than the baseline policy file.
policy/channels-denied-providerAn active channel falls under a channel deny rule.
policy/mcp-denied-serverPolicy blocks a configured MCP server.
policy/mcp-unapproved-serverA configured MCP server sits outside the allowlist.
policy/models-denied-providerA model provider or model ref in the config uses a provider that is denied.
policy/models-unapproved-providerA model provider or model ref in the config is not on the allowlist.
policy/network-private-access-enabledThe private-network SSRF escape hatch is on, but policy forbids it.
policy/routing-bindings-requiredA channel route binding is demanded by policy, yet none is set up.
policy/routing-binding-channel-unconfiguredA route binding points to a channel that is missing from channels.*.
policy/routing-agent-mismatchAn authored route resolves to a different agent than intended.
policy/routing-match-kind-mismatchAn authored route matches at a binding specificity that is unexpected.
policy/ingress-dm-policy-unapprovedThe channel DM policy falls outside the policy allowlist.
policy/ingress-dm-scope-unapprovedsession.dmScope fails to meet the DM isolation scope required by policy.
policy/ingress-open-groups-deniedA channel group policy is set to open, while policy disallows open group ingress.
policy/ingress-group-mention-requiredMention gates are disabled for a channel or group entry, though policy requires them.
policy/gateway-non-loopback-bindThe gateway bind posture allows non-loopback exposure, which policy denies.
policy/gateway-auth-disabledGateway authentication is off, but policy mandates it.
policy/gateway-rate-limit-missingThe gateway auth rate-limit posture is not explicit, though policy demands it.
policy/gateway-control-ui-insecureInsecure exposure toggles for the Gateway Control UI are switched on.
policy/gateway-tailscale-funnelGateway Tailscale Funnel exposure is active, but policy forbids it.
policy/gateway-remote-enabledRemote mode for the gateway is running, though policy denies it.
policy/gateway-http-endpoint-enabledA Gateway HTTP API endpoint is turned on, yet policy denies it.
policy/gateway-http-url-fetch-unrestrictedURL-fetch input on the Gateway HTTP API lacks a required URL allowlist.
policy/gateway-node-command-deniedA node command that policy denies is not denied by OpenClaw config.
policy/agents-workspace-access-deniedAgent sandbox mode or workspace access is not within the policy allowlist.
policy/agents-tool-not-deniedA tool that policy requires to be denied is not denied by an agent or default config.
policy/tools-profile-unapprovedA global or per-agent tool profile in the config is outside the allowlist.
policy/tools-fs-workspace-only-requiredFilesystem tools lack the workspace-only path posture configuration.
policy/tools-exec-security-unapprovedExec security mode is not listed in the policy allowlist.
policy/tools-exec-ask-unapprovedExec ask mode is not listed in the policy allowlist.
policy/tools-exec-host-unapprovedExec host routing is not listed in the policy allowlist.
policy/tools-elevated-enabledElevated tool mode is on, but policy denies it.
policy/tools-also-allow-missingA configured alsoAllow list omits an entry that policy requires.
policy/tools-also-allow-unexpectedA configured alsoAllow list contains an entry that policy does not expect.
policy/tools-required-deny-missingA global or per-agent tool deny list misses a tool that must be denied.
policy/sandbox-mode-unapprovedSandbox mode is not within the policy allowlist.
policy/sandbox-backend-unapprovedSandbox backend is not within the policy allowlist.
policy/sandbox-container-posture-unobservableA container posture rule is active for a backend that cannot observe it.
policy/sandbox-container-host-network-deniedA container-backed sandbox or browser operates in host network mode.
policy/sandbox-container-namespace-join-deniedA container-backed sandbox or browser joins the namespace of another container.
policy/sandbox-container-mount-mode-requiredA mount in a container-backed sandbox or browser is not read-only.
policy/sandbox-container-runtime-socket-mountA mount in a container-backed sandbox or browser exposes the container runtime socket.
policy/sandbox-container-unconfined-profileThe container sandbox profile is unconfined, which policy denies.
policy/sandbox-browser-cdp-source-range-missingThe sandbox browser CDP source range is absent, though policy requires one.
policy/data-handling-telemetry-content-captureTelemetry content capture is enabled, but policy denies it.
policy/data-handling-session-retention-not-enforcedSession retention maintenance is not enforced, though policy requires it.
policy/data-handling-session-transcript-memory-enabledSession transcript memory indexing is on, but policy denies it.
policy/secrets-unmanaged-providerA provider referenced by a config SecretRef is absent from the declarations under secrets.providers.
policy/secrets-denied-provider-sourceA source used by a config secret provider or SecretRef is forbidden by policy.
policy/secrets-insecure-providerPolicy denies an insecure posture, yet a secret provider opts into it.
policy/auth-profile-invalid-metadataAn auth profile in config lacks valid metadata for provider or mode.
policy/auth-profile-unapproved-modeThe mode of a config auth profile falls outside the policy allowlist.
policy/exec-approvals-missingPolicy demands the SQLite exec approvals document, but its row is absent.
policy/exec-approvals-invalidParsing the configured SQLite exec approvals document fails.
policy/exec-approvals-default-security-unapprovedA security mode in exec approval defaults sits outside the policy allowlist.
policy/exec-approvals-agent-security-unapprovedThe effective exec approval security mode for an agent lies outside the allowlist.
policy/exec-approvals-auto-allow-skills-enabledPolicy denies implicit auto-allow of skill CLIs, yet an exec approval agent does it.
policy/exec-approvals-allowlist-missingA pattern mandated by policy is missing from the approvals allowlist.
policy/exec-approvals-allowlist-unexpectedThe approvals allowlist holds a pattern that policy does not anticipate.
policy/tools-missing-risk-levelRisk metadata is absent from a governed tool declaration.
policy/tools-unknown-risk-levelA governed tool declaration carries an unrecognized risk value.
policy/tools-missing-sensitivity-tokenSensitivity metadata is missing from a governed tool declaration.
policy/tools-missing-ownerOwner metadata is absent from a governed tool declaration.
policy/tools-unknown-sensitivity-tokenA governed tool declaration uses a sensitivity value that is unrecognized.

A finding may carry two parts: target for the workspace object observed to deviate, and requirement for the authored rule that triggered the finding. Currently both are oc:// address strings, though the field names indicate policy role rather than address format.

Example findings:

{
  "checkId": "policy/channels-denied-provider",
  "severity": "error",
  "message": "Channel 'telegram' uses denied provider 'telegram'.",
  "source": "policy",
  "path": "openclaw config",
  "ocPath": "oc://openclaw.config/channels/telegram",
  "target": "oc://openclaw.config/channels/telegram",
  "requirement": "oc://policy.jsonc/channels/denyRules/#0",
  "fixHint": "Telegram is not approved for this workspace."
}
{
  "checkId": "policy/tools-missing-risk-level",
  "severity": "error",
  "message": "AGENTS.md tool 'deploy' has no explicit risk classification.",
  "source": "policy",
  "path": "AGENTS.md",
  "line": 12,
  "ocPath": "oc://AGENTS.md/tools/deploy",
  "target": "oc://AGENTS.md/tools/deploy",
  "requirement": "oc://policy.jsonc/tools/requireMetadata"
}
{
  "checkId": "policy/mcp-unapproved-server",
  "severity": "error",
  "message": "MCP server 'remote' is not in the policy allowlist.",
  "source": "policy",
  "path": "openclaw config",
  "ocPath": "oc://openclaw.config/mcp/servers/remote",
  "target": "oc://openclaw.config/mcp/servers/remote",
  "requirement": "oc://policy.jsonc/mcp/servers/allow"
}
{
  "checkId": "policy/models-unapproved-provider",
  "severity": "error",
  "message": "Model ref 'anthropic/claude-sonnet-4.7' uses unapproved provider 'anthropic'.",
  "source": "policy",
  "path": "openclaw config",
  "ocPath": "oc://openclaw.config/agents/defaults/model/fallbacks/#0",
  "target": "oc://openclaw.config/agents/defaults/model/fallbacks/#0",
  "requirement": "oc://policy.jsonc/models/providers/allow"
}
{
  "checkId": "policy/network-private-access-enabled",
  "severity": "error",
  "message": "Network setting 'browser-private-network' allows private-network access.",
  "source": "policy",
  "path": "openclaw config",
  "ocPath": "oc://openclaw.config/browser/ssrfPolicy/dangerouslyAllowPrivateNetwork",
  "target": "oc://openclaw.config/browser/ssrfPolicy/dangerouslyAllowPrivateNetwork",
  "requirement": "oc://policy.jsonc/network/privateNetwork/allow"
}
{
  "checkId": "policy/gateway-non-loopback-bind",
  "severity": "error",
  "message": "Gateway bind setting 'gateway-bind' permits non-loopback exposure.",
  "source": "policy",
  "path": "openclaw config",
  "ocPath": "oc://openclaw.config/gateway/bind",
  "target": "oc://openclaw.config/gateway/bind",
  "requirement": "oc://policy.jsonc/gateway/exposure/allowNonLoopbackBind"
}
{
  "checkId": "policy/gateway-node-command-denied",
  "severity": "error",
  "message": "Gateway node command 'system.run' is denied by policy but not denied by OpenClaw config.",
  "source": "policy",
  "path": "openclaw config",
  "ocPath": "oc://openclaw.config/gateway/nodes/commands/deny",
  "target": "oc://openclaw.config/gateway/nodes/commands/deny",
  "requirement": "oc://policy.jsonc/gateway/nodes/denyCommands",
  "fixHint": "Add 'system.run' to gateway.nodes.commands.deny or update policy after review."
}
{
  "checkId": "policy/agents-workspace-access-denied",
  "severity": "error",
  "message": "agents.defaults sandbox workspaceAccess 'rw' is not allowed by policy.",
  "source": "policy",
  "path": "openclaw config",
  "ocPath": "oc://openclaw.config/agents/defaults/sandbox/workspaceAccess",
  "target": "oc://openclaw.config/agents/defaults/sandbox/workspaceAccess",
  "requirement": "oc://policy.jsonc/agents/workspace/allowedAccess"
}

Repair

Access to doctor --lint and policy check is read-only.

When workspaceRepairs is not explicitly turned on, doctor --fix only touches policy-managed workspace settings; otherwise, checks report what they would fix and leave settings untouched.

In this release, repair can turn off channels that channels.denyRules denies and carry out the automatic narrowing repairs listed below. Turn on workspaceRepairs only after reviewing the policy file, since a valid rule can alter workspace config:

  • apply tools.elevated.enabled=false when a global policy blocks elevated tools
  • insert missing required-deny tool ids into tools.deny or agents.entries.*.tools.deny when policy requires those tools to be denied
  • flip insecure gateway.controlUi.* toggles to false
  • apply gateway.mode=local when policy blocks remote gateway mode
  • change reported gateway.http.endpoints.*.enabled paths to false when policy blocks Gateway HTTP API endpoints
  • change reported channel ingress groupPolicy paths to allowlist when policy blocks open group ingress
  • change reported channel ingress requireMention paths to true when policy requires group mentions
  • apply diagnostics.otel.captureContent=false, or diagnostics.otel.captureContent.enabled=false for object-form telemetry capture settings, when policy blocks telemetry content capture

Repairs scoped to elevated tools are detect-only. Scoped data-handling repairs are also skipped when the finding points to shared telemetry config, because altering the shared setting would impact more than the scoped policy target.

dataHandling.sensitiveLogging.requireRedaction offers neither check nor repair. OpenClaw always redacts sensitive logs, so nothing can flag it as off. The key remains a supported policy rule: openclaw policy checks its shape, openclaw policy compare still requires a candidate policy to be at least as strict as the baseline for it, and openclaw policy check logs the runtime invariant oc://openclaw.invariant/logging/redaction in the dataHandling evidence and attestation as proof the requirement is met.

Scoped required-deny repairs are skipped when the finding reports inherited root tools.deny, because adding the required tool to root config would impact more than the scoped policy target. Agent-local required-deny repairs can update the reported agents.entries.*.tools.deny path.

Scoped channel ingress repairs are skipped when the finding reports inherited channels.defaults.*, because changing the shared channel default would impact more than the scoped policy target. Gateway HTTP URL-fetch allowlist findings stay manual because automatic repair cannot pick the correct endpoint URL allowlist values.

Gateway bind and node-command findings always require review. When policy/gateway-non-loopback-bind or policy/gateway-node-command-denied can be resolved to a config path, doctor --fix surfaces the suggested gateway.bind or gateway.nodes.commands.deny adjustment as skipped preview guidance. No change is applied, and the finding stays unrepaired until an operator reviews and modifies config or policy.

{
  "plugins": {
    "entries": {
      "policy": {
        "config": {
          "workspaceRepairs": true,
        },
      },
    },
  },
}

Exit codes

Command012
policy checkNo findings at the threshold.One or more findings met the threshold.Argument or runtime failure.
policy compareThe policy file is at least as strict as the baseline.The policy file is invalid, missing, or weaker than baseline rules.Argument or runtime failure.
policy watchNo findings and accepted hash is current.Findings exist or accepted attestation is stale.Argument or runtime failure.
5,788 words · updated Aug 22, 2026