Pairing and Allowlists: Controlling Who Can Talk to Your AI Agent
Access control in most software decides what someone can see. Access control in OpenClaw decides what someone can make your computer do. That’s a meaningfully different question, and it deserves more thought than a five-minute setup usually gets.
The good news is that the model is small — four policies, one handshake, a couple of list formats. The important news is that these checks run before the model is ever invoked, which makes them the cheapest and most reliable control in the entire system. A rejected sender costs you nothing, reaches nothing, and cannot argue with anything.
This page covers the mechanics, then the design patterns that matter once more than one person is involved.
Why This Is the Highest-Leverage Setting
Recall the order of operations when a message arrives:
Inbound message │ ▼ ① Access policy ◄── dmPolicy / allowFrom / groupPolicy │ REJECTED HERE = zero cost, zero reach ▼ ② Mention gating ◄── requireMention / implicitMentions │ ▼ ③ Session resolution │ ▼ ④ Model invocation ◄── first token spent, first risk taken │ ▼ ⑤ Tool executionEverything interesting — cost, prompt injection risk, tool execution — lives at step ④ and below. Access control sits at step ①.
This has a practical consequence worth stating plainly: every other defence in OpenClaw is probabilistic; this one is binary. Tool policy narrows what a compromised context can do. Sandboxing contains the damage. Careful prompting reduces the odds. But an unapproved sender simply doesn’t get a turn. There is no clever phrasing that gets past an allowlist, because the allowlist is checked by code that doesn’t read English.
Spend your first hour of security effort here. It has better returns than anything else you can do.
The Four DM Policies
dmPolicy is set per channel and governs direct messages.
| Policy | What happens to an unknown sender | Use when |
|---|---|---|
pairing (default) | Gets a one-time code; blocked until you approve | You want others to be able to request access |
allowlist | Blocked silently; no handshake offered | You know exactly who should have access |
open | Allowed (requires explicit allowFrom: ["*"]) | Almost never — see below |
disabled | All DMs ignored | The agent is group-only, or scheduled-only |
pairing — the sensible default
An unrecognised sender triggers a one-time code. Nothing happens until you approve it:
openclaw pairing approve telegram 428913Two built-in limits keep this from being abusable: codes expire after one hour, and pending requests are capped at three per account. So somebody who stumbles onto your bot can’t queue up fifty approval prompts to wear you down or bury a real request among fakes.
This is the right default because it separates reachability from authorisation. Anyone can knock. Only you decide who comes in.
allowlist — the tighter option
No handshake at all. If you’re not on the list, the agent behaves as though it doesn’t exist.
{ channels: { telegram: { dmPolicy: "allowlist", allowFrom: ["tg:123456789", "tg:987654321"] } }}Prefer this over pairing when the set of users is fixed and known — your own devices, your partner, one colleague. It removes an entire interaction surface. There’s nothing to phish, nothing to social-engineer, no approval prompt that could be mistimed or misread.
If you find pairing tedious, this is the setting you actually want. Not open.
open — read this before you use it
open allows every inbound DM, and it deliberately requires an explicit allowFrom: ["*"] to work. That awkwardness is intentional: the config is making you state the thing out loud.
What open means in practice: any stranger who discovers your bot’s username is now in direct conversation with a model that holds whatever tool permissions you granted. If exec is available, they’re talking to something that can run commands on your machine. Not because the model is disloyal — because it’s helpful, and they asked nicely with a plausible story.
There are legitimate uses: a public information bot with a read-only agent, strict rate limits, and no filesystem access. If that’s genuinely your setup, fine. If you’re reaching for open because approving a pairing code felt like friction, use allowlist instead.
disabled — more useful than it sounds
Ignores DMs entirely. Worth knowing about for two setups:
- A group-only agent that participates in a team channel but has no private surface at all.
- A scheduled-only agent that exists purely to run cron jobs and push notifications outward, with no inbound conversation.
Narrowing the input surface to nothing is a real security posture, not a degraded one.
Allowlist Formats
Getting the identifier format wrong is the single most common reason an allowlist appears not to work. The pattern is always: stable machine ID, not human-readable name.
| Channel | Format | Example |
|---|---|---|
| Telegram | Numeric user ID | "tg:123456789" |
| Discord | Numeric snowflake | "987654321098765432" |
| Slack | User reference | "user:U12345" |
| Phone number / JID | "+15551234567" | |
| Matrix | Full matrix ID | "@user:matrix.org" |
| iMessage | Phone or email | "+15555550123", "a@example.com" |
Usernames are deliberately not used. They can be changed, released, and re-registered by someone else — an allowlist keyed on @johndoe is an allowlist that silently transfers to whoever claims that handle next.
Where to get the right value: the Control UI shows the exact identifier when a message arrives. That’s faster and more reliable than hunting through platform settings, and it eliminates format guesswork entirely.
Group Access Is a Separate Question
dmPolicy governs private messages. Groups have their own controls, and it’s important to understand they’re independent — an allowlisted DM sender has no special standing in a group, and vice versa.
{ channels: { whatsapp: { dmPolicy: "pairing", allowFrom: ["+15555550123"],
groupPolicy: "allowlist", groupAllowFrom: ["+15551234567"], groups: { "*": { requireMention: true } } } }}groupPolicy takes allowlist (default), open, or disabled. With allowlist, only configured groups are permitted at all.
Then requireMention decides whether the agent speaks in a permitted group. These stack: a group must be allowed and the bot must be addressed.
The thing about groups that people underestimate
In a DM, you control both ends of the conversation. In a group, every participant is an input source — and so is every message any of them pastes in. A colleague forwards a customer email. Someone shares a stack trace from a public issue tracker. Someone links an article the agent then fetches.
All of that text lands in the context window of a model holding your tool permissions. None of those people are attackers. That’s exactly the point: the injected instruction doesn’t have to come from someone hostile, it just has to reach the context.
The mitigation isn’t to distrust your colleagues. It’s to make the agent in that room incapable of doing damage — which brings us to the pattern that matters most.
The Pattern: Match Capability to Trust
Here is the design idea that makes multi-user OpenClaw workable, and it’s worth more than every other tip on this page combined.
Don’t try to give different people different permissions on the same agent. Give different agents to different trust levels, and route accordingly.
Access control decides who gets in. Agent configuration decides what they can reach once inside. Using both together gives you real tiers:
{ agents: { entries: { // Tier 1 — you, from your own devices personal: { tools: { allow: ["read", "write", "exec", "browser"] } },
// Tier 2 — trusted humans, DM only household: { tools: { allow: ["read", "web_search"], deny: ["exec", "write"] }, sandbox: { mode: "all", workspaceAccess: "ro" } },
// Tier 3 — group channels, semi-public public_room: { tools: { allow: ["web_search"], deny: ["exec", "write", "read", "browser"] }, sandbox: { mode: "all", workspaceAccess: "none" } } } }}Now the question “what if someone in the Discord tries to make it delete my files?” has a structural answer rather than a hopeful one: the agent handling that room has no filesystem tool. There is nothing to talk it into.
Compare with the alternative — one powerful agent plus a system prompt saying “only obey the owner.” That’s a preference expressed in the same channel as the attack. It is not a control.
Put the capabilities where the untrusted input isn’t. If you remember one sentence from this series, that’s a good candidate.
Session Isolation Is Part of Access Control
People file this under “sessions” and miss that it’s an access question. It isn’t about memory quality — it’s about who can read whose conversation.
{ session: { dmScope: "per-channel-peer" }}Without it, DMs can funnel into one shared session. Your private conversation sits in the context window while the agent answers someone else. The agent may then reference it — helpfully, accurately, and disastrously.
This failure is silent. Nothing errors. You find out when someone mentions knowing something you never told them.
The rule: allowlisting a second person and setting per-channel-peer are the same task. Do them in the same edit.
Operating This Over Time
Review the list periodically. Allowlists accumulate. The contractor you added in March, the friend who wanted to try it — each is a standing grant nobody revisits. Read the list every few months and remove what’s stale.
Comment your entries. The file is JSON5, so this costs nothing and saves real time:
{ channels: { telegram: { allowFrom: [ "tg:123456789", // me, primary "tg:987654321", // me, travel phone "tg:555444333" // Priya — added 2026-03, review Sept ] } }}Audit before widening anything.
openclaw security audit --deepRun this before changing gateway.bind, before switching a policy to open, and before adding a group. It catches the combinations you didn’t think about.
Know how to revoke fast. If access needs to stop right now, remove the entry — the Gateway hot-reloads, so it takes effect immediately. For a compromised channel credential, revoke the token at the platform (BotFather’s /revoke, Discord’s regenerate, Slack’s rotate) rather than only editing config. Config controls who your Gateway listens to; the token controls who can act as your bot.
Protect the config itself. It holds tokens and allowlists in plaintext:
chmod 700 ~/.openclawchmod 600 ~/.openclaw/openclaw.jsonTroubleshooting
“The bot ignores me.” Most likely a pending pairing request. List and approve it. If pairing isn’t the policy, your ID isn’t in allowFrom — or is there in the wrong format.
“My allowlist entry doesn’t work.” Format. @username where a numeric ID belongs, a missing user: prefix on Slack, a phone number without + and country code. Take the exact value from the Control UI.
“Someone I removed can still message it.” Check every level — channels.defaults, the per-channel block, per-group overrides. A permissive groupPolicy can leave a path open that removing a DM entry doesn’t close.
“Pairing codes expire before I approve them.” They last an hour. If you’re regularly too slow, the honest answer is that allowlist fits your workflow better.
“The agent knows things the sender never told it.” Session bleed. dmScope: "per-channel-peer", immediately.
A Defensible Baseline
{ session: { dmScope: "per-channel-peer" },
channels: { defaults: { groupPolicy: "allowlist", implicitMentions: { replyToBot: true, quotedBot: true, threadParticipation: true } },
telegram: { botToken: "REPLACE_ME", dmPolicy: "pairing", allowFrom: [ "tg:YOUR_ID" // owner ], groups: { "*": { requireMention: true } } } },
tools: { profile: "messaging", deny: ["group:automation", "group:runtime", "group:fs"], exec: { security: "deny", ask: "always" } }}One person, one channel, no execution, isolated sessions, groups gated. Everything after this should be a deliberate addition you can explain — not a default you inherited.
Where to Go Next
Access control is the front door. The next section is about what’s behind it — what the agent can actually do once a message is allowed through.
Skills in OpenClaw covers packaging repeatable behaviour, and Tools and MCP covers the capability layer and the policy that constrains it. If the trust-tier pattern above is the part you want to push further, The OpenClaw Threat Model is where it gets developed properly.