跳转至

通道 Ingress

通道入口是入站通道事件的实验性访问控制边界。插件拥有平台事实和副作用;核心拥有通用策略:DM/群组允许列表、配对存储中的 DM 条目、路由门、命令门、事件授权、提及激活、脱敏诊断和准入。

在接收路径中使用 runtime.channel.inbound.ingress。从 openclaw/plugin-sdk/channel-ingress-runtime 导入身份和策略工具。

运行时解析器

在此示例中,cfg 是根 OpenClaw 配置,config 是已解析的通道账户配置。从插件的接收路径提供 runtime、normalizePlatformUserId、route、agentRoute、readStoreAllowFrom 以及消息事实。

import { defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";

const identity = defineStableChannelIngressIdentity({
  key: "platform-user-id",
  normalize: normalizePlatformUserId,
  sensitivity: "pii",
});

const result = await runtime.channel.inbound.ingress.resolve({
  channelId: "my-channel",
  accountId,
  identity,
  subject: { stableId: platformUserId },
  conversation: { kind: isGroup ? "group" : "direct", id: conversationId },
  contextBinding: {
    agentId: agentRoute.agentId,
    sessionKey: agentRoute.sessionKey,
    messageId,
    inboundEventKind: "user_request",
  },
  event: { kind: "message", authMode: "inbound", mayPair: !isGroup },
  policy: {
    dmPolicy: config.dmPolicy,
    groupPolicy: config.groupPolicy,
    groupAllowFromFallbackToAllowFrom: true,
  },
  allowFrom: config.allowFrom,
  groupAllowFrom: config.groupAllowFrom,
  accessGroups: cfg.accessGroups,
  route,
  readStoreAllowFrom,
  command: hasControlCommand ? { allowTextCommands: true, hasControlCommand } : undefined,
});

const ctx = runtime.channel.inbound.buildContext({
  // Pass the exact host result; do not rebuild participant evidence from
  // SenderId, From, session keys, routes, rooms, or message metadata.
  channelIngress: result,
  // ...normalized channel facts
});

不要预先计算有效允许列表、命令所有者或命令组。解析器会从原始允许列表、存储回调、路由描述符、访问组、策略和会话类型推导它们。

运行时公开 createResolver、resolve 和 resolveStable,其输入与独立 SDK 辅助函数相同。其解析器和 buildContext 共享同一个宿主实例和插件生命周期。请从同一运行时使用两者;另一个 Gateway 或替换插件无法兑现该结果。

独立的 resolveChannelMessageIngress、resolveStableChannelMessageIngress 和 createChannelIngressResolver 辅助函数保留 OpenClaw 2026.9.5 中记录的接收路径契约。在受信任且处于活动状态的通道插件的宿主管理回调中,它们会委托给该确切插件实例已注册的运行时,并在结果进入其 buildContext 时保留参与者归属。在注册期间创建的工厂会保留其创建实例;从另一个插件调用它不会借用该插件的权限。未限定调用仍然仅涉及策略。已退役实例以及过期或不匹配的交接无法附加受信任身份。新的接收路径应使用上述显式运行时方法;现有独立调用方仍受支持。

对于将进入宿主上下文的结果,请在通道路由所有者选择最终代理和会话之后进行解析。contextBinding 会将这些事实与最终宿主上下文消息 ID(在任何回复 ID 映射之后)以及入站事件类型一起冻结。将 contextBinding.nativeChannelId 设置为宿主上下文的 reply.nativeChannelId ?? conversation.nativeChannelId,即使它与会话的 id 相等。仅用于决策的检查可以省略绑定,但这样的结果不是有效的执行来源证明,并且不得作为 channelIngress 传递。当通道批量处理多条已准入消息时,请按源顺序传递它们的精确结果;最终确定的上下文消息 ID 标识最后一个源结果。

产品参与者身份

身份描述符可以提供 resolveParticipant(subject),仅当插件能够证明这些远程事实时,才返回 { domain, idKind, id }。该域属于远程服务:例如,Slack 工作区或应用范围的标识颁发者。它不是 OpenClaw 的本地 accountId。当服务赋予它们不同含义时,请保持用户 ID、机器人 ID 和代理身份彼此区分。名称和成功的 Gateway 配置文件查找不是身份证据。

将精确的解析器结果通过宿主注入的上下文构建器传递。宿主会私下携带产品事实,直到已接受输入被记录到会话参与者聚合中。原始产品身份不会被添加到公共诊断结果中。不合格的生产者会保留一个未解析的观察,而不是成为 Gateway 配置文件或虚构的远程主体。产品参与不会授予访问权限。

此产品路径独立于执行身份审计收集:它既不会启用该收集,也不会重用其 HMAC 引用或不透明诊断载体。审计的独立证据契约将在下文描述。

结果

捆绑插件应直接消费现代投影:

字段 含义
ingress 有序门决策和准入
senderAccess 仅发送方/会话授权
routeAccess 路由和路由发送方投影
commandAccess 命令授权;当没有命令门运行时为 requested: false
activationAccess 提及/激活结果

事件授权仍可在有序的 ingress.graph 和决定性的 ingress.reasonCode 上获取;不会发出单独的事件投影。

已弃用的第三方 SDK 辅助函数可能在内部重建旧版形状。新的捆绑接收路径不应将现代结果转换回本地 DTO。

当启用执行身份审计收集时,受信任的活跃原生插件是其远程参与者事实的权威进程内生产者。宿主注入的已注册运行时将解析器结果绑定到精确的插件记录和注册表生命周期纪元,然后在一次性上下文交接期间验证其完整可用的对话、路由、代理、会话、消息、事件和参与者范围。公共独立构建器仍为非权威,且不能签发参与者证据。 队列收集仅在每项贡献都对同一参与者具有有效证据时保留归属;混合、缺失、过期或未签发的证据为 unknown。载体是不透明的、有界的、一次性的,且仅用于诊断。 插件不能从调用方选择的发送者、账户、房间、路由、会话、消息或传输字段签发参与者证据。SDK 有意不暴露记录、纪元、所有者能力、参与者证据构造函数或证据复制器。结构相似的结果、过期记录、复用的结果或范围已更改的上下文不会获得宿主权限。

boundary-verified 表示核心已验证参与者事实以精确的记录、纪元、范围和一次性交接跨越了此受信任的活跃已注册原生插件边界。它并不意味着核心独立查询了远程服务;只有通道插件才能观察到该传输事实。

审计状态相互区分:

  • supported:权威入口解析器已运行。其精确结果可以产生存在的调用者,以及强制或仅归属的覆盖。
  • unknown:受支持的交接缺失、过期、伪造、复用、混合,或以其他方式未通过宿主验证。Unknown 绝不意味着允许。
  • unsupported:指定路径没有权威入口解析器集成,并显式传递 channelIngress: "unsupported"。Unsupported 绝不意味着允许,也不是不完整接线的捷径。

标识符认证

IdentifierAuthentication 将标识符声明评级为 verified、asserted、unverified 或 mutable,从最强到最弱。它仅是通道授权的输入。它不是主体、授权、关系或执行身份保证强度。特别是,verified 的标识符声明绝不会成为执行保证 boundary-verified 或 cryptographic。

从 openclaw/plugin-sdk/channel-ingress-runtime 导入该类型和 meetsIdentifierAuthentication(actual, minimum)。下游身份验证映射器应使用此布尔比较器,而不是维护自己的排名表。

这些含义是规范性的:

  • verified:拥有该标识符的受信任传输或会话边界已将此精确标识符绑定到此发送者。
  • asserted:受信任边界为发送者提供了担保,但未绑定此精确标识符。
  • unverified:标识符精确且稳定,但未证明所声明的所有权。
  • mutable:标识符是可更改或共享的别名,例如显示名称。

仅从由拥有边界控制的传输或会话元数据中声明 verified。发送者控制的内容、模型输入、普通消息上下文、路由元数据以及宿主准入载体的完整性均不能确立它。

内核保留所匹配的精确脱敏允许列表条目与主体标识符对。它通过取该精确对的较弱声明来组合条目声明和主体声明,然后将其与 minIdentifierAuthentication 进行比较。同种标识符仍保持区分,因此较弱的次要电子邮件不会削弱单独匹配的已验证电子邮件。

提供按消息 authentication 映射的主体必须声明它希望计入的每个字段。提供的映射中缺失的字段被视为 unverified,即使其身份描述符声明了更强的静态声明。具有静态强度的通道完全省略该映射。

从安全适配器的 resolveDmPolicy 结果中暴露 classifyEntryAuthentication: identityEntryAuthenticationClassifier(identity),并从 openclaw/plugin-sdk/channel-ingress-runtime 导入该辅助函数。它使用身份描述符的条目规范化器,并返回接受字段中最强的静态声明;如果没有字段接受该条目,则返回 undefined;通配符条目被排除。安全审计 统计仅依赖可变标识符的已配置 allowFrom 条目:当名称匹配被禁用时发出警告,并在启用时预览禁用后有多少条目将停止授权。发现包含计数和配置路径,而非原始条目;配对存储审批不在此检查范围内。符号 accessGroup: 引用单独解析成员资格,不计为可变标识符。

现有插件在弃用窗口期间保持源代码兼容:

已弃用字段 精确映射
dangerous: true authentication: "mutable"
dangerous: false 或省略 默认 authentication: "asserted"
mutableIdentifierMatching: "enabled" 最低 mutable
mutableIdentifierMatching: "disabled" 或省略 默认最低 asserted

显式的 authentication 或 minIdentifierAuthentication 优先。已弃用字段将保留至当前 Plugin SDK 主版本,并计划在捆绑插件和已知外部插件迁移后的下一个主版本中移除。

捆绑通道声明

捆绑通道使用共享身份声明的每个接收路径所支持的最强声明。这些是通道授权声明,而非执行身份保证:

渠道 标识符声明 权威传输或会话事实
Discord 网关用户 ID:verified;PluralKit 成员 ID:asserted;名称和标签:mutable Discord 在通过已认证的 bot-token 网关会话传递的事件中提供 author.id 或 user.id。PluralKit 成员 ID 来自其已认证的 API 响应,而不是 Discord 网关。
Google Chat sender.name:verified;邮箱:mutable Webhook 在消费 Google 拥有的事件主体之前,会验证 Google 的签名 token、签发者和已配置的受众。
IRC 服务器连接前缀和 user@host:asserted;基于 nick 的别名:mutable 所选 IRC 服务器为连接前缀提供担保,但通用传输本身无法证明账户所有权。
Mattermost 帖子用户 ID:verified;用户名:mutable 已认证的 Mattermost WebSocket 会发出服务器拥有的帖子事件,其 post.user_id 标识作者。
Microsoft Teams 发送者和会话 ID:asserted;发送者名称:mutable Bot Framework 会对连接器活动进行身份验证,但插件不会独立证明每个 ID 表示形式的精确所有权。
Slack 用户和工作区用户 ID:asserted;名称和 slug:mutable Slack 直接传递会绑定用户 ID,而中继模式会对中继对端进行身份验证,但不提供端到端的精确发送者证明。共享声明使用可辩护的通用声明。

如果接收路径无法支持其渠道共享的声明,请拆分该声明或提供较弱的逐消息声明。切勿从消息文本、路由或主机证据载体完整性推断出更强的声明。

访问组

accessGroup:<name> 条目保持脱敏。核心自行解析静态 message.senders 组,并且仅对需要平台查找的动态组调用 resolveAccessGroupMembership。缺失、不受支持和失败的组会失败关闭。

事件模式

authMode 含义
inbound 常规入站发送者门控
command 用于回调或作用域按钮的命令门控
origin-subject 操作者必须匹配原始消息主体
route-only 仅用于路由作用域内可信事件的路由门控
none 插件拥有的内部事件绕过共享身份验证

对于表情回应、按钮、回调和原生命令,使用 mayPair: false。

路由与激活

使用路由描述符表示房间、主题、guild、线程或嵌套路由策略:

route: {
  id: "room",
  allowed: roomAllowed,
  enabled: roomEnabled,
  senderPolicy: "replace",
  senderAllowFrom: roomAllowFrom,
  blockReason: "room_sender_not_allowlisted",
}

当插件具有多个可选路由描述符时,使用 channelIngressRoutes(...);它会过滤禁用的分支,同时保持路由事实通用,并按每个描述符的 precedence 排序。

提及门控是一种激活门控。提及未命中会返回 admission: "skip",以便回合内核不处理仅观察回合。大多数渠道应让激活位于发送者和命令门控之后。需要在发送者允许列表噪声之前静默未提及流量的公共聊天界面,可以在禁用文本命令绕过时选择 activation.order: "before-sender"。具有隐式激活的渠道(例如机器人线程中的回复)会使用 resolveChannelImplicitMentions(...) 解析 channels.defaults.implicitMentions 以及渠道和账户覆盖项,然后将结果作为 activation.implicitMentions 传递。投影的 activationAccess.shouldBypassMention 会报告命令或隐式激活何时绕过了显式提及。

脱敏

原始发送者值和原始允许列表条目仅作为解析器输入。它们不得出现在已解析状态、决策、诊断、快照或兼容性事实中。请使用不透明的主体 ID、条目 ID、路由 ID 和诊断 ID。

验证

pnpm test src/channels/message-access/message-access.test.ts src/plugin-sdk/channel-ingress-runtime.test.ts
pnpm plugin-sdk:api:diff --base "$(git merge-base origin/main HEAD)" --head HEAD

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw