Pairing, Allowlisting, and Per-Channel Identity Mapping

Learning Objectives

Pre-Reading Check — Pairing and Enrollment

1. What makes out-of-band approval the source of security in a pairing flow?

The pairing code is encrypted before it is sent to the sender
The sender can request a code, but only an operator on a separate trusted console can approve it
The code is long enough that it cannot be guessed within an hour
The assistant verifies the sender's webhook signature before issuing the code

2. When an unknown contact first messages the bot under a pairing policy, what happens to that first message?

It is forwarded to the model, which decides whether to respond
It is answered normally but flagged for later review
It never reaches the LLM; the assistant replies only with a pairing code and blocks the sender
It is queued until the code expires, then delivered

3. Which hardening measure prevents an attacker from flooding the endpoint with pairing requests?

A cap on pending pairing requests per channel, with extra attempts silently ignored
Requiring the sender to solve a CAPTCHA before a code is issued
Making codes case-sensitive
Encrypting the pending-request store on disk

4. What is the difference between DM pairing and node pairing?

DM pairing is temporary; node pairing is permanent
DM pairing controls which people may message the bot; node pairing controls which physical devices may connect to the gateway
DM pairing uses codes; node pairing uses passwords
DM pairing is for owners; node pairing is for strangers

5. Why is revoking a paired contact described as "fast and local"?

Because the assistant automatically re-verifies every contact each hour
Because the binding is stored data, so you delete one approved-contact entry and the next message is treated as an unknown stranger
Because revocation waits for a short-lived token to expire on its own
Because revocation must be pushed out to a fleet of devices at once

Pairing and Enrollment

Key Points

Any WhatsApp number, Telegram handle, or Slack app is reachable by strangers. If the assistant forwards every inbound message to the model, it takes instructions from the entire internet — the over-trust failure from Chapter 1. Enrollment closes this gap. The real-world analogy is a secure facility: a stranger at the door is not admitted because they ask; they receive a visitor token, and a badged employee must vouch for that token on a separate, trusted path before the door unlocks.

The pairing code is typically an 8-character string built to avoid lookalike characters (no 0/O or 1/l confusion) and expires after roughly one hour. The sender receives it in-channel, but approval happens elsewhere — for example the owner runs openclaw pairing approve whatsapp <CODE> on a trusted device, which writes an allowlist entry.

Two categories of pairing guard different doors:

Pairing typeControlsWhat it bindsExample command
DM pairingWhich people/handles on a channel may message the botA human's chat identity to the assistantopenclaw pairing approve <channel> <CODE>
Node pairingWhich physical devices may connect to the gatewayA device (iOS, Android, macOS, headless host) to the gatewayopenclaw pairing approve nodes <CODE>

A Worked Pairing Flow

Tracing a first-contact sequence for a new Telegram user, Dana: (1) Dana messages the bot and the gate finds no approved contact. (2) The assistant issues a code such as K7M9-P2QX, records a pending request keyed to her Telegram user ID, and replies only with the code — the message never reaches the LLM. (3) The operator runs openclaw pairing approve telegram K7M9-P2QX out-of-band, moving Dana from pending to approved. (4) Her next message resolves to an approved contact and is forwarded, subject to her capability tier. (5) If the operator never approves, the code expires in ~1 hour; if an attacker floods, rate limiting caps pending requests at 3.

Figure 5.1: Out-of-band pairing/enrollment sequence for a first-contact sender

sequenceDiagram participant Dana as Dana (Telegram) participant Gate as Access-Control Gate participant Store as Pending/Approved Store participant Op as Operator (trusted console) participant LLM as Agent / LLM Dana->>Gate: First inbound message Gate->>Store: Look up (telegram, user_id) Store-->>Gate: No approved contact Gate->>Store: Record pending request + code "K7M9-P2QX" Gate-->>Dana: Reply with code only (message never reaches LLM) Note over Op: Out-of-band, separate trust domain Op->>Store: openclaw pairing approve telegram K7M9-P2QX Store-->>Op: Pending -> Approved (allowlist entry written) Dana->>Gate: Next message Gate->>Store: Look up (telegram, user_id) Store-->>Gate: Approved contact + capability tier Gate->>LLM: Forward message (subject to tier)

Visual animation — coming soon

Binding, Revocation, and Re-Pairing

The result of pairing is data: an entry linking a channel identity to an approved-contact record. Pending requests and approved contacts are typically stored as separate JSON files under a credentials directory such as ~/.openclaw/credentials/. Because the binding is data, its lifecycle is easy to manage. Revocation — needed when a device is lost, an account is compromised, or a relationship ends — is simply removing the approved-contact entry. Re-pairing is the same enrollment flow run again. The critical property is that revocation is fast and local: you delete one entry and the next message from that identity is an unknown stranger again.

Figure 5.2: Binding lifecycle — pending, approved, revoked, and re-pairing state transitions

stateDiagram-v2 [*] --> Unknown Unknown --> Pending: First inbound message (code issued) Pending --> Approved: Operator approves out-of-band Pending --> Unknown: Code expires (~1h) or rate-limit drop Approved --> Revoked: Delete approved-contact record Revoked --> Unknown: Identity severed Unknown --> Pending: Re-pairing (same flow again) Approved --> [*]
Post-Reading Check — Pairing and Enrollment

1. What makes out-of-band approval the source of security in a pairing flow?

The pairing code is encrypted before it is sent to the sender
The sender can request a code, but only an operator on a separate trusted console can approve it
The code is long enough that it cannot be guessed within an hour
The assistant verifies the sender's webhook signature before issuing the code

2. When an unknown contact first messages the bot under a pairing policy, what happens to that first message?

It is forwarded to the model, which decides whether to respond
It is answered normally but flagged for later review
It never reaches the LLM; the assistant replies only with a pairing code and blocks the sender
It is queued until the code expires, then delivered

3. Which hardening measure prevents an attacker from flooding the endpoint with pairing requests?

A cap on pending pairing requests per channel, with extra attempts silently ignored
Requiring the sender to solve a CAPTCHA before a code is issued
Making codes case-sensitive
Encrypting the pending-request store on disk

4. What is the difference between DM pairing and node pairing?

DM pairing is temporary; node pairing is permanent
DM pairing controls which people may message the bot; node pairing controls which physical devices may connect to the gateway
DM pairing uses codes; node pairing uses passwords
DM pairing is for owners; node pairing is for strangers

5. Why is revoking a paired contact described as "fast and local"?

Because the assistant automatically re-verifies every contact each hour
Because the binding is stored data, so you delete one approved-contact entry and the next message is treated as an unknown stranger
Because revocation waits for a short-lived token to expire on its own
Because revocation must be pushed out to a fleet of devices at once
Pre-Reading Check — Allowlisting Who Can Talk to the Agent

1. Why is a default-deny allowlist considered to "fail safe" while a default-allow denylist "fails open"?

Allowlists are faster to evaluate than denylists
With an allowlist an overlooked case stays blocked; with a denylist an overlooked case gets through
Denylists require encryption while allowlists do not
Allowlists automatically expire, so stale rules never accumulate

2. Why must the access-control gate run before the message reaches the model?

Because the model is slower than the gate and would create a bottleneck
Because terminating unallowed messages at the gate denies a prompt-injection payload its audience
Because the model cannot read channel identifiers
Because webhook signatures can only be verified after the model responds

3. Which DM policy is the recommended secure default?

open — anyone may message the bot
disabled — the channel accepts no DMs at all
pairing — unknown senders get a code and are blocked until manually approved
allowFrom: ["*"] — a wildcard allowlist

4. What do capability tiers add on top of a plain allowlist?

They separate being allowed to talk to the agent from being allowed to ask for anything
They encrypt each message according to the sender's rank
They replace pairing so no out-of-band approval is needed
They let denylisted users through if they are trusted enough

5. Why do rate limiting and abuse controls apply inside the allowlist, not just at the pairing gate?

Because allowlisted users are exempt from all other checks
Because even an approved sender can be abusive, maliciously or because their account was compromised
Because the LLM charges per token regardless of sender
Because pairing codes expire and must be re-issued periodically

Allowlisting Who Can Talk to the Agent

Key Points

The two postures differ sharply in how they fail:

PostureRuleFailure modeAnalogy
Allowlist (default-deny)Everything is denied; you enumerate the few things that are allowedFails safe — an overlooked case stays blockedA guest list at a private event: not on the list, not in
Denylist (default-allow)Everything is allowed; you enumerate the things that are deniedFails open — an overlooked case gets throughA bouncer with a photo of known troublemakers: any new face walks in

Each channel ships with a configurable DM policy, spanning secure to reckless:

DM policyBehaviorRecommendation
pairingUnknown senders get a code and are blocked until manually approvedSecure default
allowlistOnly pre-approved identities may message the botSafe (no self-enrollment)
openAnyone may message (equivalent to allowFrom: ["*"])Explicitly not recommended
disabledThe channel accepts no DMsSafe (channel off)

The Access-Control Gate

The gate that enforces default-deny runs before an inbound message reaches the agent. It asks: Is this sender in the allowlist? If this is a first-time DM, has pairing been approved? If either check fails, the message stops there. This ordering connects directly to Chapter 2: prompt injection can only hijack the model if untrusted text reaches it, so terminating unallowed messages at the gate denies the injection payload an audience.

Figure 5.3: Default-deny decision path for an inbound message

