docs/gateway/security/exposure-runbook.md
This runbook turns the broader Security guidance into an operator checklist for remote access and messaging exposure.
Prefer the narrowest pattern that satisfies the workflow.
| Pattern | Recommended when | Required controls |
|---|---|---|
| Loopback + SSH tunnel | Personal use, admin access, debugging | Keep gateway.bind: "loopback" and tunnel 127.0.0.1:18789 |
| Loopback + Tailscale Serve | Personal tailnet access to Control UI/WebSocket | Keep Gateway loopback-only; Tailscale identity headers only authenticate the Control UI WebSocket surface, not other auth paths |
| Tailnet/LAN bind | Dedicated private network with known devices | Gateway auth, firewall allowlist, no public port-forward |
| Trusted reverse proxy | Organization SSO/OIDC in front of Gateway | trusted-proxy auth, strict trustedProxies, header overwrite/strip rules, explicit allowed users |
| Public internet | Rare, high-risk deployments | Identity-aware proxy, TLS, rate limits, strict allowlists, sandboxed non-main sessions |
Avoid direct public port-forwarding to the Gateway. If public access is required, put an identity-aware proxy in front of it and make the proxy the only network path to the Gateway.
Record these before changing bind, proxy, Tailscale, or channel policy:
~/.openclaw).gateway.bind; default port 18789).~/.openclaw/openclaw.json and credentials.If more than one person can message the bot, treat this as shared delegated tool authority, not per-user host isolation.
Run before opening access:
openclaw doctor
openclaw security audit
openclaw security audit --deep
openclaw health
Resolve critical findings first. Accept warnings only when intentional and
documented for the deployment. See Security audit checks
for what each checkId means and its fix key.
For remote CLI validation, pass credentials explicitly:
openclaw gateway probe --url ws://127.0.0.1:18789 --token "$OPENCLAW_GATEWAY_TOKEN"
Do not assume local config credentials apply to an explicit remote URL.
Use this shape as the starting point for exposed deployments:
{
gateway: {
bind: "loopback",
auth: {
mode: "token",
token: "replace-with-a-long-random-token",
},
},
session: {
dmScope: "per-channel-peer",
},
agents: {
defaults: {
sandbox: { mode: "non-main" },
},
},
tools: {
profile: "messaging",
exec: { security: "deny", ask: "always" },
elevated: { enabled: false },
},
}
Widen one control at a time: add a specific channel allowlist before enabling write-capable tools, or enable a reverse proxy before accepting remote Control UI traffic.
tools.exec.security: "deny" blocks all exec calls, including benign
diagnostics. If diagnostics or low-risk commands are required, relax this only
after choosing the specific senders, agents, commands, and approval mode that
match your threat model.
Messaging channels are untrusted input surfaces. Before allowing DMs or groups:
dmPolicy: "pairing" or a strict allowFrom list over dmPolicy: "open"."*" allowlists with broad tool access.session.dmScope: "per-channel-peer" (or "per-account-channel-peer" for
multi-account channels) when multiple people can DM the bot, so DM sessions
don't share context.Pairing approves the sender to trigger the bot. It does not make that sender a separate host security boundary.
For identity-aware proxies:
gateway.trustedProxies must list only the proxy source IPs.gateway.auth.trustedProxy.allowUsers when the proxy serves more than
one audience.gateway.auth.trustedProxy.allowLoopback only for a same-host proxy
where local processes are trusted and the proxy owns the identity headers.Run openclaw security audit --deep after proxy changes. Trusted-proxy
findings are high-signal because the proxy becomes the authentication
boundary.
Before exposing an agent to remote senders:
If remote users are not fully trusted, isolation must come from separate deployments, not only from prompts or session labels.
After each exposure change:
openclaw security audit --deep.Do not proceed to the next exposure change until the current one is understood.
If the Gateway may be overexposed:
{
gateway: {
bind: "loopback",
},
channels: {
whatsapp: { dmPolicy: "disabled" },
telegram: { dmPolicy: "disabled" },
discord: { dmPolicy: "disabled" },
slack: { dmPolicy: "disabled" },
},
tools: {
exec: { security: "deny", ask: "always" },
elevated: { enabled: false },
},
}
Then:
"*" and unexpected senders from allowlists.openclaw security audit --deep.