Channels/Allowlists — Access Control Engine

Pattern-Based Sender Filtering & Command Authorization

The Allowlists sub-module controls who can interact with the OpenClaw agent. It compiles sender identity patterns into fast lookup sets, resolves DM and group allowFrom sources, gates slash commands behind access groups, and enforces mention requirements in group chats.

4
Core Files
10+
Match Sources
3
Gating Modes
TypeScript
Language

Allowlists Command Gating Mention Gating Access Groups

📚 What is this & why does it matter?

Without access control, any user on any platform could invoke the agent and run arbitrary commands. The allowlists sub-module is the gatekeeper — it decides, for every inbound message, whether the sender is authorized and what level of access they have.

💡 Real-world analogy

Think of a nightclub with a guest list. The bouncer (allowlist engine) has a compiled list of VIP names. When someone arrives, the bouncer checks their ID (sender identity candidates) against the list. A wildcard * entry means “everyone gets in.” For the VIP lounge (slash commands), there’s an additional access check (command gating). In group settings, the bouncer also checks if the guest called out the club owner by name (mention gating).

Architecture

flowchart TD
    subgraph Input["Inbound Message"]
        SENDER["Sender Identity"]
        CMD["Command Detected?"]
        MENTION["Was @mentioned?"]
    end
    subgraph Allowlist["Allowlist Engine"]
        COMPILE["compileAllowlist()"]
        RESOLVE["resolveCompiledAllowlistMatch()"]
        CANDIDATES["Identity Candidates"]
    end
    subgraph AllowFrom["AllowFrom Sources"]
        DMMERGE["mergeDmAllowFromSources()"]
        GROUPFROM["resolveGroupAllowFromSources()"]
    end
    subgraph Gating["Command & Mention Gating"]
        CMDGATE["resolveControlCommandGate()"]
        MGATE["resolveMentionGating()"]
    end
    subgraph Result["Access Decision"]
        ALLOW["Allowed"]
        DENY["Denied / Blocked"]
    end

    SENDER --> CANDIDATES
    CANDIDATES --> COMPILE
    COMPILE --> RESOLVE
    RESOLVE --> ALLOW
    RESOLVE --> DENY
    DMMERGE --> COMPILE
    GROUPFROM --> COMPILE
    CMD --> CMDGATE
    CMDGATE --> ALLOW
    CMDGATE --> DENY
    MENTION --> MGATE
    MGATE --> ALLOW
    MGATE --> DENY

Allowlist Matching Pipeline

The core matching function accepts a compiled allowlist and an ordered array of identity candidates. Each candidate has a value (the identifier string) and a source (how it was derived). The engine checks candidates in order; the first match wins.

Match Sources

SourceDescriptionExample
wildcardUniversal match via * entryallowFrom: ["*"]
idPlatform-specific numeric/string ID"123456789"
usernamePlatform username/handle"johndoe"
nameDisplay name match"John Doe"
tagPlatform tag (e.g., Discord discriminator)"johndoe#1234"
prefixed-idChannel-prefixed ID"telegram:123456"
prefixed-userChannel-prefixed username"slack:johndoe"
prefixed-nameChannel-prefixed display name"discord:John"
slugNormalized slug form"john-doe"
localpartEmail local part"john" from john@example.com

Compilation

export function compileAllowlist(entries: ReadonlyArray<string>): CompiledAllowlist {
  const set = new Set(entries.filter(Boolean));
  return {
    set,
    wildcard: set.has("*"),
  };
}

AllowFrom Source Resolution

DM and group contexts have separate allowFrom sources that are merged before compilation:

flowchart LR
    subgraph DM["DM Context"]
        AF["allowFrom config"]
        SAF["storeAllowFrom"]
        DMP["dmPolicy"]
    end
    subgraph Group["Group Context"]
        GAF["groupAllowFrom"]
        FAF["fallbackToAllowFrom"]
    end
    DM --> MERGE["mergeDmAllowFromSources()"]
    Group --> GRESOLVE["resolveGroupAllowFromSources()"]
    MERGE --> COMPILED["CompiledAllowlist"]
    GRESOLVE --> COMPILED
export function mergeDmAllowFromSources(params: {
  allowFrom?: Array<string | number>;
  storeAllowFrom?: Array<string | number>;
  dmPolicy?: string;
}): string[] {
  const storeEntries = params.dmPolicy === "allowlist" ? [] : (params.storeAllowFrom ?? []);
  return [...(params.allowFrom ?? []), ...storeEntries]
    .map((value) => String(value).trim())
    .filter(Boolean);
}

Command Gating

Slash commands can be restricted to authorized users only. The gating system supports three modes when access groups are disabled:

ModeBehavior
allowAll users can use slash commands (default)
denyNo users can use slash commands
configuredOnly explicitly configured authorizers can use commands
export function resolveControlCommandGate(params: {
  useAccessGroups: boolean;
  authorizers: CommandAuthorizer[];
  allowTextCommands: boolean;
  hasControlCommand: boolean;
  modeWhenAccessGroupsOff?: CommandGatingModeWhenAccessGroupsOff;
}): { commandAuthorized: boolean; shouldBlock: boolean }

Mention Gating

In group chats, the agent can require an @mention before processing. The system handles implicit mentions, bypass rules (authorized control commands), and detection capability flags.

export function resolveMentionGating(params: MentionGateParams): MentionGateResult {
  const implicit = params.implicitMention === true;
  const bypass = params.shouldBypassMention === true;
  const effectiveWasMentioned = params.wasMentioned || implicit || bypass;
  const shouldSkip = params.requireMention && params.canDetectMention && !effectiveWasMentioned;
  return { effectiveWasMentioned, shouldSkip };
}

Focus Deep-Dives

🔎 Allowlist Matching

Core matching engine: compiled sets, candidate resolution, match source tracking, and wildcard semantics.

🔒 Command Gating

Slash command authorization with access groups, dual-text control, and configurable deny modes.

👥 AllowFrom Rules

DM/group source merging, store-backed allowlists, fallback strategies, and sender ID validation.