Security and Threat Model for AI Gateway with Shell Access
This page outlines the security model for a personal assistant gateway, covering trust boundaries and threat considerations. It is essential for operators deploying a single-user gateway with shell access.
Read this when
- Adding features that widen access or automation
- Reviewing OpenClaw security posture or hardening a deployment
Warning
Personal assistant trust model. The material below presumes a single trusted operator boundary per gateway, meaning a single-user, personal-assistant arrangement. OpenClaw does not function as a hostile multi-tenant security perimeter for multiple adversarial users sharing one agent or gateway. For mixed-trust or adversarial-user scenarios, isolate trust boundaries: use a separate gateway with its own credentials, and where feasible, distinct OS users or hosts.
Scope: personal assistant security model
- Supported: exactly one user/trust boundary per gateway, with one OS user/host/VPS per boundary preferred.
- Not supported: a shared gateway or agent used by mutually untrusted or adversarial users.
- Isolating adversarial users calls for separate gateways, ideally on separate OS users or hosts.
- When several untrusted users can message a single tool-enabled agent, they all inherit that agent's delegated tool authority.
- Anyone able to alter Gateway host state or config (
~/.openclaw, includingopenclaw.json) must be regarded as a trusted operator. - Within a single Gateway, authenticated operator access is a trusted control-plane role, not a per-user tenant role.
sessionKey(session IDs, labels) selects routing; it does not authorize anything.
Hosting multiple users or organizations? Deploy one isolated Gateway cell per tenant rather than sharing a Gateway. Consult Multi-tenant hosting.
Before touching remote access, DM policy, reverse proxy, or public exposure, use the Gateway exposure runbook as a pre-flight or rollback checklist.
openclaw security audit
Execute this after any config change or before exposing network surfaces:
openclaw security audit
openclaw security audit --deep # attempts a live Gateway probe
openclaw security audit --fix # apply safe remediations
openclaw security audit --json
--fix is deliberately limited: it turns open group policies into allowlists, tightens permissions on state/config/include files (600 files, 700 dirs), and on Windows applies ACL resets rather than POSIX chmod.
What the audit checks (high level)
- Inbound access - DM/group policies, allowlists: can strangers trigger the bot?
- Tool blast radius - elevated tools plus open rooms: could prompt injection become shell/file/network actions?
- Exec filesystem drift - mutating filesystem tools denied while
exec/processremain available with no sandbox constraints. - Exec approval drift -
security="full",autoAllowSkills, interpreter allowlists withoutstrictInlineEval.security="full"by itself is a broad posture warning, not evidence of a bug; it is the intended default for trusted personal-assistant setups, so tighten it only when your threat model demands approval or allowlist guardrails. - Network exposure - Gateway bind/auth, Tailscale Serve/Funnel, weak or short auth tokens.
- Browser control exposure - remote nodes, relay ports, remote CDP endpoints.
- Local disk hygiene - permissions, symlinks, config includes, synced-folder paths.
- Plugins - loading without an explicit allowlist.
- Policy drift - sandbox Docker settings configured but sandbox mode off;
gateway.nodes.commands.denyentries that appear effective but only match exact command IDs (for examplesystem.run), not shell text inside the payload; dangerousgateway.nodes.commands.allowentries; globaltools.profile="minimal"overridden per agent; plugin-owned tools reachable under a permissive policy. - Runtime expectation drift - assuming implicit exec still means
sandboxwhentools.exec.hostnow defaults toauto, or settingtools.exec.host="sandbox"while sandbox mode is off. - Model hygiene - warns on legacy configured models (soft warning, not a hard block).
Every finding carries a structured checkId (for instance gateway.bind_no_auth, tools.exec.security_full_configured). Prefixes: fs.* (permissions), gateway.* (bind/auth/Tailscale/Control UI/trusted-proxy), hooks.*/browser.*/sandbox.*/tools.exec.* (per-surface hardening), plugins.*/skills.* (supply chain), security.exposure.* (access policy x tool blast radius). The full catalog with severity and auto-fix support: Security audit checks. Also see Formal Verification.
Priority order when triaging findings
- Anything "open" with tools enabled: secure DMs/groups first (pairing/allowlists), then tighten tool policy and sandboxing.
- Public network exposure (LAN bind, Funnel, missing auth): address immediately.
- Browser control remote exposure: handle like operator access (tailnet-only, pair nodes deliberately, no public exposure).
- Permissions: state/config/credentials/auth must not be group/world-readable.
- Plugins: load only what you explicitly trust.
- Model choice: prefer modern, instruction-hardened models for any bot with tools.
Hardened baseline in 60 seconds
{
gateway: {
mode: "local",
bind: "loopback",
auth: { mode: "token", token: "replace-with-long-random-token" },
},
session: {
dmScope: "per-channel-peer",
},
tools: {
profile: "messaging",
deny: ["group:automation", "group:runtime", "group:fs", "sessions_spawn", "sessions_send"],
fs: { workspaceOnly: true },
exec: { security: "deny", ask: "always" },
elevated: { enabled: false },
},
channels: {
whatsapp: { dmPolicy: "pairing", groups: { "*": { requireMention: true } } },
},
}
Keeps the Gateway local-only, isolates DMs, and disables control-plane/runtime tools by default. From there, re-enable tools selectively per trusted agent.
Built-in baseline for chat-driven agent turns: non-owner senders cannot use the cron or gateway tools regardless of config.
Requester-scoped controls and prompt context
tools.toolsBySender, sender ownership, and owner-only tool inventories are checked against the originating requester of the current turn. They do not authenticate or sanitize other content in that model prompt, including quoted text, prior shared-room history, forwarded content, fetched content, attachments, tool results, or other prompt inputs. Content from another person can therefore influence an owner-triggered turn when it is included in that turn's context.
Treat these controls as defense in depth that reduces direct capability for a requester, not as hostile multi-user isolation. Use contextVisibility to filter supported channel-supplied context, restrict tools and sandbox the agent, and use separate gateways and ideally separate OS users or hosts when participants are mutually adversarial.
Trust boundary matrix
Quick model for triaging risk reports:
| Boundary or control | What it means | Common misread |
|---|---|---|
gateway.auth (token/password/trusted-proxy/device auth) | Authenticates callers to gateway APIs | "Needs per-message signatures on every frame to be secure" |
sessionKey | Routing key for context/session selection | "Session key is a user auth boundary" |
| Prompt/content guardrails | Reduce model abuse risk | "Prompt injection alone proves auth bypass" |
| Browser evaluate | Intentional operator capability when enabled | "Any JS eval primitive is automatically a vuln in this trust model" |
Local TUI ! shell | Explicit operator-triggered local execution | "Local shell convenience command is remote injection" |
| Node pairing and node commands | Operator-level remote execution on paired devices | "Remote device control should be treated as untrusted user access by default" |
gateway.nodes.pairing.autoApproveCidrs | Opt-in trusted-network node enrollment policy | "A disabled-by-default allowlist is an automatic pairing vulnerability" |
gateway.nodes.pairing.sshVerify | Key-verified node enrollment over operator SSH | "Default-on auto-approval is an automatic pairing vulnerability" |
Not vulnerabilities by design
Common findings closed as no-action
- Chains that rely solely on prompt injection, with no policy, authentication, or sandbox in place.
- Findings that presuppose hostile multi-tenant operation on a single shared host or configuration.
- Standard operator read-path access (such as
sessions.list/sessions.preview/chat.history) flagged as IDOR within a shared-gateway architecture. - Deployments restricted to localhost (for instance, a loopback-only gateway missing HSTS) reported as issues.
- Discord inbound webhook signature checks for inbound routes that this repository does not contain.
- Node pairing metadata misread as a hidden second approval gate per command for
system.run; the actual execution boundary is the gateway's global node command policy combined with the node's own exec approvals. gateway.nodes.pairing.sshVerifyflagged as a flaw simply because it ships enabled. It never grants approval based solely on network locality or SSH reachability: the gateway reads the device identity back over SSH (BatchMode, strict host keys) and approves only when the pending request matches an exact device key, meaning the connecting keypair must already reside under the operator's account on a host the operator owns. Probes stay limited to private/CGNAT source addresses, share the trusted-CIDR eligibility floor (fresh scopelessrole: nodeonly), andsshVerify: falsedisables the feature.gateway.nodes.pairing.autoApproveCidrsreported as a vulnerability on its own. It is off by default, needs explicit CIDR/IP entries, applies only to first-timerole: nodepairing with no requested scopes, and never auto-approves operator/browser/Control UI, WebChat, role/scope upgrades, metadata or public-key changes, or same-host loopback trusted-proxy header paths (even when loopback trusted-proxy auth is active).- "Missing per-user authorization" reports that mistake
sessionKeyfor an auth token.
Gateway and node trust
View the Gateway and node as a single operator trust domain with distinct roles:
- Gateway: the control plane and policy surface (
gateway.auth, tool policy, routing). - Node: the remote execution surface paired to that Gateway (commands, device actions, host-local capabilities).
- A caller authenticated to the Gateway is trusted at Gateway scope; after pairing, node actions count as trusted operator actions on that node. See Operator scopes.
- Direct loopback backend clients authenticated with the shared gateway token/password can make internal control-plane RPCs without a user device identity. This is not a remote or browser pairing bypass: network clients, node clients, device-token clients, and explicit device identities still go through pairing and scope-upgrade enforcement.
- Exec approvals (allowlist + ask) guard operator intent, not hostile multi-tenant isolation. They bind exact request context and best-effort direct local file operands; they do not semantically model every runtime/interpreter loader path. Use sandboxing and host isolation for strong boundaries.
- Trusted single-operator default: host exec on
gateway/nodeis allowed without approval prompts (security="full",ask="off"). That is deliberate UX, not a vulnerability by itself.
For hostile-user isolation, split trust boundaries by OS user/host and run separate gateways.
Threat model
Your AI assistant can run arbitrary shell commands, read/write files, reach network services, and message anyone (if given channel access). People who message it can try to trick it into harmful actions, social-engineer access to your data, or probe for infrastructure details.
Most failures here are not exotic exploits; they are "someone messaged the bot and the bot did what they asked." OpenClaw's stance, in order:
- Identity first: decide who can talk to the bot (DM pairing / allowlists / explicit "open").
- Scope next: decide where the bot can act (group allowlists + mention gating, tools, sandboxing, device permissions).
- Model last: assume the model can be manipulated; design so manipulation has limited blast radius.
DM access: pairing, allowlist, open, disabled
Every DM-capable channel supports dmPolicy (or *.dm.policy), which gates inbound DMs before the message is processed:
| Policy | Behavior |
|---|---|
pairing | Default. Unknown senders get a pairing code; bot ignores them until approved. Codes expire after 1 hour; repeated DMs do not resend a code until a new request is created. Pending requests capped at 3 per channel. |
allowlist | Unknown senders blocked, no pairing handshake. |
open | Anyone can DM (public). Requires the channel allowlist to include "*" (explicit opt-in). |
disabled | Inbound DMs ignored entirely. |
openclaw pairing list <channel>
openclaw pairing approve <channel> <code>
Details + files on disk: Pairing
Treat dmPolicy="open" and groupPolicy="open" as last-resort settings; prefer pairing + allowlists unless you fully trust every member of the room.
Allowlists (two layers)
- DM allowlist (
allowFrom/channels.discord.allowFrom/channels.slack.allowFrom; legacy:channels.discord.dm.allowFrom,channels.slack.dm.allowFrom): who can DM the bot. WhendmPolicy="pairing", approvals write to~/.openclaw/credentials/<channel>-allowFrom.json(default account) or<channel>-<accountId>-allowFrom.json(non-default accounts), merged with config allowlists. - Group allowlist (channel-specific): which groups/channels/guilds the bot accepts at all.
channels.whatsapp.groups,channels.telegram.groups,channels.imessage.groups: per-group defaults likerequireMention; when set, also acts as a group allowlist (include"*"to keep allow-all behavior). Customize mention triggers withagents.entries.*.groupChat.mentionPatterns(for example["@openclaw", "@mybot"]) sorequireMentiongates on your own bot names.groupPolicy="allowlist"+groupAllowFrom: restrict who can trigger the bot inside a group session (WhatsApp/Telegram/Signal/iMessage/Microsoft Teams).channels.discord.guilds/channels.slack.channels: per-surface allowlists + mention defaults.- Check order:
groupPolicy/group allowlists first, then mention/reply activation. Replying to a bot message (implicit mention) does not bypassgroupAllowFrom.
Details: Configuration and Groups
DM session isolation (multi-user mode)
By default, OpenClaw routes all DMs into the main session for cross-device continuity. If multiple people can DM the bot (open DMs or a multi-person allowlist), isolate DM sessions:
{ session: { dmScope: "per-channel-peer" } }
session.dmScope values:
| Value | Scope |
|---|---|
main (config default) | A single session is shared by every DM. |
per-channel-peer | Each channel and sender pairing receives its own isolated DM context (secure DM mode). |
per-account-channel-peer | Same as the row above, but divided further per account (multi-account channels). |
per-peer | One session is assigned to each sender across all channels of a given type. |
During local CLI onboarding, an explicit session.dmScope is preserved while everything else stays unset, which means the "main" default takes effect: the agent's rolling main session is shared by all direct messages across channels (the personal-agent default). For inboxes that are shared or multi-user, configure session.dmScope: "per-channel-peer"; when multi-user DM traffic is detected, openclaw security audit suggests enabling isolation.
This boundary governs messaging context, not host administration. If users are mutually adversarial yet share the same Gateway host or config, deploy separate gateways for each trust boundary instead.
When the same person reaches you through multiple channels, apply session.identityLinks to merge those DM sessions into a single canonical identity. Refer to Session Management and Configuration.
Context visibility vs trigger authorization
Two distinct ideas are involved:
- Trigger authorization: determines who can activate the agent (
dmPolicy,groupPolicy, allowlists, mention gates). - Context visibility: decides what supplemental context reaches the model (reply body, quoted text, thread history, forwarded metadata).
The second is governed by contextVisibility:
"all"(default): supplemental context remains exactly as received."allowlist": supplemental context is limited to senders permitted by active allowlist checks."allowlist_quote": behaves likeallowlist, but a single explicit quoted reply is still retained.
Apply this per channel or per room/conversation, as described in Groups. Findings that merely indicate "model can see quoted or historical text from senders not on the allowlist" are hardening issues that contextVisibility can address; they are not auth or sandbox bypasses by themselves. A report with security impact still needs to demonstrate an actual trust-boundary bypass.
Prompt injection
An attacker crafts a message that steers the model into unsafe behavior ("ignore your instructions", "dump your filesystem", "follow this link and run commands").
Model selection now carries significant weight. Frontier models have become notably more resilient: in a 2026 crowdsourced arena with 272K attacks across 41 agent scenarios, scored only when the agent both performed the harmful action and concealed it from the user, success rates were 0.5% for Claude Opus 4.5, 1.0% for Sonnet 4.5, 1.3% for Haiku 4.5, and 8.5% for Gemini 2.5 Pro. Within a model family, robustness tracked with capability, so the models we recommend serve as a meaningful mitigation on their own, not merely a soft guardrail.
Two caveats prevent this from being fully solved. Adaptive human attackers still break models that perform well on static benchmarks, with published success rates above 80% against state-of-the-art defenses once the attacker adapts. Smaller or older models also remain considerably easier to manipulate. Treat model choice as your first and least expensive layer, then rely on hard enforcement, such as tool policy, exec approvals, sandboxing, and channel allowlists, for anything whose blast radius you would not accept on a bad day.
Prompt injection does not require public DMs: even if only you can message the bot, any untrusted content it reads (web search or fetch results, browser pages, emails, docs, attachments, pasted logs or code) can carry adversarial instructions. The content itself is a threat surface, not just the sender.
Red flags to treat as untrusted:
- "Read this file/URL and do exactly what it says."
- "Ignore your system prompt or safety rules."
- "Reveal your hidden instructions or tool outputs."
- "Paste the full contents of ~/.openclaw or your logs."
What helps in practice:
- Keep inbound DMs locked down (pairing/allowlists). Groups are a supported deployment, not a last resort: use mention gating and
contextVisibilityso the agent reads what it needs and no more. Reserve extra caution for genuinely public rooms, where anyone can post untrusted content. - Treat links, attachments, and pasted instructions as hostile by default.
- Run sensitive tool execution in a sandbox; keep secrets out of the agent's reachable filesystem. Sandboxing is opt-in: if sandbox mode is off, implicit
host=autoresolves to the gateway host, while explicithost=sandboxstill fails closed (no sandbox runtime available). Sethost=gatewayto make that behavior explicit in config. - Limit high-risk tools (
exec,browser,web_fetch,web_search) to trusted agents or explicit allowlists. - If you allowlist interpreters (
python,node,ruby,perl,php,lua,osascript), enabletools.exec.strictInlineEvalso inline eval forms (-c,-e, and similar) still need explicit approval. In allowlist mode, any heredoc segment (<<) always requires reviewer or explicit approval, regardless of quoting, so an allowlisted command cannot use a heredoc body to bypass allowlist review. - Reduce blast radius by using a read-only or tool-disabled reader agent to summarize untrusted content, then pass the summary to your main agent.
- For Gmail hooks, the built-in per-message session isolates conversation context but does not remove the target agent's tool or workspace permissions. Route untrusted mail to a dedicated reader agent, apply per-agent sandbox and tool restrictions, and constrain any handoff to the main agent with
tools.agentToAgent. See Gmail integration. - Keep
web_search/web_fetch/browseroff for tool-enabled agents unless needed. - For OpenResponses URL inputs (
input_file/input_image), set a tightgateway.http.endpoints.responses.files.urlAllowlist/images.urlAllowlistand keepmaxUrlPartslow (empty allowlists count as unset). Usefiles.allowUrl: false/images.allowUrl: falseto disable URL fetching entirely. - Keep secrets out of prompts; pass them via env/config on the gateway host instead.
Model choice matters. Prompt-injection resistance is not uniform across model tiers, as smaller or cheaper models are more susceptible to tool misuse and instruction hijacking under adversarial prompts.
Warning
For tool-enabled agents or agents that read untrusted content, prompt-injection risk with older or smaller models is often too high. Do not run those workloads on weak model tiers.
- Use the latest-generation, best-tier model for any bot that can run tools or touch files/networks.
- Do not use older/weaker/smaller tiers for tool-enabled agents or untrusted inboxes.
- If you must use a smaller model, reduce blast radius: read-only tools, strong sandboxing, minimal filesystem access, strict allowlists. Enable sandboxing for all sessions and disable
web_search/web_fetch/browserunless inputs are tightly controlled. - For chat-only personal assistants with trusted input and no tools, smaller models are usually fine.
External content and untrusted-input wrapping
OpenResponses input_file text remains untrusted external input even though the Gateway performs local decoding, since the block includes <<<EXTERNAL_UNTRUSTED_CONTENT ...>>> boundary markers and Source: External metadata, a path that skips the longer SECURITY NOTICE: banner applied elsewhere. When media-understanding pulls text out of attached documents and adds it to the media prompt, the same marker-based wrapping is used.
OpenClaw additionally removes common special-token literals from self-hosted LLM chat templates (Qwen/ChatML, Llama, Gemma, Mistral, Phi, GPT-OSS role/turn tokens) from wrapped external content and metadata before the model sees them. Self-hosted OpenAI-compatible backends (vLLM, SGLang, TGI, LM Studio, custom Hugging Face tokenizer stacks) may tokenize literal strings like <|im_start|> or <|start_header_id|> as structural chat-template tokens inside user content; without this cleanup, untrusted text in a fetched page, email body, or file-contents tool output could fabricate a synthetic assistant/system role boundary. Sanitization occurs at the external-content wrapping layer, so it applies uniformly to fetch/read tools and inbound channel content. Hosted providers (OpenAI, Anthropic) already sanitize on their side; keep external-content wrapping enabled and, when available, prefer backend settings that split or escape special tokens.
A separate sanitizer for outbound model responses strips leaked <tool_call>, <function_calls>, <system-reminder>, <previous_response>, and similar internal scaffolding from user-visible replies at the final channel delivery boundary.
This is not a replacement for dmPolicy, allowlists, exec approvals, sandboxing, or contextVisibility, it closes one specific tokenizer-layer bypass.
Bypass flags (keep off in production)
hooks.mappings[].allowUnsafeExternalContenthooks.gmail.allowUnsafeExternalContent- Cron payload field
allowUnsafeExternalContent
Enable only temporarily for tightly scoped debugging; if enabled, isolate that agent (sandbox + minimal tools + dedicated session namespace).
Hook payloads count as untrusted content even when delivery comes from systems you control (mail/docs/web content can carry prompt injection). Weak model tiers raise this risk, for hook-driven automation, prefer strong modern model tiers and keep tool policy tight (tools.profile: "messaging" or stricter), plus sandboxing where possible.
Reasoning and verbose output in groups
/reasoning, /verbose, and /trace can reveal internal reasoning, tool output, or plugin diagnostics not meant for a public channel, they can include tool args, URLs, plugin diagnostics, and data the model saw. Keep them disabled in public rooms; enable only in trusted DMs or tightly controlled rooms.
Command authorization
Slash commands and directives are honored only for authorized senders. Configure an explicit per-provider commands.allowFrom list, or let command authorization follow channel allowlists and pairing state. Access-group entries referenced by channel allowlists are resolved automatically; there is no opt-in toggle. If a channel allowlist is empty or includes "*", commands are effectively open for that channel. See Access groups and Slash commands.
/exec is a session-only convenience for authorized operators, it does not write config or change other sessions.
Control plane tools
Two built-in tools remain control-plane sensitive:
gatewayreads config withconfig.schema.lookup/config.get. It cannot write config, update OpenClaw, or restart the Gateway.croncreates scheduled jobs that keep running after the original chat/task ends.
The gateway tool stays owner-only because config reads can expose secrets and host topology. Agents request persistent config or lifecycle changes through the openclaw delegation tool; OpenClaw maps them to typed operations and requires human approval before applying them. See OpenClaw setup agent.
For any agent/surface handling untrusted content, deny these by default:
{
tools: {
deny: ["gateway", "cron", "sessions_spawn", "sessions_send"],
},
}
commands.restart=false disables /restart and external SIGUSR1 restart requests. The gateway agent tool has no restart action.
Node execution (system.run)
If a macOS node is paired, the Gateway can invoke system.run on it, this is remote code execution on that Mac.
- Requires node pairing (approval + token). Pairing establishes node identity/trust and token issuance; it is not a per-command approval surface.
- The Gateway applies a coarse global node command policy via
gateway.nodes.commands.allow/gateway.nodes.commands.deny. The deny list matches exact node command names only (for examplesystem.run), not shell text inside a command payload, a reconnecting node advertising a different command list is not, by itself, a vulnerability if the gateway global policy and the node's own exec approvals still enforce the boundary. - The per-node
system.runpolicy is the node's own exec approvals file (exec.approvals.node.*), controlled on the Mac via Settings -> Exec approvals (security + ask + allowlist); it can be stricter or looser than the gateway's global command-ID policy. - A node running
security="full"andask="off"follows the default trusted-operator model, expected behavior, not a bug, unless your deployment needs a tighter stance. - Approval mode binds exact request context and, when possible, one concrete local script/file operand. If OpenClaw cannot identify exactly one direct local file for an interpreter/runtime command, approval-backed execution is denied rather than promising full semantic coverage.
- For
host=node, approval-backed runs also store a canonical preparedsystemRunPlan; later approved forwards reuse that stored plan, and gateway validation rejects caller edits to command/cwd/session context after the approval request was created. - To disable remote execution entirely: set security to
denyand remove node pairing for that Mac.
Dynamic skills (watcher / remote nodes)
OpenClaw can refresh the skills list mid-session: the skills watcher updates the snapshot on the next agent turn when SKILL.md changes, and connecting a macOS node can make macOS-only skills eligible (based on bin probing). Treat skill folders as trusted code and restrict who can modify them.
Plugins
Plugins run in-process with the Gateway, treat them as trusted code.
- Always pull plugins from sources you trust; lean on explicit
plugins.allowallowlists whenever possible, inspect plugin configuration before turning it on, and reboot the Gateway after any plugin change. - Plugin installation and updates execute code:
- The install location is the plugin-specific folder within the active plugin install root.
- ClawHub packages and OpenClaw's bundled or official catalog count as trusted sources. Any new npm,
npm-pack:, git, local path or archive, or marketplace source triggers a warning before installation; noninteractive installs demand--forceafter you have vetted and trusted that source.--forceverifies provenance and allows overwrites, but it does not skipsecurity.installPolicyor other install safety checks. Updates keep using the source you already picked. - OpenClaw does not apply built-in local dangerous-code blocking during install or update. For operator-owned local allow, warn, or block decisions, use
security.installPolicy; for diagnostic scanning, useopenclaw security audit --deep. - npm and git plugin installs only run package-manager dependency convergence during the explicit install or update flow. Local paths and archives are handled as self-contained packages, so OpenClaw copies or references them without executing
npm install. - Favor pinned exact versions (
@scope/pkg@1.2.3) and review the unpacked code before enabling. security.installPolicyallows operators to run a trusted local command that returnsallow,warn, orblockfor skill and plugin installs. This runs after source material is staged but before installation proceeds, and it also applies to ClawHub skills.- A
warnresult halts before commit. Interactive CLI commands ask the operator to type the plugin or skill name using the same wording as suspicious ClawHub releases, then re-evaluate policy before continuing. A rendered review longer than 4,000 characters fails closed before prompting. Declined and non-interactive direct CLI commands can use--acknowledge-install-policy-warningas explicit approval after review for every warning in that command invocation. The Control UI exposes the same invocation-wide approval through Install anyway for plugin installs. Other Gateway-backed and automatic installs stay blocked when they lack an operator-confirmation flow. Every approved warning is re-evaluated before continuing.blockand policy failures remain terminal. Neither--forcenor the deprecated plugin install/update flag--dangerously-force-unsafe-installapproves policy warnings.
Details: Plugins
Sandboxing
Dedicated doc: Sandboxing
Two complementary approaches:
- Full Gateway in Docker (container boundary): Docker
- Tool sandbox (
agents.defaults.sandbox; host gateway + sandbox-isolated tools; built-in Docker and Podman backends): Sandboxing
Note
To prevent cross-agent access, keep
agents.defaults.sandbox.scopeat"agent"(default) or use"session"for stricter per-session isolation.scope: "shared"uses a single container or workspace.
Agent workspace access inside the sandbox (agents.defaults.sandbox.workspaceAccess):
"none"(default): tools see a sandbox workspace under~/.openclaw/sandboxes; agent workspace is off-limits."ro": mounts the agent workspace read-only at/agent(disableswrite/edit/apply_patch)."rw": mounts the agent workspace read/write at/workspace.
Extra sandbox.docker.binds are validated against normalized, canonicalized source paths. A blocked-path denylist covers /etc, /private/etc, /proc, /sys, /dev, /root, /boot, and directories that commonly contain or alias the Docker socket (/run, /var/run, and docker.sock under them), plus HOME credential subpaths (.aws, .cargo, .config, .docker, .gnupg, .netrc, .npm, .ssh). Parent-symlink tricks and canonical home aliases are resolved through existing ancestors and re-checked, so they still fail closed if they resolve into a blocked root.
Warning
tools.elevatedserves as the global baseline escape hatch, executing exec outside the sandbox. By default, the effective host isgateway, ornodewhen the exec target is set tonode. Keeptools.elevated.allowFromrestrictive and avoid enabling it for untrusted users. Apply further per-agent restrictions throughagents.entries.*.tools.elevated. Refer to Elevated mode for details.
Sub-agent delegation guardrail
When session tools are enabled, treat delegated sub-agent runs as an additional boundary decision:
- Refuse
sessions_spawnunless the agent genuinely requires delegation. - Restrict
agents.defaults.subagents.allowAgentsand any per-agentagents.entries.*.subagents.allowAgentsoverrides to known-safe target agents. - For workflows that must remain sandboxed, invoke
sessions_spawnwithsandbox: "require"(defaulting to"inherit");"require"fails quickly when the target child runtime lacks sandboxing.
Read-only mode
Create a read-only profile by merging agents.defaults.sandbox.workspaceAccess: "ro" (or "none" for no workspace access) with tool allow/deny lists that block write, edit, apply_patch, exec, process, and similar.
tools.exec.applyPatch.workspaceOnly: true(default): preventsapply_patchfrom writing or deleting outside the workspace directory even with sandboxing disabled. Setfalseonly if you deliberately wantapply_patchto modify files outside the workspace.tools.fs.workspaceOnly: true(optional): limitsread/write/edit/apply_patchpaths and native prompt image auto-load paths to the workspace directory.- Keep filesystem roots narrow, avoiding broad roots like your home directory for agent/sandbox workspaces, which can expose sensitive local files (such as state/config under
~/.openclaw) to filesystem tools.
Per-agent access profiles (multi-agent)
Each agent can have its own sandbox and tool policy: full access, read-only, or no access. See Multi-Agent Sandbox & Tools for precedence rules.
Common patterns: personal agent (full access, no sandbox), family/work agent (sandboxed with read-only tools), public agent (sandboxed with no filesystem/shell tools).
Full access (no sandbox)
{
agents: {
entries: {
personal: {
default: true,
workspace: "~/.openclaw/workspace-personal",
sandbox: { mode: "off" },
},
},
},
}
Read-only tools + read-only workspace
{
agents: {
entries: {
family: {
default: true,
workspace: "~/.openclaw/workspace-family",
sandbox: { mode: "all", scope: "agent", workspaceAccess: "ro" },
tools: {
allow: ["read"],
deny: ["write", "edit", "apply_patch", "exec", "process", "browser"],
},
},
},
},
}
No filesystem/shell access (provider messaging allowed)
{
// Session tools can reveal transcript data. Default scope is current + spawned;
// reads also include same-agent groups watched through ambient group awareness.
// Use visibility: "self" to exclude those watched sessions.
tools: { sessions: { visibility: "tree" } }, // self | tree | agent | all
agents: {
entries: {
public: {
default: true,
workspace: "~/.openclaw/workspace-public",
sandbox: { mode: "all", scope: "agent", workspaceAccess: "none" },
tools: {
allow: [
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status",
"discord",
"slack",
"telegram",
"whatsapp",
],
deny: [
"apply_patch",
"browser",
"canvas",
"cron",
"edit",
"exec",
"gateway",
"image",
"nodes",
"process",
"read",
"write",
],
},
},
},
},
}
Browser control risks
Enabling browser control grants the model a real browser. If that profile already has logged-in sessions, the model can access those accounts and data, so treat browser profiles as sensitive state.
- Use a dedicated profile for the agent (the default
openclawprofile); avoid your personal daily-driver profile. - Keep host browser control disabled for sandboxed agents unless you trust them.
- The standalone loopback browser control API only honors shared-secret auth (gateway token bearer auth or gateway password), not trusted-proxy or Tailscale Serve identity headers.
- Treat browser downloads as untrusted input; prefer an isolated downloads directory.
- Disable browser sync and password managers in the agent profile if possible.
- For remote gateways, "browser control" equals "operator access" to whatever that profile can reach.
- Keep Gateway and node hosts tailnet-only; avoid exposing browser control ports to LAN or public internet.
- Disable browser proxy routing when not needed (
gateway.nodes.browser.mode="off"). - Chrome MCP existing-session mode is not "safer": it can act as you in whatever that host Chrome profile can reach.
- Browser Relay Authentication v2 never sends the persistent extension relay key. The extension and external CDP clients verify a signed server challenge before returning a short-lived, one-time, connection-bound HMAC proof. Proofs bind the protocol version, role, transport, method, resource, flow, profile, and relay instance; replay on the same or another socket fails.
browser.extensionRelay.allowLegacyAuthdefaults totruefor one migration window. This temporarily accepts old Bearer, Basic, and token-subprotocol relay clients. Update every relay client, then set it tofalse. V2 clients never downgrade after a failed proof or unsupported response.- Chrome extension pairing stores its access mode in extension-owned Chrome storage, not Gateway config. All tabs exposes every eligible ordinary tab in that Chrome profile except session-paused tabs; Selected tabs uses the OpenClaw tab group as its ACL. Existing pairings migrate to Selected tabs, while new personal-browser pairings recommend All tabs. Incognito and internal Chrome pages remain excluded in either mode.
- Automatic Chrome extension setup uses an origin-locked native messaging manifest discovered from an exact unpacked extension path in Chrome profile metadata. The one-shot host accepts only a versioned request with a fresh nonce, caps input at 4 KiB, validates the Chrome-supplied origin, and returns only a locally owned pairing. It never transfers a remote Gateway key.
- Native-host manifests, launchers, and status output contain no pairing key. OpenClaw refuses symlinks, unsafe ownership/modes, wildcard origins, and foreign registrations using the same host name. Windows uses the manual pairing fallback until an executable native-host path is supported.
- Run a node host on the browser machine and let the Gateway proxy browser actions when the Gateway is remote from the browser (see Browser tool); treat node pairing like admin access, keep Gateway and node host on the same tailnet, and avoid exposing relay/control ports over LAN, public internet, or Tailscale Funnel.
Browser SSRF policy (strict by default)
Private/internal destinations stay blocked unless you explicitly opt in.
- Default:
browser.ssrfPolicy.dangerouslyAllowPrivateNetworkunset, so private/internal/special-use destinations stay blocked. Legacy aliasallowPrivateNetworkstill accepted. - Opt-in: set
dangerouslyAllowPrivateNetwork: trueto allow those destinations. - In strict mode, use wildcard-aware
allowedHostnamesentries for patterns like*.example.comand exact host exceptions, including otherwise-blocked names likelocalhost. - Direct navigation requests are preflight checked. During the action and bounded post-action grace, guarded Playwright interactions (click, coordinate click, hover, drag, scroll, select, press, type, form fill, and evaluate) intercept policy-denied top-level and subframe document loads before HTTP request bytes, then best-effort re-check the final
http(s)URL. - Before each fresh managed Chrome launch, OpenClaw best-effort disables network prediction, suppressing Chromium's observed speculative preconnect for those denied loads. This is defense in depth, not a policy boundary: a browser reused across a control-service restart and other browser backends may not share the hardening. Page routing remains request-level interception, not a network firewall: redirect hops, a popup's first request, Service Worker traffic, page code that runs after the bounded guard window, and some background/subresource paths can bypass it. Final-URL checks remain detection/quarantine defense; complete prevention requires owner-side egress isolation or a policy-enforcing proxy.
{
browser: {
ssrfPolicy: {
dangerouslyAllowPrivateNetwork: false,
allowedHostnames: ["*.example.com", "example.com", "localhost"],
},
},
}
Network exposure
Bind, port, firewall
The Gateway serves both WebSocket and HTTP traffic through a single port, which defaults to 18789 and can be adjusted via config, flags, or environment variables: gateway.port, --port, OPENCLAW_GATEWAY_PORT. This HTTP layer hosts the Control UI (SPA assets located at the default base path /), widget documents for embedding (/__openclaw__/canvas), and A2UI renderer assets (/__openclaw__/a2ui). Since widget documents carry agent-written HTML and JavaScript, they should be regarded as untrusted when opened in a standard browser, kept away from untrusted networks and users, and never given the same origin as privileged web surfaces.
The listening address is determined by gateway.bind:
- With
"loopback"(the default), connections are restricted to local clients. - Using
"lan","tailnet", or"custom"broadens the exposure. These options should be paired with gateway authentication (a shared token or password, or a properly set up trusted proxy) plus a genuine firewall.
General guidance: Tailscale Serve is preferable to binding directly to the LAN, since Serve keeps the Gateway on loopback and delegates access control to Tailscale; if a LAN bind is unavoidable, restrict the port via a strict source-IP allowlist in the firewall rather than opening it broadly with port forwarding; and under no circumstances should the Gateway be left unauthenticated on 0.0.0.0.
Docker port publishing with UFW
Exposed container ports (-p HOST:CONTAINER, or ports: in Compose) traverse Docker's forwarding chains rather than only the host's INPUT rules. Apply your restrictions in DOCKER-USER, which is evaluated before Docker's own accept rules; on most current distributions the iptables-nft frontend handles this, and those rules still reach the nftables backend.
# /etc/ufw/after.rules (append as its own *filter section)
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
-A DOCKER-USER -s 127.0.0.0/8 -j RETURN
-A DOCKER-USER -s 10.0.0.0/8 -j RETURN
-A DOCKER-USER -s 172.16.0.0/12 -j RETURN
-A DOCKER-USER -s 192.168.0.0/16 -j RETURN
-A DOCKER-USER -s 100.64.0.0/10 -j RETURN
-A DOCKER-USER -p tcp --dport 80 -j RETURN
-A DOCKER-USER -p tcp --dport 443 -j RETURN
-A DOCKER-USER -m conntrack --ctstate NEW -j DROP
-A DOCKER-USER -j RETURN
COMMIT
IPv6 maintains its own tables, so if Docker IPv6 is active, add a corresponding policy under /etc/ufw/after6.rules. Avoid hardcoding interface names like eth0, because they differ between VPS images (ens3, enp*, and others), and a wrong name can silently cause your deny rule to be skipped.
ufw reload
iptables -S DOCKER-USER
ip6tables -S DOCKER-USER
nmap -sT -p 1-65535 <public-ip> --open
Only the ports you deliberately expose should be reachable from outside, which for typical setups means SSH plus reverse proxy ports.
mDNS/Bonjour discovery
When the bundled bonjour plugin is active, the Gateway advertises presence over mDNS (_openclaw-gw._tcp, port 5353) to support local device discovery. Full mode adds TXT records that reveal operational details: cliPath (a filesystem path that discloses the username and install location), sshPort (signals that SSH is available), and displayName/lanHost (hostname information). Broadcasting such infrastructure details makes LAN reconnaissance simpler.
-
Leave Bonjour off unless LAN discovery is actually required; it starts automatically on macOS hosts and is opt-in elsewhere. Direct Gateway URLs, Tailnet, SSH, or wide-area DNS-SD avoid local multicast altogether.
-
Minimal mode (the default when Bonjour is enabled, and recommended for exposed gateways) leaves out sensitive fields:
{ discovery: { mdns: { mode: "minimal" } } } -
Off disables local discovery while leaving the plugin enabled:
{ discovery: { mdns: { mode: "off" } } } -
Full mode (opt-in) adds
cliPathandsshPort:{ discovery: { mdns: { mode: "full" } } } -
Alternatively, set
OPENCLAW_DISABLE_BONJOUR=1to turn off mDNS without touching configuration.
In minimal mode the Gateway broadcasts role, gatewayPort, and transport while omitting cliPath and sshPort; clients that need the CLI path can retrieve it through the authenticated WebSocket connection instead.
Gateway WebSocket auth
Authentication is mandatory by default: if no valid auth path is configured, the Gateway rejects WebSocket connections, failing closed. Onboarding creates a token by default, even for loopback, so local clients must authenticate.
{ gateway: { auth: { mode: "token", token: "your-token" } } }
You can have openclaw doctor --generate-gateway-token generate one for you.
Note
gateway.remote.tokenandgateway.remote.passwordact as client credential sources; by themselves they do not secure local WS access. Local call paths rely ongateway.remote.*only as a fallback whengateway.auth.*is not set. Ifgateway.auth.tokenorgateway.auth.passwordis explicitly configured through SecretRef and cannot be resolved, resolution fails closed, with no masking via remote fallback.
When using wss://, pin the remote TLS certificate with gateway.remote.tlsFingerprint. Plaintext ws:// is permitted for loopback, private IP literals, .local, and Tailnet *.ts.net gateway URLs; for other trusted private-DNS names, set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 on the client process as a break-glass measure, which applies only to the process environment and not to an openclaw.json key. Mobile pairing and Android manual or scanned gateway routes enforce stricter rules: cleartext is allowed only for loopback, while private-LAN, link-local, .local, and dotless hostnames require TLS unless you explicitly choose the trusted private-network cleartext path.
Device pairing happens automatically for direct local loopback connections, and also for a limited backend or container-local self-connect path used by trusted shared-secret helper flows. Tailnet and LAN connections, including same-host connections to a tailnet address, count as remote and still require approval. A resolved tailnet address or custom address that is neither 127.0.0.1 nor 0.0.0.0 adds an extra 127.0.0.1 listener; only connections reaching that local listener get loopback semantics. If forwarded-header evidence appears on a loopback request, loopback locality is revoked; metadata-upgrade auto-approval stays narrowly scoped. Refer to Gateway pairing.
Auth modes:
"token": shared bearer token (recommended for most setups)."password": prefer setting viaOPENCLAW_GATEWAY_PASSWORD."trusted-proxy": trust an identity-aware reverse proxy to authenticate users and pass identity via headers. See Trusted Proxy Auth.
Rotation checklist (token/password): generate/set a new secret (gateway.auth.token or OPENCLAW_GATEWAY_PASSWORD); restart the Gateway (or the macOS app if it supervises the Gateway); update remote clients (gateway.remote.token/.password); verify the old credentials no longer work.
Tailscale Serve identity headers
When gateway.auth.allowTailscale is true (default for Serve), OpenClaw accepts the Tailscale Serve identity header tailscale-user-login for Control UI/WebSocket authentication. It verifies identity by resolving the x-forwarded-for address through the local Tailscale daemon (tailscale whois) and matching it to the header. This only triggers on OpenClaw's dedicated managed-Tailscale listener and requires x-forwarded-for, x-forwarded-proto, and x-forwarded-host; headers on the ordinary Gateway listener do not establish Serve provenance or tokenless auth. For this async check, failed attempts for the same {scope, ip} are serialized before the limiter records the failure, so concurrent bad retries from one Serve client can lock out the second attempt immediately.
HTTP API endpoints (/v1/*, /tools/invoke, /api/channels/*) do not use Tailscale identity-header auth - they follow the gateway's configured HTTP auth mode.
Gateway HTTP bearer auth is effectively all-or-nothing operator access. Credentials that can call /v1/chat/completions, /v1/responses, plugin routes such as /api/v1/admin/rpc, or /api/channels/* are full-access operator secrets for that gateway: shared-secret bearer auth restores the full default operator scopes (operator.admin, operator.approvals, operator.pairing, operator.read, operator.talk.secrets, operator.write) and owner semantics for agent turns, and narrower x-openclaw-scopes values do not reduce that shared-secret path. Per-request scope semantics only apply when the request comes from an identity-bearing mode (trusted proxy auth) or an explicitly no-auth private ingress; in those modes, omitting x-openclaw-scopes falls back to the normal operator default scope set, and owner-level headers like x-openclaw-model require operator.admin when scopes are narrowed. /tools/invoke and HTTP session history endpoints follow the same shared-secret rule. Do not share these credentials with untrusted callers; prefer separate gateways per trust boundary.
Tokenless Serve auth assumes the gateway host itself is trusted - it is not protection against hostile same-host processes. If untrusted local code may run on the gateway host, disable allowTailscale and require explicit shared-secret auth (token or password).
An externally managed Tailscale Serve or Funnel route may forward these headers to the ordinary listener only through an explicitly configured gateway.trustedProxies source with a valid non-loopback forwarded client address. OpenClaw treats that request as generic proxy ingress: the configured gateway auth applies, allowTailscale grants nothing, and no WhoIs lookup runs. Gateway-protected routes reject external Funnel ingress when auth mode is none; aggregate health, readiness, and startup probes keep their bounded unauthenticated responses. See Tailscale, Health and readiness, and Trusted Proxy Auth.
See Tailscale and Web overview.
Reverse proxy configuration
Set gateway.trustedProxies for proper forwarded-client IP handling behind nginx/Caddy/Traefik/etc. When the Gateway detects proxy headers from an address not in trustedProxies, it will not treat the connection as local; if gateway auth is disabled, that connection is rejected. This prevents proxied connections from appearing to come from localhost and receiving automatic trust.
With token or password authentication, a loopback proxy on the same host that has not been configured is refused on Gateway-authenticated routes, since OpenClaw cannot trace forwarded client headers back to their origin. HTTP requests get 403 along with proxy_attribution_required; WebSocket authentication fails and advises configuring gateway.trustedProxies. Webhook routes that rely on plugin authentication keep their own signature or credential validation and disregard unverified forwarded claims. Set trustedProxies to a narrow scope and have the proxy overwrite or securely reconstruct forwarded headers; consult Rate limiting.
trustedProxies also drives gateway.auth.mode: "trusted-proxy", which applies stricter rules: by default it fails closed on proxies originating from loopback. Reverse proxies on the same host can employ trustedProxies for local-client identification and forwarded-IP handling, but they can only meet trusted-proxy authentication mode when gateway.auth.trustedProxy.allowLoopback = true; otherwise fall back to token/password authentication.
gateway:
trustedProxies:
- "10.0.0.1" # reverse proxy IP
allowRealIpFallback: false # default false; only enable if your proxy cannot provide X-Forwarded-For
auth:
mode: password
password: ${OPENCLAW_GATEWAY_PASSWORD}
When trustedProxies is enabled, the Gateway relies on X-Forwarded-For to resolve the client IP; X-Real-IP is disregarded unless gateway.allowRealIpFallback: true is set explicitly. Make sure your proxy overwrites X-Forwarded-For/X-Real-IP instead of adding to them:
# good
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
# bad: preserves/appends untrusted client-supplied values
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
Trusted proxy headers do not automatically grant trust to node device pairing: gateway.nodes.pairing.autoApproveCidrs is a separate operator policy that is off by default, and loopback-source trusted-proxy header paths remain excluded from node auto-approval even when loopback trusted-proxy authentication is active, because local callers can fabricate those headers.
HSTS and origin notes
- OpenClaw's gateway prioritizes local and loopback connections. If TLS termination happens at a reverse proxy, configure HSTS at that layer.
- When the gateway handles HTTPS termination itself,
gateway.http.securityHeaders.strictTransportSecuritysends the HSTS header from OpenClaw responses. - Control UI deployments outside loopback require
gateway.controlUi.allowedOriginsby default;allowedOrigins: ["*"]is an explicit allow-all policy rather than a secure default, so avoid it except in tightly controlled local testing. - Loopback authentication failures never trigger lockout, meaning a local CLI cannot be blocked before its credentials are checked. Incorrect credentials are still logged and subject to progressive delays (bounded delay, one shared timer per key); a successful authentication resets only the history for the matching credential class. This makes repeated guessing from a single loopback source more costly, but it does not protect against an attacker who can open many parallel loopback connections, since credentials are compared before the failure response is delayed. Loopback reachability itself constitutes a trust boundary, see Node pairing.
- Browser-origin authentication failures on loopback remain rate-limited even when the general loopback exemption is active, but the lockout key is scoped per normalized
Originvalue rather than a single shared localhost bucket. gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=trueturns on Host-header origin fallback mode; treat this as a dangerous operator-chosen policy.- View DNS rebinding and proxy-host header behavior as deployment hardening concerns; keep
trustedProxiestight and avoid direct public internet exposure of the gateway. - Full deployment guidance: Trusted Proxy Auth.
Control UI over HTTP
The Control UI creates device identity with pure-JS Ed25519, so pairing functions on any origin, including plain HTTP.
- Token/password authentication does not replace browser device identity: HTTP browsers still pair with a signed device key that never travels over the wire. Prefer HTTPS (for example, Tailscale Serve), since plaintext transport still exposes the page and the shared secret to on-path attackers.
gateway.controlUi.dangerouslyDisableDeviceAuth: retired break-glass input, now completely inert. Control UI browsers pair through the standard device flow;openclaw doctor --fixremoves the legacy key.- Separately, successful
gateway.auth.mode: "trusted-proxy"authentication can admit operator Control UI sessions without device identity when the browser cannot provide one. Browsers capable of minting an identity (any origin, including plain HTTP) follow the normal pairing flow instead, automatic withdeviceAutoApprove, otherwise a one-time approval. This does not apply to node-role Control UI sessions.
Insecure/dangerous flags
openclaw security audit raises config.insecure_or_dangerous_flags for each enabled known insecure or dangerous debug switch, one finding per flag. Leave these unset in production. If audit suppressions are configured, security.audit.suppressions.active remains in the active output even when matching findings are moved to suppressedFindings.
Flags tracked by the audit today
gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=truesecurity.audit.suppressions configured (<count>)hooks.gmail.allowUnsafeExternalContent=truehooks.mappings[<index>].allowUnsafeExternalContent=truetools.exec.applyPatch.workspaceOnly=falseplugins.entries.acpx.config.permissionMode=approve-all
All dangerous*/dangerously* keys in the config schema
Control UI and browser:
gateway.controlUi.dangerouslyAllowHostHeaderOriginFallbackgateway.controlUi.dangerouslyDisableDeviceAuth(retired, inert)browser.ssrfPolicy.dangerouslyAllowPrivateNetwork
Channel name-matching (bundled and plugin channels; also per accounts.<accountId> where applicable):
channels.discord.dangerouslyAllowNameMatchingchannels.googlechat.dangerouslyAllowNameMatchingchannels.msteams.dangerouslyAllowNameMatchingchannels.slack.dangerouslyAllowNameMatchingchannels.irc.dangerouslyAllowNameMatching(plugin channel)channels.mattermost.dangerouslyAllowNameMatching(plugin channel)channels.synology-chat.dangerouslyAllowNameMatching(plugin channel)channels.synology-chat.dangerouslyAllowInheritedWebhookPath(plugin channel)channels.zalouser.dangerouslyAllowNameMatching(plugin channel)
Network exposure:
channels.telegram.network.dangerouslyAllowPrivateNetwork(also per account)
Sandbox Docker (defaults + per-agent):
agents.defaults.sandbox.docker.dangerouslyAllowReservedContainerTargetsagents.defaults.sandbox.docker.dangerouslyAllowExternalBindSourcesagents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin
Deployment and host trust
- Encrypt the entire disk on the gateway host; if that machine is shared, run the Gateway under its own dedicated OS account.
- Dependency locking for published packages: when building from source,
pnpm-lock.yamlis used; the releasedopenclawnpm package and OpenClaw-managed npm plugin packages ship withnpm-shrinkwrap.json, so installs rely on the reviewed transitive dependency set from that release rather than computing a new graph during installation. This acts as a supply-chain hardening and release reproducibility boundary, not a sandbox; see npm shrinkwrap. - Safe file handling: OpenClaw relies on
@openclaw/fs-safefor root-constrained file access, atomic writes, archive extraction, temporary workspaces, and secret-file utilities. Native acceleration is off by default; setOPENCLAW_FS_SAFE_NATIVE_MODE=autoto activate an installed platform binding, orrequireto fail closed when no native support exists. More at Secure file operations. - Risks in a shared Slack workspace: when any Slack member can message the bot, the main concern is delegated tool authority, since any permitted sender can trigger tool calls (
exec, browser, network/file tools) inside the agent's policy, prompt or content injection from one sender may alter shared state, devices, or outputs, and if the shared agent holds sensitive credentials or files, any allowed sender could drive exfiltration through tool use. For team workflows, use separate agents or gateways with minimal tools; keep personal-data agents private. - Company-shared agent (acceptable pattern): this works when all users of the agent sit inside the same trust boundary (for instance, a single company team) and the agent is used strictly for business. Deploy it on a dedicated machine, VM, or container, with a dedicated OS user and separate browser, profile, and accounts, and never sign that runtime into personal Apple or Google accounts or personal password-manager or browser profiles. Mixing personal and company identities on one runtime breaks the separation and raises personal-data exposure risk.
Secrets on disk
Treat everything under ~/.openclaw/ (or $OPENCLAW_STATE_DIR/) as potentially containing secrets or private data:
| Path | Contents |
|---|---|
openclaw.json | Config can hold tokens (gateway, remote gateway), provider settings, and allowlists. |
credentials/** | Channel credentials (for example WhatsApp creds), pairing allowlists, legacy OAuth imports. |
state/openclaw.sqlite | Shared runtime state, including native MCP OAuth access/refresh tokens, dynamic client registration secrets, and discovery state. |
agents/<agentId>/agent/openclaw-agent.sqlite | Per-agent runtime state, including model auth profiles. |
agents/<agentId>/agent/auth-profiles.json | Legacy model-auth migration source; doctor imports supported records into the per-agent SQLite database. |
agents/<agentId>/agent/codex-home/** | Per-agent Codex app-server account, config, skills, plugins, native thread state, diagnostics (default). |
$CODEX_HOME/** or ~/.codex/** | Native Codex runtime state. The ordinary harness reaches it only with explicit plugins.entries.codex.config.appServer.homeScope: "user". The separate supervision connection reaches it when its resolved home scope is "user", which is the default for stdio or Unix when unset. Contains the native Codex account, config, plugins, and thread store. Supervision lists source metadata and keeps a continued Chat's canonical native branch and later turns on that connection; branching copies bounded persisted user and assistant history into an authenticated, model-locked OpenClaw Chat. Enable only for an owner-controlled Gateway. See Codex harness and Codex supervision. |
secrets.json (optional) | File-backed secret payload used by file SecretRef providers (secrets.providers). |
agents/<agentId>/agent/auth.json | Legacy compatibility file; static api_key entries are scrubbed when discovered. |
agents/<agentId>/agent/openclaw-agent.sqlite | Per-agent runtime state, including session rows and transcripts that can contain private messages and tool output. |
agents/<agentId>/sessions/** | Legacy session migration sources and archives that can contain private messages and tool output. |
| bundled plugin packages | Installed plugins (plus their node_modules/). |
sandboxes/** | Tool sandbox workspaces; can accumulate copies of files read/written inside the sandbox. |
Credential storage map
Also useful for backup decisions:
- WhatsApp:
~/.openclaw/credentials/whatsapp/<accountId>/creds.json - Telegram bot token: config/env or
channels.telegram.tokenFile(regular file only; symlinks rejected) - Discord bot token: config/env or SecretRef (env/file/exec/store providers)
- Slack tokens: config/env (
channels.slack.*) - Pairing allowlists:
~/.openclaw/credentials/<channel>-allowFrom.json(default account) /<channel>-<accountId>-allowFrom.json(non-default accounts) - Model auth profiles:
~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite(auth_profile_store) - MCP OAuth sessions:
~/.openclaw/state/openclaw.sqlite(mcp_oauth_stores) - Legacy OAuth import:
~/.openclaw/credentials/oauth.json
Hardening: keep permissions tight (700 on dirs, 600 on files); use full-disk encryption on the gateway host; prefer a dedicated OS user account if the host is shared.
File permissions
~/.openclaw/openclaw.json:600(user read/write only)~/.openclaw:700(user only)
openclaw doctor can warn and offer to tighten these.
Workspace .env files
OpenClaw loads workspace-local .env files for agents and tools, but never lets them silently override gateway runtime controls:
- Provider credential environment variables are not accessible from untrusted workspace
.envfiles, includingGEMINI_API_KEY,GOOGLE_API_KEY,XAI_API_KEY,MISTRAL_API_KEY,GROQ_API_KEY,DEEPSEEK_API_KEY,PERPLEXITY_API_KEY,BRAVE_API_KEY,TAVILY_API_KEY,EXA_API_KEY,FIRECRAWL_API_KEY, and provider auth keys declared by installed trusted plugins. Instead, place provider credentials in the Gateway process environment,~/.openclaw/.env($OPENCLAW_STATE_DIR/.env), the configenvblock, or an optional login-shell import. - Any key prefixed with
OPENCLAW_is disallowed in untrusted workspace.envfiles, which reserves the entire runtime namespace so a futureOPENCLAW_*control defaults to fail-closed rather than being silently inherited from checked-in or attacker-supplied.envcontent. - Workspace
.envoverrides also cannot set channel and provider endpoint-routing settings (such asMATRIX_HOMESERVER,MATTERMOST_URL,IRC_HOST,SYNOLOGY_CHAT_INCOMING_URL,AZURE_SPEECH_ENDPOINT, and other keys ending in_ENDPOINT), preventing a cloned workspace from rerouting bundled connector traffic through local endpoint config. These settings must originate from the gateway process environment, global runtime dotenv, explicit config, orenv.shellEnv. - Trusted process/OS environment variables, global runtime dotenv, config
env, and enabled login-shell import remain in effect, this restriction only applies to workspace.envfile loading.
Workspace .env files often sit alongside agent code, get committed by mistake, or are generated by tools; blocking provider credentials stops a cloned workspace from swapping in attacker-controlled provider accounts.
Logs and transcripts
OpenClaw writes session transcripts to disk under ~/.openclaw/agents/<agentId>/sessions/*.jsonl for session continuity and optional memory indexing, meaning any process or user with filesystem access can view them. Treat disk access as the trust boundary and secure ~/.openclaw permissions; run agents under separate OS users or hosts for stronger isolation.
Gateway logs may contain tool summaries, errors, and URLs; session transcripts can include pasted secrets, file contents, command output, and links.
- Log and transcript redaction is always active and cannot be turned off via config.
- Use
logging.redactPatternsto add custom patterns for your environment (tokens, hostnames, internal URLs). - When sharing diagnostics, prefer
openclaw status --all(pasteable, secrets redacted) over raw logs. - Prune old session transcripts and log files if long retention is unnecessary.
Details: Logging
Secure baseline (copy/paste)
{
gateway: {
mode: "local",
bind: "loopback",
port: 18789,
auth: { mode: "token", token: "your-long-random-token" },
},
channels: {
whatsapp: {
dmPolicy: "pairing",
groups: { "*": { requireMention: true } },
},
},
}
Keeps the Gateway private, requires DM pairing, and gates group replies behind a mention. Groups are fully supported, sender identity is threaded through to the agent, and per-group settings let one room run different defaults than another, so the goal here is scoping the agent's attention, not avoiding groups. For safer tool execution too, add a sandbox plus deny dangerous tools for any non-owner agent (see "Per-agent access profiles" above).
Separate numbers (WhatsApp, Signal, Telegram)
For phone-number-based channels, consider running the assistant on a separate number from your personal one, so personal conversations stay private and the bot number handles automation with its own boundaries.
Incident response
Contain
- Stop it: stop the macOS app (if it supervises the Gateway) or terminate your
openclaw gatewayprocess. - Close exposure: set
gateway.bind: "loopback"(or disable Tailscale Funnel/Serve) until you understand what happened. - Freeze access: switch risky DMs/groups to
dmPolicy: "disabled"/ require mentions, and remove any"*"allow-all entries.
Rotate (assume compromise if secrets leaked)
- Rotate Gateway auth (
gateway.auth.token/OPENCLAW_GATEWAY_PASSWORD) and restart. - Rotate remote client secrets (
gateway.remote.token/.password) on any machine that can call the Gateway. - Rotate provider/API credentials (WhatsApp creds, Slack/Discord tokens, model/API keys in
auth-profiles.json, and encrypted secrets payload values when used).
Audit
- Inspect the Gateway logs using
openclaw logs(oropenclaw --profile <profile> logswhen working with a named profile). By default, the log location is/tmp/openclaw/openclaw-YYYY-MM-DD.log; for named profiles, it is/tmp/openclaw/openclaw-<profile>-YYYY-MM-DD.log, unlesslogging.filechanges that. - Go through the applicable transcript(s):
~/.openclaw/agents/<agentId>/sessions/*.jsonl. - Examine recent configuration modifications that might have broadened access:
gateway.bind,gateway.auth, DM/group policies,tools.elevated, and any plugin updates. - Execute
openclaw security audit --deeponce more and verify that all important findings have been addressed.
Collect for a report
- The timestamp, the gateway host OS, and the OpenClaw version.
- The session transcript(s) plus a brief, redacted log excerpt.
- The attacker's input and the agent's corresponding actions.
- Whether the Gateway was reachable beyond loopback (LAN/Tailscale Funnel/Serve).
Secret scanning
CI applies the pre-commit detect-private-key hook across the repository. Should it fail, either delete or rotate the exposed key material, then reproduce the issue locally:
pre-commit run --all-files detect-private-key
Reporting security issues
Discovered a vulnerability in OpenClaw? Report it responsibly:
- Send an email to security@openclaw.ai
- Avoid public disclosure until a fix is available.
- You will receive credit (unless you choose to remain anonymous).