目录

openclaw-weixin插件深度分析报告

一、概览

openclaw-weixinOpenClaw 框架的官方微信(Weixin)渠道插件,作用是将微信账号接入 OpenClaw 的 AI Agent 管道——让 AI 可以直接通过微信接收和发送消息。

插件采用 扫码登录 + 长轮询接收消息 的架构,这与市面上大量微信机器人方案的底层逻辑一致,但有一点不同:它走的是 Tencent 官方提供的 ilinkai.weixin.qq.com 接口,而非 PC 客户端协议或网页微信协议逆向。

技术栈:TypeScript / Node.js 22+,依赖极其精简(仅 qrcode-terminal + zod


二、架构分析

2.1 目录结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
src/
├── api/          # HTTP 客户端,封装所有 Weixin API 调用
├── auth/         # 扫码登录、账号存储、配对授权
├── cdn/          # CDN 媒体上传下载(AES-128 加解密)
├── media/        # 入站媒体处理(下载、格式转换)
├── messaging/    # 消息收发主流程
├── monitor/      # 长轮询监听循环
├── storage/      # 状态持久化(游标、账号文件)
├── util/         # 日志、脱敏、随机数
├── channel.ts    # OpenClaw Channel Plugin 实现
├── runtime.ts    # 插件运行时上下文
└── log-upload.ts # 日志上传 CLI 命令
index.ts          # 插件入口,注册到 OpenClaw
openclaw.plugin.json  # 插件 manifest

2.2 消息流向

入站(接收消息)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Weixin Server
    │  长轮询,最长等 35 秒
monitor.ts → getUpdates()
    │  提取 item_list
process-message.ts
    │  下载并解密媒体文件
    │  检查 slash command
    │  存储 contextToken(内存)
OpenClaw Core → AI Agent
    │  生成回复
channel.ts → sendText() / sendMedia()

出站(发送消息)

1
2
3
4
5
6
7
AI Agent 回复
channel.ts
    │  文本:直接 POST ilink/bot/sendmessage
    │  媒体:MD5 哈希 → 生成随机 AES Key → 加密 → 上传 CDN → 发送含 CDN 引用的消息
Weixin Server → 用户

2.3 服务端接口汇总

所有请求走 HTTPS,Content-Type: application/json,携带 Authorization: Bearer <token>

接口路径 用途
ilink/bot/getupdates 长轮询拉取新消息
ilink/bot/sendmessage 发送文字/媒体消息
ilink/bot/getuploadurl 获取 CDN 预签名上传地址
ilink/bot/getconfig 获取 typing ticket(24h 缓存)
ilink/bot/sendtyping 发送"正在输入"状态
ilink/bot/get_bot_qrcode 获取扫码登录二维码
ilink/bot/get_qrcode_status 轮询扫码状态

CDN 默认地址:https://novac2c.cdn.weixin.qq.com/c2c

2.4 账号认证流程

1
2
3
4
5
6
1. 插件请求 get_bot_qrcode → 获得 qrcodeUrl + sessionKey
2. 展示二维码(终端 ASCII 码 or 图片 URL)
3. 每隔 ~1 秒轮询 get_qrcode_status
4. 用户扫码确认 → 返回 botToken + ilink_user_id
5. 凭证存入 ~/.openclaw/openclaw-weixin/accounts/{accountId}.json
6. 后续所有 API 请求携带此 token

QR 码 TTL:5 分钟,过期自动刷新,最多刷新 3 次。


三、安全分析

3.1 凭证存储

文件路径~/.openclaw/openclaw-weixin/accounts/{accountId}.json

内容结构

1
2
3
4
5
6
{
  "token": "xxx",
  "baseUrl": "https://ilinkai.weixin.qq.com",
  "savedAt": 1742000000000,
  "userId": "xxx"
}

权限设置accounts.ts:172):

1
fs.chmodSync(filePath, 0o600); // 仅文件所有者可读写

文件权限做了 0o600,但有一个细节缺陷——权限设置包裹在 try/catch 里且静默忽略失败,如果运行环境不支持 chmod(如某些容器、Windows WSL),凭证文件将以默认权限暴露。

