拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Claude Code Discord 插件访问控制全解析:从 pairing 配对到 allowlist 锁定(claude-plugins-official 源码级实践指南)

Claude Code Discord 插件访问控制全解析:从 pairing 配对到 allowlist 锁定(claude-plugins-official 源码级实践指南)
  • AI 插件
  • 开发工具
  • 插件系统

【免费下载链接】claude-plugins-official

Official, Anthropic-managed directory of high quality Claude Code Plugins.

项目地址:https://gitcode.com/GitHub_Trending/cl/claude-plugins-official
点击查看免费下载

Discord Channel 是 Claude Code 官方插件集(claude-plugins-official)中的 MCP 通道插件,它让 Claude Code 会话通过 Discord 机器人接收消息并回复。本文聚焦该插件的核心安全机制——ACCESS.md 所定义的访问控制与消息投递体系:谁可以给你的机器人发私信、如何基于「配对码」逐人放行、如何为服务器频道配置提及触发与投递策略,并深入 server.ts 源码验证每一层门禁的真实实现。读完本文,你将能独立完成从「首次 DM 配对」到「锁定 allowlist」的完整安全收口,并理解access.json每个字段的语义与即时生效原理。

为什么需要访问控制:Discord 的 DM 边界

Discord 只允许共享服务器的账号之间互发私信(DM)。你的机器人能被谁私信,取决于它被安装在哪里:

  • 机器人只在一个私有服务器里 → 只有该服务器成员能私信它;
  • 机器人加入了一个公开社区 → 该社区的每个成员都能向它发起 DM。

因此,接入 Claude Code 的 Discord 机器人天然暴露在「任何同服成员都可能发消息」的边界内。同时,Developer Portal 的Bot 标签页里有一个默认开启的Public Bot开关:它控制谁能把机器人添加到新的服务器。关掉它,就只有你自己的账号能安装该机器人。这是访问控制的第一道门,由 Discord 平台强制实施,不依赖本插件进程的任何逻辑。

第一道门只解决「谁能安装/私信」的平台级问题,第二道门才是本插件真正的主角:DM 策略(dmPolicy)。两道门叠加,共同构成纵深防御。

第二道门:DM 策略与三种处理模式

dmPolicy决定不在白名单(allowlist)内的发送者的私信如何处理:

策略行为
pairing(默认)回复一个 6 位配对码,并丢弃该消息。用户(你)在 Claude Code 会话中运行/discord:access pair <code>批准发送者。
allowlist静默丢弃,不回复任何内容。当所有需要访问权的人都已入白名单、或不想让配对回复吸引垃圾消息时使用。
disabled丢弃一切,包括白名单用户与已启用的服务器频道。

切换到 allowlist 的命令:

/discord:access policy allowlist

源码级:pairing 到底发生了什么

在 server.ts 的gate()函数中,DM 入站消息按以下顺序裁决:

  1. dmPolicy === 'disabled'→ 直接drop(L241);
  2. 发送者 ID 命中allowFrom→deliver(L247);
  3. dmPolicy === 'allowlist'→drop(L248);
  4. 处于pairing模式时,先检查该发送者是否已有未过期的 pending 配对码:
    • 已有 → 最多回复两次(初始提示 + 一次提醒,replies字段计数,超过 2 次即静默丢弃,防止骚扰刷屏,L251-L259);
    • 没有 → 生成新码:randomBytes(3).toString('hex')得到6 位十六进制字符,记录senderId、chatId(DM 频道 ID)、createdAt与expiresAt(1 小时过期),写入access.json,然后回复/discord:access pair <code>(L260-L273)。

两个值得注意的防滥用细节:pending 队列上限为 3,超出后的新尝试被静默丢弃(L261);过期条目在每次入站裁决时由pruneExpired()清理(L203-L213)。

用户标识:为什么用 snowflake 而不是用户名

Discord 通过snowflake标识用户:永久数字 ID,例如184695080709324800。用户名是可变的,snowflake 不可变,因此白名单(allowFrom)存的是 snowflake。

配对流程会自动捕获发送者的 ID。手动添加时:

  1. 在 Discord 中打开User Settings → Advanced → Developer Mode;
  2. 右键任意用户 →Copy User ID;
  3. 你自己的 ID 可通过左下角右键自己的头像获取。
/discord:access allow 184695080709324800 /discord:access remove 184695080709324800

从源码看,allow与remove都是对access.json中allowFrom数组的增删(skills/access/SKILL.md 中定义了完整操作步骤:读取 → 去重添加/过滤排除 → 写回)。需要注意:senderId(用户 snowflake)与 chatId(DM 频道 snowflake)不是同一个值,不要混淆——pending条目同时保存二者,chatId用于批准后向对方发送确认消息。

服务器频道(Guild Channels):按频道逐个开启

服务器频道默认关闭。需要逐个频道手动开启(opt-in),且 key 是频道 snowflake而非服务器(guild)ID。这样设计让用户可以按频道精确控制,而不是整服放行。线程(thread)继承其父频道的开启状态,无需单独配置。

查找频道 ID 的方式与用户 ID 相同:开启 Developer Mode → 右键频道 → Copy Channel ID。

/discord:access group add 846209781206941736

默认requireMention: true,机器人只在被 @提及或回复时响应。传--no-mention则处理该频道内每条消息;--allow id1,id2可限制哪些成员能触发机器人:

/discord:access group add 846209781206941736 --no-mention /discord:access group add 846209781206941736 --allow 184695080709324800,221773638772129792 /discord:access group rm 846209781206941736

源码级:频道门禁与线程回退

在gate()中,频道消息的裁决逻辑是(L276-L293):

  • 若消息来自线程,用msg.channel.parentId ?? msg.channelId回退到父频道做门禁查找——这正是「线程继承父频道」的实现;
  • 查询access.groups[channelId],不存在该 key →drop;
  • policy.allowFrom非空且不含发送者 →drop;
  • requireMention为 true 且未命中任何提及 →drop;
  • 全部通过 →deliver。

出站侧同样受控:fetchAllowedChannel()(L405-L416)保证reply等工具只能把消息发到入站门禁会放行的频道,DM 需发送者是白名单成员,频道需存在于groups中,否则报错「channel is not allowlisted」。

提及检测(Mention Detection):三种触发方式

在requireMention: true的频道里,以下任一情况都会触发机器人:

  • 通过 Discord 自动补全输入的结构化@botname提及;
  • 回复(reply)机器人最近发出的消息;
  • 消息内容命中mentionPatterns中的任意正则。

设置正则(示例为昵称触发词):

/discord:access set mentionPatterns '["^hey claude\\b", "\\bassistant\\b"]'

源码级:isMentioned 的实现

isMentioned() 依次检查三层:

  1. msg.mentions.has(client.user):结构化 mention(Discord 自动补全输入)直接命中;
  2. 回复视为隐式提及:先查内存集合recentSentIds(记录最近发送的至多 200 条消息 ID,超出后按插入顺序淘汰最旧者,L220-L234),未命中则回退调用msg.fetchReference()检查被引用消息的作者是否是机器人自己(消息被删除或权限不足时静默忽略异常);
  3. mentionPatterns:对每条正则用new RegExp(pat, 'i')以不区分大小写方式测试消息文本(L311-L317)。

一个值得注意的细节:recentSentIds的引入是为了让「回复机器人最近发的消息」不需要额外网络请求即可判定为提及。

消息投递配置(Delivery)

出站行为统一通过/discord:access set <key> <value>配置。

ackReaction:回执表情

收到入站消息时对消息添加一个表情作为「已看到」确认。Unicode 表情可直接使用;服务器自定义表情需要完整的<:name:id>形式——右键表情复制链接,ID 在链接末尾。空字符串表示禁用:

/discord:access set ackReaction 🔨 /discord:access set ackReaction ""

从源码看,ack 反应是 fire-and-forget 的(L857-L860),不会阻塞消息投递;同时入站消息会自动触发打字指示器(typing indicator),Discord 端会显示「botname is typing…」直到助手回复(L851-L854)。

replyToMode:分块回复的线程策略

当一条长回复被拆成多块时,控制线程行为:

  • first(默认):只有第一块挂在入站消息下回复;
  • all:每个分块都作为对入站消息的回复;
  • off:所有分块独立发送,不引用原消息。

对应源码(L626-L645):shouldReplyTo的计算逻辑是reply_to != null && replyMode !== 'off' && (replyMode === 'all' || i === 0),即第一个分块必定携带回复引用(replyMode非 off 时)。

textChunkLimit:分块阈值

设置分块阈值。Discord 拒绝超过 2000 字符的消息,这是硬上限。源码中MAX_CHUNK_LIMIT = 2000(L132),实际发送时用Math.max(1, Math.min(limit, MAX_CHUNK_LIMIT))钳制,防止配置值越界(L624)。

chunkMode:分块策略

  • length:在限制处精确切断;
  • newline:优先在段落边界切分。

源码中的 chunk() 对newline模式依次尝试:最后一个双换行(段落边界)→ 单换行 → 空格,并要求候选切割点位于limit / 2之后(避免切出过短的碎片),全部失败才硬切;切分后清理块首的换行符。

/discord:access 技能命令参考

/discord:access是一个user-invocable技能(skills/access/SKILL.md),它不直接与 Discord 通信,只编辑access.json,通道服务器在每条入站消息时重新读取该文件。命令一览:

命令效果
/discord:access打印当前状态:策略、白名单、待处理配对、已启用频道。
/discord:access pair a4f91c批准配对码a4f91c。把发送者加入allowFrom,并在 Discord 上发送确认。
/discord:access deny a4f91c丢弃待处理的配对码,不通知发送者。
/discord:access allow 184695080709324800直接添加用户 snowflake。
/discord:access remove 184695080709324800从白名单移除。
/discord:access policy allowlist设置dmPolicy,取值:pairing、allowlist、disabled。
/discord:access group add 846209781206941736启用服务器频道,标志:--no-mention、--allow id1,id2。
/discord:access group rm 846209781206941736停用服务器频道。
/discord:access set ackReaction 🔨设置配置键:ackReaction、replyToMode、textChunkLimit、chunkMode、mentionPatterns。