flowchart TD A[Inbound message] --> B{Sender in allowlist?} B -->|Yes| E{Within rate limit and tier?} B -->|No| C{First-time DM and DM policy = pairing?} C -->|Yes| D[Issue pairing code, record pending, block] C -->|No| F[Deny: drop message] E -->|Yes| G[Forward to agent / LLM] E -->|No| F D --> F

A canonical code implementation of default-deny has three parts: a base controller that verifies an authorization check actually ran on every request; a base policy that denies all actions by default; and model-specific policies that inherit from the base and selectively re-enable only the permitted actions. The design property: every granted permission is intentional rather than accidental — a developer who forgets to write a policy gets a locked door, not an open one.

Per-User and Per-Group Capability Tiers

Being allowed to talk is different from being allowed to ask for anything. Authorization should be role-based (RBAC), determined by task and responsibility rather than per individual:

TierWhoWhat they may askExample
Paired-but-untrustedNewly enrolled contactsRead-only / informational queries"What's on the public calendar?"
StandardTrusted collaboratorsRoutine actions"Draft a reply to this email"
Owner/adminThe operatorConfiguration changes, sensitive tools"Rotate the API key," "run this shell command"

The channel identity resolves to a principal; the principal's tier decides what they may ask — cleanly separating authentication (who is this?) from authorization (what may they do?). For the most sensitive actions, tiers can be reinforced with human-in-the-loop approval. Auth0 implements this with Client-Initiated Backchannel Authentication (CIBA) plus Rich Authorization Requests (RAR): the agent sends a request, the user is notified on a separate trusted device to approve a concretely described action — e.g., "Approve payment of $50.00 to ExampleCorp" — before tokens are issued. This is the same out-of-band principle from pairing, now applied to individual high-impact actions.

Visual animation — coming soon

Rate Limiting and Abuse Controls for Allowed Senders

Allowlisting stops strangers, but an approved sender can still be abusive — maliciously or because their account was compromised. Rate limiting therefore applies inside the allowlist too: cap requests per user per window, throttle expensive tool calls, and alert on anomalous bursts. Least-privilege operational practices reinforce this over time: separate administrative from standard accounts, use just-in-time (JIT) access — temporary, time-limited elevated credentials that auto-revoke to prevent "privilege creep" — and run regular access reviews to close inactive accounts.

Post-Reading Check — Allowlisting Who Can Talk to the Agent

1. Why is a default-deny allowlist considered to "fail safe" while a default-allow denylist "fails open"?

Allowlists are faster to evaluate than denylists
With an allowlist an overlooked case stays blocked; with a denylist an overlooked case gets through
Denylists require encryption while allowlists do not
Allowlists automatically expire, so stale rules never accumulate

2. Why must the access-control gate run before the message reaches the model?

Because the model is slower than the gate and would create a bottleneck
Because terminating unallowed messages at the gate denies a prompt-injection payload its audience
Because the model cannot read channel identifiers
Because webhook signatures can only be verified after the model responds

3. Which DM policy is the recommended secure default?

open — anyone may message the bot
disabled — the channel accepts no DMs at all
pairing — unknown senders get a code and are blocked until manually approved
allowFrom: ["*"] — a wildcard allowlist

4. What do capability tiers add on top of a plain allowlist?

They separate being allowed to talk to the agent from being allowed to ask for anything
They encrypt each message according to the sender's rank
They replace pairing so no out-of-band approval is needed
They let denylisted users through if they are trusted enough

5. Why do rate limiting and abuse controls apply inside the allowlist, not just at the pairing gate?

Because allowlisted users are exempt from all other checks
Because even an approved sender can be abusive, maliciously or because their account was compromised
Because the LLM charges per token regardless of sender
Because pairing codes expire and must be re-issued periodically
Pre-Reading Check — Per-Channel Identity Mapping

1. Why should permissions attach to an internal principal rather than to raw channel handles?

Raw handles are longer and harder to store
Otherwise the same person appears as unrelated strangers across channels, with multiple places to revoke and chances to miss one
Channel handles cannot be used in authorization decisions at all
Principals are required by OAuth 2.0 but handles are not

2. What is the process that links multiple external identity-provider profiles to one internal user?

Account linking
Token introspection
Session pinning
Credential stuffing

3. Why should identity mapping key off a stable, opaque identifier rather than a username or display name?

Opaque identifiers are shorter and save storage
Usernames and display names can change or be reassigned to a different person, while a stable opaque ID does not
Display names are always encrypted and cannot be read
Opaque identifiers are the only values a webhook can carry

4. How does storing the channel type alongside the external ID help prevent identity collision?

