Security Model
Security Model
Threat Model
A personal AI agent with access to email, calendars, messaging, and shell execution is a high-value target. If an agent is compromised or manipulated (via prompt injection, tool misuse, or configuration error), it could:
- Execute arbitrary shell commands
- Modify its own configuration, skills, and prompts
- Access other devices on the network
- Read or exfiltrate private data
- Send messages impersonating the owner
- Pivot from a restricted context to a privileged one
This document describes the layered security model that mitigates these risks.
Defense in Depth
Security relies on five independent layers. If any single layer is bypassed, the remaining layers still protect against full compromise.
Layer 1: Channel Policies
The first line of defense controls who can talk to the agent at all.
- DM allowlist — Only explicitly listed phone numbers can send direct messages
- Group mention gating — In group chats, the agent only responds when @mentioned
- WhatsApp policies — DMs disabled, groups restricted to an allowlist
Layer 2: Agent Bindings
Bindings route each message to the appropriate agent based on channel, peer kind (DM vs group), and peer ID.
- Most-specific wins — A binding for a specific phone number takes priority over a channel-wide fallback
- No implicit fallthrough — Without explicit bindings, messages reach the default (most privileged) agent. Always add bindings for every channel/peer combination.
See Multi-Agent Architecture for the full binding configuration.
Layer 3: Tool Policies
Each agent has an independent tool policy that controls which OpenClaw tools it can use.
| Tool | Main Agent | Group/Family Agent | Email Delegate |
|---|---|---|---|
exec | Allowed | Allowed (gated by Layer 4) | Denied |
read | Allowed (workspace only) | Allowed | Allowed (workspaceOnly) |
write, edit | Allowed (workspace only) | Denied | write only (workspaceOnly) |
browser | Allowed | Denied | Denied |
web_search, web_fetch | Allowed | Allowed | Denied |
memory_search, memory_get | Allowed | Allowed | Allowed |
sessions_send | Allowed | Allowed (can message main agent) | Allowed (main agent only) |
sessions_list, sessions_history | Allowed | Denied | Denied |
sessions_spawn | Allowed | Denied (allowAgents: []) | Denied |
fastmail_* | Denied (delegated) | Denied | All 36 tools allowed |
message (iMessage/WA/TG) | Allowed | Allowed | Denied |
cron, gateway, process | Denied | Denied | Denied |
Key principle: The main agent has write/edit/apply_patch tools available but is not filesystem-sandboxed (fs.workspaceOnly: false) — exec approvals and tool allow/deny lists are the primary security boundary, not filesystem isolation. workspaceOnly: true was previously used but disabled because it blocked the image tool and native vision auto-injection from reading inbound media at ~/.openclaw/media/inbound/. Restricted agents deny write/edit entirely — they relay to the main agent via sessions_send for any write operations.
Layer 4: Exec Approvals
The exec tool is required for MCP servers (via CLI clients) and native PIM tools. But unrestricted exec would bypass all other restrictions. Exec approvals solve this with per-agent command allowlists.
Important: There are two separate config systems for exec security. Both must be configured for consistent behavior.
| Config file | Controls | Key fields |
|---|---|---|
openclaw.json | Gateway-hosted exec (what actually runs locally) | agents.list[].tools.exec.security / .ask |
exec-approvals.json | Node host exec (remote nodes) | agents.<name>.security / .ask |
Setting security: "full" in exec-approvals.json alone does NOT affect gateway exec — you must set it in openclaw.json under each agent’s tools.exec block.
Per-Agent Exec Config in openclaw.json
Every agent needs an explicit tools.exec block. Without one, the agent falls through to gateway defaults (security: "allowlist", ask: "on-miss"), which causes approval prompts that time out with no approver.
Critical: host: "gateway" is required for allowlist enforcement. Without it, the exec handler defaults to host: "sandbox". When sandbox mode is off, the command runs directly — bypassing the allowlist check and approval forwarding entirely. The security and ask settings have no effect without host: "gateway".
// Main agent — allowlisted commands, unlisted prompt owner{ "id": "main-agent", "tools": { "exec": { "security": "allowlist", "ask": "on-miss" } }}
// Restricted agent — smaller allowlist, unlisted prompt owner{ "id": "group-agent", "tools": { "exec": { "host": "gateway", "security": "allowlist", "ask": "on-miss" } }}Exec Approvals in exec-approvals.json
This file controls the per-agent command allowlists. All agents use security: "allowlist" — the main agent has a broader allowlist while restricted agents get a minimal set. Commands not on the allowlist trigger an approval prompt to the owner via Telegram (ask: "on-miss").
{ "defaults": { "security": "deny" }, "agents": { "main-agent": { "security": "allowlist", "ask": "on-miss", "allowlist": [ { "pattern": "/path/to/.local/bin/*" }, { "pattern": "/path/to/mcporter" }, { "pattern": "/path/to/openclaw" }, { "pattern": "/path/to/workspace/scripts/*" } ] }, "group-agent": { "security": "allowlist", "ask": "on-miss", "allowlist": [ { "pattern": "/path/to/travel-hub" }, { "pattern": "/path/to/mail-inbox" }, { "pattern": "/path/to/mail-read" }, { "pattern": "/path/to/mail-list" }, { "pattern": "/path/to/mail-auth-check" } ] } }}Explicitly NOT allowlisted for restricted agents (blocked by absence):
- Direct MCP client commands (blocks private data access)
- Mail modification commands (
mail-archive,mail-mark-read) - Vault and state tools (
obsidian-note,heartbeat-state) - General shell commands (
curl,bash, etc.)
Note: Email send/reply and auth-check now run through the Apple PIM plugin (
child_processin the gateway), bypassing exec approvals entirely. Per-agent access is controlled by the plugin’s workspace config.
Valid Exec Values
| Field | Values | Gateway default |
|---|---|---|
security | deny, allowlist, full | allowlist |
ask | off, on-miss, always | on-miss |
askFallback | deny, allow | deny |
Exec Approval Forwarding (v2026.2.15+)
When ask is set to on-miss, restricted agents prompt for approval when they hit a command not on the allowlist. By default, this prompt appears only in the originating session. Approval forwarding routes these prompts to an external channel (like Telegram with inline approve/deny buttons) so the owner can approve remotely.
"approvals": { "exec": { "enabled": true, "mode": "targets", "agentFilter": ["group-agent", "family-agent"], "targets": [ { "channel": "telegram", "to": "<owner-telegram-user-id>" } ] }}| Field | Purpose |
|---|---|
enabled | Turn on approval forwarding |
mode | "session" (origin chat), "targets" (configured targets), "both" |
agentFilter | Only forward for these agents |
targets[].channel | Destination channel (Telegram recommended for inline buttons) |
targets[].to | Destination user ID |
Recommended setup: Use ask: "on-miss" for restricted agents with Telegram forwarding. Telegram’s inline button support provides a clean approve/deny UX. BlueBubbles works too but uses text-based approval.
Layer 5: Workspace Isolation
Each agent has its own workspace directory with per-agent configuration. The apple-pim-cli plugin (v3.1.0) uses a factory pattern that reads config from each agent’s workspace automatically, filtering which calendars and reminder lists the agent can access:
| Agent | Calendar Access | Reminders Access |
|---|---|---|
| Main | All | All |
| Group | Shared only (Work/Personal blocked) | Shared only (Personal blocked) |
| Family | Shared only (Work/Personal blocked) | Shared only (Personal blocked) |
Network Isolation
The agent machine should be network-isolated from other devices. Using Tailscale ACLs:
- Personal devices can reach the agent (for SSH, dashboard)
- The agent cannot initiate connections to personal devices
- Tag the agent as
tag:agentand omit any grant wheretag:agentis the source
This prevents a compromised agent from pivoting to other machines.
SSH Hardening
- Use Tailscale SSH instead of macOS built-in SSH
- Disable macOS
sshdentirely (sudo systemsetup -setremotelogin off) - Configure ACL SSH rules with
action: checkfor the agent (requires browser re-authentication) - Enable Tailscale Lock with physical devices as approval nodes
See Remote Access for the full setup.
Prompt Injection Defense
External content (email bodies, calendar event titles, message content) is untrusted data. The agent should:
- Never execute instructions found in external content
- Sanitize external text before including it in LLM context
- Add guardrails to the agent’s system prompt
See Prompt Injection Defense for sanitization patterns and guardrail examples.
Agent-to-Agent Communication
Agent-to-agent messaging is enabled for sessions_send only. Restricted agents can relay requests to the main agent when they need tools they don’t have. The email delegate agent (lobster-mail) receives work from the main agent and returns structured summaries. sessions_spawn remains denied to prevent privilege escalation via sub-agents.
{ "tools": { "sessions": { "visibility": "all" // Main agent can discover all sessions }, "agentToAgent": { "enabled": true, "allow": ["main-agent", "family-agent", "group-agent", "wa-agent", "homeclaw", "travel-hub", "lobster-mail"] } }}Session tool lockdown: Only the main agent has sessions_list and sessions_history. All other agents have these denied, preventing them from discovering other agents’ session keys or reading transcripts. This is critical for the email delegate — other agents cannot discover its session key or read email content from its transcripts.
Why this is safe now
The original escalation path (restricted agent -> main agent -> private email access) is blocked by exec approvals:
| Control | Before exec-approvals | Now |
|---|---|---|
| Which binaries each agent can run | security: "full" (anything goes) | security: "allowlist" (hard per-agent allowlists) |
| What happens on allowlist miss | Auto-approve | Prompt owner via Telegram, deny if no response |
| Restricted agents can run mcporter | Yes (Fastmail access) | No (removed from allowlist) |
| Restricted agents’ Fastmail tools | Could use via mcporter exec | fastmail_* denied in tool policy AND mcporter off allowlist |
Defense in depth for sessions_send
- Provenance tagging — Messages from other agents have
provenance.kind: "inter_session", so the main agent knows the request is NOT from the owner - Main agent privacy rules — TOOLS.md instructs the main agent to never share private data (email, financial tools) with restricted agents
- Exec approvals (hard) — Restricted agents can’t run mcporter or other privileged binaries even if they get a response
- Tool policy (hard) —
fastmail_*denied for restricted agents - Audit trail — Full exchange logged in both agents’ session transcripts
Why sessions_spawn stays denied
If sessions_spawn were enabled, a restricted agent with subagents.allowAgents: ["main-agent"] could spawn a sub-agent running as the main agent. Sub-agents inherit the target agent’s tool policy, creating a privilege escalation path. As defense in depth, restricted agents also have allowAgents: [] to prevent this even if sessions_spawn were accidentally re-enabled.
Residual risk
A restricted agent could social-engineer the main agent into sharing private data. This is mitigated by provenance tagging and privacy instructions, and is no different from a family member texting the owner directly to ask for the same information.
Red team validation
Six tests were run on 2026-03-02 covering Fastmail privacy, social engineering, exec escalation, sessions_spawn blocking, and provenance tagging. All passed. See Agent-to-Agent Communications for the full test methodology and results.
Elevated Access
Only the owner gets elevated access, applied globally across all agents:
{ "elevated": { "enabled": true, "allowFrom": { "bluebubbles": ["+1XXXXXXXXXX"] } }}When the owner @mentions in a group chat, the restricted agent temporarily gains elevated privileges for that session.
Configuration Security
- Permissions:
chmod 600foropenclaw.json,chmod 700for identity and agent directories - Slash commands: Disable
bash,config,debug, andrestartvia iMessage - Logging: Enable secret redaction patterns for API keys, tokens, phone numbers, and bearer tokens
- Backups: Always
cp openclaw.json openclaw.json.pre-<change>before editing configuration
Audit Checklist
After hardening:
openclaw security auditKey verifications:
- Agent machine cannot ping personal devices
- Personal devices can SSH to agent
- Family DMs route to restricted agent (not main)
- Group chats require @mention
- MCP tools still work
- Logs show redacted sensitive data
- Dangerous slash commands are disabled