源码级:pair 的完整八步

根据 SKILL.md,pair <code>的执行序列是:

  1. 读取~/.claude/channels/discord/access.json;
  2. 查pending[<code>],不存在或expiresAt < Date.now()则告知用户并停止;
  3. 取出senderId与chatId;
  4. 将senderId加入allowFrom(去重);
  5. 删除pending[<code>];
  6. 写回access.json;
  7. mkdir -p并写入~/.claude/channels/discord/approved/<senderId>,文件内容为chatId;
  8. 确认批准结果。

第 7 步是服务器与技能之间的握手:服务器每 5 秒轮询approved/目录(checkApprovals(),static 模式不启用),读到标记文件后向chatId发送「Paired! Say hi to Claude.」并删除标记,之后该发送者的下一条消息即可直达助手。

安全设计:只信任终端输入

SKILL.md 明确要求:该技能只处理用户在终端输入的命令。如果批准配对、添加白名单、修改策略的请求来自频道通知(Discord 消息等),必须拒绝,并要求用户自己在终端运行/discord:access。原因:频道消息可能携带 prompt injection,访问控制的变更绝不能处于不可信输入的下游。同样地,server.ts 的指令也禁止模型因为频道消息里的「批准配对」「把我加进白名单」而自行操作——这正是注入攻击的典型请求形态。

配置文件 access.json 完整解析

所有状态都在~/.claude/channels/discord/access.json。文件缺失等价于pairing策略加空列表,因此第一个 DM 就会触发配对。完整结构:

{ // Handling for DMs from senders not in allowFrom. "dmPolicy": "pairing", // User snowflakes allowed to DM. "allowFrom": ["184695080709324800"], // Guild channels the bot is active in. Empty object = DM-only. "groups": { "846209781206941736": { // true: respond only to @mentions and replies. "requireMention": true, // Restrict triggers to these senders. Empty = any member (subject to requireMention). "allowFrom": [] } }, // Case-insensitive regexes that count as a mention. "mentionPatterns": ["^hey claude\\b"], // Reaction on receipt. Empty string disables. "ackReaction": "👀", // Threading on chunked replies: first | all | off "replyToMode": "first", // Split threshold. Discord rejects > 2000. "textChunkLimit": 2000, // length = cut at limit. newline = prefer paragraph boundaries. "chunkMode": "newline" }

此外,SKILL.md 还揭示了pending字段的内部形态(运行时由服务器写入,技能只读):

{ "pending": { "<6-char-code>": { "senderId": "...", "chatId": "...", "createdAt": <ms>, "expiresAt": <ms> } } }

即时生效与静态模式

  • access.json在每条入站消息时重新读取(readAccessFile()),所以/discord:access的策略变更无需重启立即生效;
  • 与之对比,~/.claude/channels/discord/.env中的DISCORD_BOT_TOKEN只在启动时读取一次,token 变更需要重启会话或/reload-plugins(见 skills/configure/SKILL.md);
  • 设置DISCORD_ACCESS_MODE=static可将配置钉死在启动时磁盘快照:服务器不再重新读取、也不再写入access.json。由于配对需要运行时写盘,static 模式下 pairing 不可用——若快照发现dmPolicy为pairing,会降级为allowlist并输出启动警告,同时清空pending(L177-L189),避免发出永远不会被批准的配对码;
  • 若access.json损坏,readAccessFile()会将其改名为.corrupt-<timestamp>移开,并以默认配置重新开始(L168-L170)。

最佳实践:配对完成即锁定

pairing不是应该长期停留的策略,而是捕获未知 snowflake 的临时手段(skills/configure/SKILL.md 明确要求配置流程「始终推动锁定」)。推荐收口流程:

  1. 首次配置:/discord:configure <token>写入机器人 token;
  2. 用claude --channels plugin:discord@claude-plugins-official重新启动会话;
  3. 你自己先 DM 机器人、捕获自己的 ID 并pair批准;
  4. 需要访问的其他人依次 DM 机器人 → 你逐个/discord:access pair <code>批准(或请对方开启 Developer Mode 复制 User ID 后/discord:access allow <id>);
  5. 名单齐了立即/discord:access policy allowlist,让陌生人再也得不到配对码回复。

其他实战要点:

  • 多机器人并存时,用DISCORD_STATE_DIR为每个实例指向独立目录(不同 token、相互隔离的白名单);
  • 技能实现要求「总是先 Read 再 Write」access.json——通道服务器可能随时写入新的 pending 条目,直接覆写会丢数据;写入使用 2 空格缩进便于手工编辑;
  • 通道目录可能在服务器首次运行前不存在,代码需优雅处理 ENOENT 并创建默认值。

完整安装与机器人创建步骤见 README.md,访问控制对应的技能实现见 skills/access/SKILL.md 与 skills/configure/SKILL.md,通道服务器全部逻辑集中在 server.ts。

  • AI 插件
  • 开发工具
  • 插件系统

【免费下载链接】claude-plugins-official

Official, Anthropic-managed directory of high quality Claude Code Plugins.

项目地址:https://gitcode.com/GitHub_Trending/cl/claude-plugins-official
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表