3.2 AES-128-ECB 加密模式

文件src/cdn/aes-ecb.ts

1
const cipher = createCipheriv("aes-128-ecb", key, null);

ECB(电子密码本)模式是一种确定性加密:相同明文 + 相同密钥 → 相同密文。这是密码学中的经典反例(可进行频率分析和块替换攻击)。

不过这里有一个缓解因素:每个文件使用独立随机生成的 16 字节 AES Keycrypto.randomBytes(16)),因此不同文件无法交叉关联。

结论:ECB 模式很可能是 Weixin CDN 协议层面的要求,而非插件设计缺陷。密钥随机化在一定程度上补救了 ECB 的最大缺陷,但仍不如 CBC/GCM 安全。

3.3 Context Token 内存存储

文件src/messaging/inbound.ts:15

1
const contextTokenStore = new Map<string, string>();

Context Token 是回复消息的"通行证",由服务端在每条入站消息中下发,插件用 accountId:userId 作 key 存在进程内存里。

问题

  • 进程重启后 token 全部丢失
  • Agent 回复若跨越重启,将因缺少 token 发送失败
  • 进程内存 dump 或 /proc/[pid]/mem 读取可提取 token

3.4 日志中的敏感信息

文件src/util/redact.ts

1
2
3
function redactBody(body: string): string {
  return body.length > 200 ? `${body.slice(0, 200)}...(truncated)` : body;
}

脱敏仅是截断前 200 字符,并非字段级脱敏。若 JSON body 中 context_token 出现在前 200 字符内,会以明文写入日志。

日志路径:/tmp/openclaw/openclaw-YYYY-MM-DD.log

额外问题/tmp 目录在大多数 Unix 系统下全局可读,日志文件默认权限也没有限制,任意有 shell 权限的用户都能读取。

3.5 Session 暂停逻辑的遗漏

文件src/api/session-guard.ts

