mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
6e026c2fe3
* fix(gateway): bind auth limits to ingress attribution * fix(gateway): close remaining ingress auth gaps * fix(gateway): carry attribution into new ingress paths * fix(gateway): close ingress ownership gaps * fix(gateway): complete proxy ingress hardening * fix(gateway): stabilize managed Tailscale ingress * fix(gateway): make Tailscale cleanup ownership-safe Refuse reset-on-exit publication until Tailscale exposes an atomic owner-bound cleanup operation, and migrate legacy configs with Doctor. * fix(gateway): finish ingress ownership repair * fix(gateway): own managed Tailscale route lifetime Run managed Serve and Funnel routes as foreground claims tied to the Gateway lifecycle. Retire named Service config through Doctor because Tailscale Services cannot run in foreground mode. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): align Tailscale consumers and build guards Remove the retired named-service config from Telegram Mini App URL resolution and register the lifecycle worker as an explicit production entry. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): preserve retired Tailscale inputs Keep Funnel enabled when removing an ignored named-Service setting and accept the legacy positive reset flag as a no-op now that managed routes always follow Gateway lifetime. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): preserve Tailscale route diagnostics Prefer the actionable foreground CLI failure captured during timeout cleanup, and cover the original delayed-failure ordering. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): reconcile Tailscale ingress with main Preserve current ingress ownership contracts after the rebase, retire the obsolete device-auth migration check, validate route-owner IPC, and move Tailscale auth coverage onto the managed listener. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): finish ingress rebase coverage Unify the rebased net imports and let module-reset WebSocket tests prepare attribution through the same fresh module instance as the handler. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * test(gateway): align run-loop server fixture --------- Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
290 lines
12 KiB
Markdown
290 lines
12 KiB
Markdown
---
|
|
summary: "Pairing overview: approve who can DM you + which nodes can join"
|
|
read_when:
|
|
- Setting up DM access control
|
|
- Pairing a new iOS/Android node
|
|
- Reviewing OpenClaw security posture
|
|
title: "Pairing"
|
|
---
|
|
|
|
"Pairing" is OpenClaw's explicit access approval step.
|
|
It is used in two places:
|
|
|
|
1. **DM pairing** (who is allowed to talk to the bot)
|
|
2. **Node pairing** (which devices/nodes are allowed to join the gateway network)
|
|
|
|
Security context: [Security](/gateway/security)
|
|
|
|
## 1) DM pairing (inbound chat access)
|
|
|
|
When a channel is configured with DM policy `pairing`, unknown senders get a short code and their message is **not processed** until you approve.
|
|
|
|
Default DM policies are documented in: [Security](/gateway/security)
|
|
|
|
`dmPolicy: "open"` is public only when the effective DM allowlist includes `"*"`.
|
|
Setup and validation require that wildcard for public-open configs. If existing
|
|
state contains `open` with concrete `allowFrom` entries, runtime still admits
|
|
only those senders, and pairing-store approvals do not widen `open` access.
|
|
|
|
Pairing codes:
|
|
|
|
- 8 characters, uppercase, no ambiguous chars (`0O1I`).
|
|
- **Expire after 1 hour**. The bot only sends the pairing message when a new request is created (roughly once per hour per sender).
|
|
- Pending DM pairing requests are capped at **3 per channel account**; additional requests are ignored until one expires or is approved.
|
|
|
|
### Approve from the Control UI
|
|
|
|
Open **Settings → Channels → DM access requests**. The queue combines pending
|
|
requests from every configured channel account whose DM policy is `pairing`.
|
|
Filter by channel or account, review the sender ID and metadata, then choose
|
|
**Approve**.
|
|
|
|
Approval grants direct-message access only. It does not grant group access. The
|
|
approval dialog also offers these explicit options when supported:
|
|
|
|
- **Notify the requester after approval**
|
|
- **Also make this sender the first command owner**, shown only when no command
|
|
owner exists and the Control UI session has `operator.admin`
|
|
|
|
Choose **Dismiss** to remove a pending request without approving it. Dismissal is
|
|
not a permanent block; the sender can request access again later.
|
|
|
|
### Approve from the CLI
|
|
|
|
```bash
|
|
openclaw pairing list telegram
|
|
openclaw pairing approve telegram <CODE>
|
|
```
|
|
|
|
Add `--notify` to tell the requester on the same channel. Multi-account channels
|
|
take `--account <id>`.
|
|
|
|
Unlike the Control UI's explicit checkbox, the CLI automatically bootstraps
|
|
`commands.ownerAllowFrom` when no command owner is configured, using an entry
|
|
such as `telegram:123456789`. This gives first-time setups an explicit owner for
|
|
privileged commands and exec approval prompts. After an owner exists, later
|
|
pairing approvals only grant DM access; they do not add more owners.
|
|
|
|
<Note>
|
|
WhatsApp's login QR links a WhatsApp account to OpenClaw. DM access requests
|
|
approve people who message that account. These are separate flows.
|
|
</Note>
|
|
|
|
Supported channels (any installed channel plugin that declares pairing; external plugins such as `openclaw-weixin` can add more): `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloud-talk`, `nostr`, `signal`, `slack`, `sms`, `synology-chat`, `telegram`, `twitch`, `whatsapp`, `zalo`, `zalouser`.
|
|
|
|
### Reusable sender groups
|
|
|
|
Use top-level `accessGroups` when the same trusted sender set should apply to
|
|
multiple message channels or to both DM and group allowlists.
|
|
|
|
Static groups use `type: "message.senders"` and are referenced with
|
|
`accessGroup:<name>` from channel allowlists:
|
|
|
|
```json5
|
|
{
|
|
accessGroups: {
|
|
operators: {
|
|
type: "message.senders",
|
|
members: {
|
|
discord: ["discord:123456789012345678"],
|
|
telegram: ["987654321"],
|
|
whatsapp: ["+15551234567"],
|
|
},
|
|
},
|
|
},
|
|
channels: {
|
|
telegram: { dmPolicy: "allowlist", allowFrom: ["accessGroup:operators"] },
|
|
whatsapp: { groupPolicy: "allowlist", groupAllowFrom: ["accessGroup:operators"] },
|
|
},
|
|
}
|
|
```
|
|
|
|
Access groups are documented in detail here: [Access groups](/channels/access-groups)
|
|
|
|
### Where the state lives
|
|
|
|
Stored in the shared SQLite state database at
|
|
`~/.openclaw/state/openclaw.sqlite`:
|
|
|
|
- pending requests in `channel_pairing_requests`
|
|
- approved senders in `channel_pairing_allow_entries`
|
|
|
|
Account scoping behavior:
|
|
|
|
- each request and approved sender is keyed by channel and account
|
|
- runtime reads only the canonical SQLite rows; it does not merge legacy files
|
|
|
|
Older gateways wrote `<channel>-pairing.json` and
|
|
`<channel>-<accountId>-allowFrom.json` under `~/.openclaw/credentials/`.
|
|
Startup migration and `openclaw doctor --fix` import those files into SQLite and
|
|
remove each source after a successful import. Treat the SQLite database as
|
|
sensitive because these rows gate access to your assistant.
|
|
|
|
<Note>
|
|
The pairing allowlist store is for DM access. Group authorization is separate.
|
|
Approving a DM pairing code does not automatically allow that sender to run group
|
|
commands or control the bot in groups. First-owner bootstrap is separate config
|
|
state in `commands.ownerAllowFrom`, and group chat delivery still follows the
|
|
channel's group allowlists (for example `groupAllowFrom`, `groups`, or per-group
|
|
or per-topic overrides depending on the channel).
|
|
</Note>
|
|
|
|
## 2) Node device pairing (iOS/Android/macOS/headless nodes)
|
|
|
|
Nodes connect to the Gateway as **devices** with `role: node`. The Gateway
|
|
creates a device pairing request that must be approved.
|
|
|
|
### Pair from the Control UI (recommended)
|
|
|
|
Use an already connected Control UI session with `operator.admin` access:
|
|
|
|
1. Open the Control UI and go to **Settings → Devices**.
|
|
2. On the **Devices** page, click **Pair device**.
|
|
3. Keep **Full access (recommended)**, or select **Limited access** to omit
|
|
administrative Gateway controls.
|
|
4. Click **Create setup code**.
|
|
5. On your phone, open the OpenClaw app → **Settings** → **Gateway**.
|
|
6. Scan the QR code or paste the setup code, then connect.
|
|
|
|
Official OpenClaw iOS and Android apps are approved automatically when their
|
|
setup-code metadata matches. If **Pending approval** shows a request (for
|
|
example, for a non-official client or mismatched metadata), review its role and
|
|
scopes before approving it.
|
|
|
|
The button is disabled when the current Control UI session does not have
|
|
administrator access. Use the CLI approval flow below from the Gateway host in
|
|
that case.
|
|
|
|
### Pair via Telegram
|
|
|
|
If you use the `device-pair` plugin, you can do first-time device pairing entirely from Telegram:
|
|
|
|
1. In Telegram, message your bot: `/pair`
|
|
2. The bot replies with two messages: an instruction message and a separate **setup code** message (easy to copy/paste in Telegram).
|
|
3. On your phone, open the OpenClaw iOS app → Settings → Gateway.
|
|
4. Scan the QR code (`/pair qr`) or paste the setup code and connect.
|
|
5. The official mobile app connects automatically. If `/pair pending` shows a
|
|
request, review its role and scopes before approving it.
|
|
|
|
The setup code is a base64-encoded JSON payload that contains:
|
|
|
|
- `url`: the Gateway WebSocket URL (`ws://...` or `wss://...`)
|
|
- `urls`: when available, the ordered LAN/Tailnet routes the mobile app can try
|
|
- `bootstrapToken`: a single-use bootstrap token for the initial pairing handshake; the Gateway expires it after 10 minutes
|
|
|
|
Run `/pair cleanup` to invalidate unused setup codes once pairing finishes.
|
|
|
|
That bootstrap token carries the built-in pairing bootstrap profile:
|
|
|
|
- a secure `wss://` setup (or same-host loopback) defaults to `node` plus full
|
|
native-mobile `operator` access
|
|
- the handed-off `node` token stays `scopes: []`
|
|
- the default handed-off `operator` token includes `operator.admin`,
|
|
`operator.approvals`, `operator.read`, `operator.talk.secrets`, and
|
|
`operator.write`
|
|
- Control UI **Limited access** and `openclaw qr --limited` omit
|
|
`operator.admin` while keeping the other operator scopes
|
|
- plaintext LAN `ws://` setup automatically uses the same limited profile;
|
|
configure `wss://` or Tailscale Serve and generate a new code for full access
|
|
- later token rotation/revocation remains bounded by both the device's approved
|
|
role contract and the caller session's operator scopes
|
|
|
|
Treat the setup code like a password while it is valid.
|
|
|
|
The iOS and Android **Settings → Gateway** pages show **Full** or **Limited**
|
|
access. To upgrade a limited phone, first configure a secure `wss://` or
|
|
Tailscale Serve route, then generate a new full-access setup code, scan or paste
|
|
it in that settings page, and reconnect.
|
|
|
|
For Tailscale, public, or other remote mobile pairing, use Tailscale Serve/Funnel
|
|
or another `wss://` Gateway URL. Plaintext `ws://` setup codes are accepted only
|
|
for loopback, private LAN addresses, `.local` Bonjour hosts, and the Android
|
|
emulator host. Non-loopback plaintext routes receive limited access. Tailnet
|
|
CGNAT addresses, `.ts.net` names, and public hosts still fail closed before
|
|
QR/setup-code issuance.
|
|
|
|
OpenClaw advertises Tailscale setup URLs only when it owns the route through
|
|
`gateway.tailscale.mode=serve|funnel`. Legacy external Serve routes that proxy a
|
|
`gateway.bind=lan` listener are not advertised because the ordinary listener
|
|
rejects Tailscale-shaped proxy ingress. Run `openclaw doctor` to preview the
|
|
safe default-route migration, then `openclaw doctor --fix` and restart the
|
|
Gateway. Custom Serve ports and Tailscale Services require manual migration.
|
|
For a retired `gateway.tailscale.serviceName` config, Doctor disables managed
|
|
ingress and prints the command needed to clear the retained Service route.
|
|
|
|
### Approve a node device
|
|
|
|
```bash
|
|
openclaw devices list
|
|
openclaw devices approve <requestId>
|
|
openclaw devices reject <requestId>
|
|
```
|
|
|
|
When an explicit approval is denied because the approving paired-device session
|
|
was opened with pairing-only scope, the CLI retries the same request with
|
|
`operator.admin`. This lets an existing admin-capable paired device recover a new
|
|
Control UI/browser pairing without editing the pairing store by hand. The
|
|
Gateway still validates the retried connection; tokens that cannot authenticate
|
|
with `operator.admin` remain blocked.
|
|
|
|
If the same device retries with different auth details (for example different
|
|
role/scopes/public key), the previous pending request is superseded and a new
|
|
`requestId` is created.
|
|
|
|
<Note>
|
|
An already paired device does not get broader access silently. If it reconnects asking for more scopes or a broader role, OpenClaw keeps the existing approval as-is and creates a fresh pending upgrade request. Use `openclaw devices list` to compare the currently approved access with the newly requested access before you approve.
|
|
</Note>
|
|
|
|
### Optional trusted-CIDR node auto-approve
|
|
|
|
Device pairing remains manual by default. For tightly controlled node networks,
|
|
you can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
|
|
|
|
```json5
|
|
{
|
|
gateway: {
|
|
nodes: {
|
|
pairing: {
|
|
autoApproveCidrs: ["192.168.1.0/24"],
|
|
},
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
This only applies to fresh `role: node` pairing requests with no requested
|
|
scopes. Operator, browser, Control UI, and WebChat clients still require manual
|
|
approval. Role, scope, metadata, and public-key changes still require manual
|
|
approval.
|
|
|
|
### Node pairing state storage
|
|
|
|
Stored in the shared SQLite state database at `~/.openclaw/state/openclaw.sqlite`:
|
|
|
|
- pending device pairing requests (short-lived; they expire after 5 minutes)
|
|
- paired devices + tokens
|
|
|
|
Older gateways kept this state in `~/.openclaw/devices/*.json`; those files are
|
|
imported into SQLite at gateway startup and archived with a `.migrated` suffix.
|
|
|
|
### Notes
|
|
|
|
- The `node.pair.*` API (CLI: `openclaw nodes pending|approve|reject|remove|rename`) manages
|
|
node capability approvals stored on the same paired device records. WS nodes
|
|
still require device pairing; see [Node pairing](/gateway/pairing).
|
|
- The pairing record is the durable source of truth for approved roles. Active
|
|
device tokens stay bounded to that approved role set; a stray token entry
|
|
outside the approved roles does not create new access.
|
|
|
|
## Related docs
|
|
|
|
- Security model + prompt injection: [Security](/gateway/security)
|
|
- Updating safely (run doctor): [Updating](/install/updating)
|
|
- Channel configs:
|
|
- Telegram: [Telegram](/channels/telegram)
|
|
- WhatsApp: [WhatsApp](/channels/whatsapp)
|
|
- Signal: [Signal](/channels/signal)
|
|
- iMessage: [iMessage](/channels/imessage)
|
|
- Discord: [Discord](/channels/discord)
|
|
- Slack: [Slack](/channels/slack)
|