It compresses the identifier so collisions are impossible
It disambiguates identifiers so (slack, U04AB12CD) and (telegram, 483920174) can never be confused
It forces every channel to use the same ID format
It lets display names be used safely as the mapping key

5. What is the decisive advantage of per-channel mapping when one of a user's channels is compromised?

All of the user's channels are automatically frozen for safety
You can revoke the single compromised channel entry without severing the human's other links
The compromised channel is migrated to a new provider automatically
The principal is deleted and must be re-created from scratch

Per-Channel Identity Mapping

Key Points

If you attach permissions to raw handles, the same person shows up as three unrelated strangers across three channels, a permission granted on Slack does not follow them to Telegram, and you have three places to revoke access with three chances to miss one. Mapping every channel identity to a single principal gives you one place to reason about who someone is and what they may do. The standard technique is account linking: store the external channel identifier plus its provider/channel type as attributes of the internal principal, and resolve inbound messages by looking up (channel, external_id) -> principal.

ChannelExternal identifierInternal principalTier
SlackU04AB12CDdanastandard
Telegram483920174danastandard
Emaildana@example.comdanastandard

An inbound Telegram message resolves (telegram, 483920174) -> dana, and dana's tier governs the request regardless of which channel it arrived on.

Figure 5.4: Per-channel identity mapping — many channel handles resolve to one principal and tier

flowchart LR S["Slack: U04AB12CD"] --> P["Principal: dana"] T["Telegram: 483920174"] --> P E["Email: dana@example.com"] --> P P --> R["Tier: standard"] R --> Z["Authorization decision (what may they ask?)"]

Visual animation — coming soon

Key Off Stable, Opaque Identifiers

The most common mistake is keying off a mutable value. Usernames, display names, and handles can change — and in some systems can be reassigned to a different person after an account is deleted. SAML and OIDC systems use a NameID: "a unique, pseudo-random string that should not change for the user over time," assigned by the IdP. The rule for a bot: pin the mapping to the immutable Slack user ID or Telegram user ID — not the username, display name, or handle. The analogy is a passport number versus a name: two people can share a name and one person can change theirs, but the passport number is a stable, unique key issued by an authority. Where multiple providers feed one system, a normalization layer (e.g., Amazon Cognito with a PreTokenGeneration Lambda mapping IdP groups into user-pool groups) harmonizes identities into a common shape.

Preventing Collision, Confusion, and Scoping Memory

Two dangers arise with multiple channels. Identity collision — two external identities resolving to the same principal, or one identity reused for two humans — is prevented by keying off stable opaque IDs and storing the channel type alongside the external ID, so (slack, U04AB12CD) and (telegram, 483920174) are always disambiguated. Cross-channel confusion — authority leaking between channels — is contained by attaching permissions to the principal while still respecting where a request came from; an owner-tier action from a low-assurance channel (easily spoofed email) may warrant step-up confirmation on a higher-assurance channel.

Because the mapping is data, revocation is a targeted edit: remove a role assignment (Slack models this with admin.roles.addAssignments / removeAssignments / listAssignments), delete the approved-contact record to sever a channel identity, or apply just-in-time grants that auto-expire. The decisive advantage: if Dana's Telegram account is stolen, you delete (telegram, 483920174) and her Slack and email access continue uninterrupted. Finally, scoping applies to memory and context too — each principal has its own conversational memory and data scope, preventing cross-user leakage (a theme revisited in Chapter 10's confused-deputy problem).

Post-Reading Check — Per-Channel Identity Mapping

1. Why should permissions attach to an internal principal rather than to raw channel handles?

Raw handles are longer and harder to store
Otherwise the same person appears as unrelated strangers across channels, with multiple places to revoke and chances to miss one
Channel handles cannot be used in authorization decisions at all
Principals are required by OAuth 2.0 but handles are not

2. What is the process that links multiple external identity-provider profiles to one internal user?

Account linking
Token introspection
Session pinning
Credential stuffing

3. Why should identity mapping key off a stable, opaque identifier rather than a username or display name?

Opaque identifiers are shorter and save storage
Usernames and display names can change or be reassigned to a different person, while a stable opaque ID does not
Display names are always encrypted and cannot be read
Opaque identifiers are the only values a webhook can carry

4. How does storing the channel type alongside the external ID help prevent identity collision?

It compresses the identifier so collisions are impossible
It disambiguates identifiers so (slack, U04AB12CD) and (telegram, 483920174) can never be confused
It forces every channel to use the same ID format
It lets display names be used safely as the mapping key

5. What is the decisive advantage of per-channel mapping when one of a user's channels is compromised?

All of the user's channels are automatically frozen for safety
You can revoke the single compromised channel entry without severing the human's other links
The compromised channel is migrated to a new provider automatically
The principal is deleted and must be re-created from scratch

Your Progress

Answer Explanations