当服务端返回 errcode -14(会话过期),插件会将该账号标记为"暂停"状态持续 1 小时。但是,长轮询监控循环(monitor.ts并不检查 session 是否暂停,会继续调用 getUpdates(),可能持续触发无效请求或导致状态不一致。

3.6 无证书 Pinning

标准 Node.js fetch() 不做证书 Pinning。在证书链遭受 MITM 攻击(如企业代理、恶意 CA 注入)的场景下,HTTPS 流量可被解密,bot token 可被截获。

3.7 安全评级汇总

问题 严重程度 文件
Context Token 仅存内存,重启丢失 inbound.ts:15
AES-ECB 加密模式 中(协议约束) aes-ecb.ts:7
日志含部分 context_token 明文 redact.ts, api.ts:103
Session 暂停未覆盖监控循环 session-guard.ts, monitor.ts
凭证 chmod 失败静默忽略 accounts.ts:172
日志文件全局可读(/tmp) logger.ts:106
无证书 Pinning api.ts:108
无 API 响应 Schema 校验 api.ts:150

四、隐私分析

4.1 收集哪些数据

插件在本地会存储以下数据:

数据 路径 说明
Bot Token ~/.openclaw/openclaw-weixin/accounts/*.json 永久存储,权限 0o600
账号 UserId 同上 扫码确认的微信号标识
同步游标 ~/.openclaw/openclaw-weixin/accounts/*.sync.json 断点续传用,不含消息内容
日志 /tmp/openclaw/openclaw-YYYY-MM-DD.log 含用户 ID、部分消息片段(前 200 字符)
入站媒体文件 ~/.openclaw/weixin/media/inbound/ 或系统 tmp 解密后明文存储,清理策略由框架决定

4.2 数据不出本地

插件没有内置遥测或追踪,所有 API 调用仅面向 ilinkai.weixin.qq.com,不向其他第三方发送数据。

唯一的数据上传入口是 CLI 命令(完全可选、需用户主动触发):

1
openclaw openclaw-weixin logs-upload --url <url>

该命令将本地日志文件以 multipart/form-data 形式 POST 到用户指定的 URL,上传地址由用户控制,不是预设的 Anthropic/OpenClaw 服务器。

4.3 端到端加密现状

  • 传输层:全程 HTTPS,合格
  • CDN 存储:AES-128 加密,但密钥随消息下发(非端到端)
  • 本地存储:解密后明文写入临时文件,无额外加密
  • Agent 处理:消息以明文传入 AI Agent

结论:这不是端到端加密方案,消息在服务端(Weixin ilink 服务)和 OpenClaw Agent 处均可以明文访问。

4.4 用户隔离

默认情况下,所有联系你 bot 的微信用户共享同一个 AI 对话上下文。

如需隔离,需要显式配置:

1
openclaw config set agents.mode per-channel-per-peer

开启后,每个微信用户获得独立的 Agent 会话,数据不互通。

4.5 微信账号与 OpenClaw 账号的关联

src/auth/pairing.ts 在登录时将扫码者的 ilink_user_id 写入 OpenClaw 的 allowFrom 列表,后续只有该用户才能向 bot 下达指令。这是一个合理的授权控制设计,但意味着微信 ID 会被持久化关联到 OpenClaw 账号体系中。


五、与 OpenClaw 的集成分析

5.1 插件注册机制

插件通过 openclaw.plugin.json 声明身份:

1
2
3
4
{
  "id": "openclaw-weixin",
  "channel": true
}

index.ts 向 OpenClaw 框架注册三个组件:

  1. Channel Plugin:处理消息收发
  2. CLI 子命令openclaw openclaw-weixin logs-upload
  3. Runtime Context:框架 API 的引用

5.2 Channel Plugin 能力声明

1
2
3
4
5
6
7
// src/channel.ts:95-98
capabilities: {
  chatTypes: ["direct"],      // 仅支持私聊
  mediaTypes: ["image", "video", "file", "voice"],
  deliveryMode: "direct",     // 直接发送,不排队
  textChunkLimit: 4000,       // 单条消息最大字符数
}

5.3 使用的 OpenClaw SDK API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 账号 ID 文件系统安全化
normalizeAccountId(raw)

// 配置 Schema 包装(Zod)
buildChannelConfigSchema(schema)

// typing 状态回调
createTypingCallbacks()

// 命令授权检查
resolveSenderCommandAuthorizationWithRuntime()

// DM 授权策略判断
resolveDirectDmAuthorizationOutcome()

// 获取推荐临时目录
resolvePreferredOpenClawTmpDir()

// Markdown 转纯文本(微信不渲染 Markdown)
stripMarkdown()

// 文件锁(多实例防冲突)
withFileLock()

5.4 Agent Prompt 注入

插件向 AI Agent 的系统 Prompt 注入以下提示(channel.ts:105):

  • 如何通过 message 工具发送图片(支持本地路径和 HTTPS URL)
  • 图片搜索后直接发图的正确姿势
  • 必须使用绝对路径(微信场景没有 cwd 上下文)
  • 创建定时任务时 delivery.to 必须设置为微信 ID

这是一种典型的 Channel-specific prompt engineering,让通用 Agent 懂得微信渠道的发送规范。

5.5 媒体发送流程详解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
AI 生成本地文件路径 or HTTPS URL
    
channel.ts  sendMedia()
    
    ├─ 下载远程文件(如果是 URL
    
    ├─ 计算 MD5
    ├─ crypto.randomBytes(16) 生成 AES Key
    ├─ ilink/bot/getuploadurl  获得 CDN 预签名参数
    ├─ AES-128-ECB 加密文件内容
    ├─ POST 加密字节到 CDN
    
    └─ ilink/bot/sendmessage(携带 CDN 引用 + AES Key

密钥随消息发送,接收方(或服务端)负责解密。这套流程是 Weixin ilink 协议层面的设计,插件按协议实现。


六、横向对比:它和其他微信机器人方案有什么不同?

维度 openclaw-weixin 传统 PC 协议逆向(如 Wechaty IPAD) 网页微信
合规性 官方 ilink 接口 逆向非公开协议,存在封号风险 官方已停止支持
稳定性 依赖 ilinkai 服务可用性 协议改版即失效 极不稳定
功能 私聊,图片/视频/文件/语音 群聊、更多消息类型 极为受限
认证方式 扫码 + server token 扫码 + 内存 session Cookie
媒体加密 AES-128-ECB,有一定保障 通常明文或用内置密钥 明文
Agent 集成 OpenClaw 原生深度集成 需要额外适配层

七、潜在风险场景

场景一:服务器被攻破

攻击者获得 ~/.openclaw/openclaw-weixin/accounts/ 目录访问权:

  • 拿到 bot token → 可冒充 bot 发送消息给任意联系人
  • 没有 token 轮换机制,需要手动重新登录才能失效旧 token

场景二:进程崩溃后 Context Token 丢失

用户发了一条消息,AI 正在生成回复时进程崩溃:

  • Context Token 丢失,重启后无法回复
  • 用户端看起来消息"发出去了"但没有回应
  • 需要用户再发一条消息才能恢复

场景三:日志泄露

/tmp/openclaw/*.log 被其他本地用户读取(共享服务器场景):

  • 用户 ID 暴露
  • 消息内容片段(前 200 字符)暴露
  • 若 context_token 出现在请求 body 前段,可被提取用于消息伪造

场景四:QR 码时序攻击

Bot 登录时终端展示二维码:

  • 若终端输出被捕获(如远程日志、截屏),攻击者可抢先扫码
  • 扫码 TTL 5 分钟内有效,超时会刷新
  • 扫码本身由用户微信账号的推送确认,纯窃取 QR 图片不能完成登录

八、改进建议

高优先级

  1. Context Token 持久化:写入磁盘(带 TTL 清理),避免重启后丢失回复能力
  2. Session 暂停检查:监控循环入口增加 assertSessionActive() 调用
  3. 日志目录权限限制/tmp/openclaw 设为 0o700,日志文件设为 0o600
  4. context_token 字段级脱敏:在 redactBody() 中对 JSON key 进行专项屏蔽

中优先级

  1. API 响应 Schema 校验:利用已有的 zod 依赖对所有服务端响应做校验
  2. chmod 失败告警:捕获异常后写日志,不能静默丢弃
  3. QR 码轮询退避:由固定 1 秒改为指数退避,减少无效请求

长期方向

  1. Token 轮换机制:支持定期或手动触发 token 刷新
  2. 媒体文件临时目录权限:将解密后的媒体文件写入权限受控的目录,而非系统 /tmp
  3. WebSocket 支持:当 ilink 协议支持时,迁移为 WebSocket 实时推送,减少长轮询开销

九、总结

openclaw-weixin 是一个架构清晰、代码质量较高的 OpenClaw 渠道插件。它走的是 Tencent 官方 ilink API,没有逆向非公开协议,合规层面比市面上大多数微信机器人方案更稳健。

主要亮点:

  • 插件与 OpenClaw 框架深度集成,开箱即用
  • 媒体文件通过 CDN + AES 加密传输,基本符合安全规范
  • 无内置遥测,数据不出本地
  • 代码模块化良好,依赖极简

主要隐患:

  • Context Token 仅存内存,高可用场景有风险
  • 日志权限管理粗放,共享服务器下有信息泄露风险
  • 没有 API 响应的运行时类型校验
  • 凭证 chmod 失败被静默忽略

对于个人或小规模部署,现有实现已经足够;若要在多用户、生产级环境使用,建议优先处理上述高优先级问题。


附录:关键文件速查

文件 关键行 内容
index.ts 1-27 插件注册入口
src/channel.ts 77-380 Channel Plugin 核心实现
src/auth/login-qr.ts 126-333 扫码登录完整流程
src/auth/accounts.ts 140-180 凭证读写与权限设置
src/api/api.ts 40-160 HTTP 客户端封装
src/monitor/monitor.ts 37-166 长轮询主循环
src/cdn/aes-ecb.ts 1-21 AES-128-ECB 加解密
src/cdn/upload.ts 52-100 媒体上传流程
src/messaging/inbound.ts 15-26 Context Token 内存存储
src/util/logger.ts 100-115 日志写入路径与权限
src/util/redact.ts 28-31 日志脱敏(截断 200 字符)
src/api/session-guard.ts 1-58 Session 暂停/恢复逻辑

本报告基于静态代码审计,未进行动态运行测试。如有任何问题欢迎讨论。