feat: trusted-operator security defaults for OpenClaw 2026.5.x#40
Merged
Conversation
Adopt a trusted-operator default config profile so a fresh clawctl create produces a working gateway without manual post-bootstrap edits. Recent upstream OpenClaw split sandbox into multiple independent policy layers; we currently only configure two of them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A introspection of a live VM. Real schema differs from web research in important ways: tools.exec.security is the primary gate, ~/.openclaw/exec-approvals.json is a parallel host-side policy that intersects with the config policy, and the 1Password capability hardcodes defaults.security=deny + agents.main.security=allowlist — which is the proximate cause of the observed exec denial. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The capability previously wrote security=deny defaults and agents.main.security=allowlist with just op in the allowlist. After the recent upstream policy-intersection model where per-agent overrides beat config requests, this silently blocked all exec on a fresh clawctl install — only the op binary ran, everything else returned security=deny. clawctl's trust model is single-trusted-operator (same person owns host and VM, gateway is reachable only over localhost/Tailscale), so this file is now clawctl-managed in the permissive direction: security=full everywhere. The op allowlist entry stays as forward-compatible documentation for operators who later flip to restrictive mode via openclaw approvals or by editing the file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Post-onboard now runs \`openclaw exec-policy preset yolo\` (sets tools.exec.security=full and matching approvals defaults) and writes: - commands.config=true — agent may use the /config slash command - tools.elevated.enabled=true — privileged surfaces available - tools.elevated.allowFrom.<channel> — auto-derived from each channel's allowFrom list, so the operator never repeats IDs Sandbox now defaults to off (was opt-in). Trusted-operator model: clawctl owns the host+VM, sandbox-as-protection adds friction without buying isolation we don't already have. Flip via \`agent.sandbox: true\` if desired. New typed field \`agent.elevated.allowFrom\` lets operators override the auto-derived elevated allowFrom map. Channel-derived defaults are the common case, so most operators won't need this. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cover what clawctl writes to the gateway config (tools.exec.security, commands.config, tools.elevated.*) and the auto-derivation of elevated.allowFrom from channel allowFrom. Document the new agent.elevated.allowFrom field and the new sandbox default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
telegram.allowFrom was declared as type "text" but the runtime accepts arrays (channels.ts postCommands.run uses Array.isArray) and the canonical form in configs and tests is array. The derived zod schema z.string() rejected array values during loadConfig validation, blocking any headless config that listed allowFrom IDs. New "stringList" field type derives to z.array(z.string()). Wizard editing for this type is not wired up yet (the field still renders read-only); the existing wizard never produced valid arrays anyway. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
commands.config=true gates whether the /config slash command exists, but commands.ownerAllowFrom gates whether *this sender* is allowed to call it (and other owner-only commands: /diagnostics, /export-trajectory, exec approvals). Doctor warns about an empty ownerAllowFrom even after we enable commands.config. We reuse the channel-derived allowFrom map: the senders the operator trusted at the channel layer are the same humans they want as owners. Flatten to "<channel>:<id>" strings, which is the format commands.ownerAllowFrom expects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
E2E verified on a fresh sam instance: tools.exec.security=full, commands.config=true, tools.elevated.enabled=true, commands.ownerAllowFrom auto-derived as telegram:<id>, sandbox off, 1Password no longer overrides defaults. openclaw doctor no longer flags the command-owner warning. Re-apply is idempotent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
tools.exec.*config +~/.openclaw/exec-approvals.jsonhost file +tools.elevated.*+commands.*). On a freshclawctl create, agents could not exec, install packages, or use/config— bootstrap left an unusable gateway.one-passwordcapability was hardcodingdefaults.security=deny+agents.main.security=allowlistin the approvals file. Under the new per-agent-override-wins rule, this silently capped effective policy atallowlisteven when the gateway config asked forfull.What changed
one-passwordcapability writes permissive approvals (security=fulleverywhere). The op pattern stays in the allowlist as forward-compat documentation for operators who later flip to restrictive mode.bootstrap.tspost-onboard now:openclaw exec-policy preset yolo.commands.config=true(enable the/configslash).tools.elevated.enabled=trueand auto-derivestools.elevated.allowFrom.<channel>from each channel'sallowFrom.commands.ownerAllowFromto<channel>:<id>from the same map — required separately becausecommands.configgates the slash andcommands.ownerAllowFromgates the sender.agents.defaults.sandbox.mode=off(was opt-in). Flip back on withagent.sandbox: true.agent.elevated.allowFromfield lets operators override the auto-derived elevated map without falling back to theopenclawpassthrough.telegram.allowFromwas declaredtype: "text"but the runtime accepts arrays and tests use arrays; the derivedz.string()blocked any headless config with sender IDs. NewstringListfield type fixes the validation.docs/config-reference.mddocuments the trusted-operator defaults, the newagent.elevated.allowFromfield, and the new sandbox default.The full schema map and design rationale are in
tasks/2026-05-14_0030_openclaw-security-defaults/TASK.md.Test plan
bun test(266 pass, 10 skip, 0 fail)bun run lintsamVM viavm-bootstrap.json(zai provider, Telegram channel, Tailscale serve, 1Password secrets)tools.exec.security=full,commands.config=true,tools.elevated.enabled=true,tools.elevated.allowFrom.telegramauto-derived,commands.ownerAllowFrom=["telegram:<id>"],agents.defaults.sandbox.mode=offopenclaw exec-policy showreports effectivesecurity=full, ask=offopenclaw doctorno longer flags the command-owner warningclawctl-dev createagainst the same instance is a no-op for security config)Follow-ups (separate PRs)
stringListfield type (currently renders read-only; the existing wizard never produced valid arrays either, so no regression).FailoverError: No API key found for provider "zai"during first-run bootstrap-prompt — looks like a timing issue between auth-profile patching and the prompt send. Bootstrap completes regardless.🤖 Generated with Claude Code