腾讯云 OpenClaw 安全治理实践与工具链安全管控能力升级
常量时间、凭证脱敏与对象生命周期:四项上游修复的技术复盘
By:腾讯云 CVM
OpenClaw 的架构目标是把多种消息通道与多种模型后端汇聚到同一套网关之内。上游侧支持 Telegram、iMessage、Matrix、WhatsApp、LINE、Zalo、飞书、QQ 等通道;后端侧覆盖 OpenAI、Anthropic、HuggingFace、Replicate、腾讯云、阿里云等服务。实际部署通常只启用其中一部分;本文讨论的是这种多通道、多后端架构下容易被忽略的安全边界,并不假设某一台实例同时接入了全部能力。
本文围绕腾讯云团队已合入 OpenClaw 主干的四项上游 PR:两处 HMAC 签名校验中的时序侧信道、一处云厂商凭证日志脱敏缺口、一处网关进程内存退化。每项以现场、根因、复现、修复、回归测试五段展开,文末汇总三条可直接纳入 Code Review 清单的检查项。
1. 范围与索引
本文覆盖四个已合入主干的修复,分别涉及两个 channel 扩展、日志脱敏、网关节点状态。OpenClaw 上游已将 channel 迁移为extensions//目录下的 bundled plugin,src/channels/**仅保留 core 实现。下表给出 PR、路径、缺陷类型与根因索引;下图给出对应的工程边界分组。
|
|
|
|
|
|
|---|---|---|---|---|
|
|
extensions/line/src/signature.ts |
|
|
|
|
|
extensions/nextcloud-talk/src/signature.ts |
|
charCodeAt 循环被 V8 JIT 优化抹平 |
|
|
|
src/logging/redact.ts |
|
|
|
|
|
src/gateway/node-pending-work.ts |
|
Map 与内层状态生命周期错位 |
|

2. LINE 通道的时序侧信道:被优化掉的 timingSafeEqual(#55663)
严重程度:中高影响范围:所有启用 LINE 通道的 OpenClaw 部署
2.1 现场
LINE 平台为每条 Webhook 事件下发x-line-signature头部,其值为以渠道密钥(Channel Secret)对消息体执行 HMAC-SHA256、再经 Base64 编码得到的摘要。接入方在收到事件后必须自行计算签名并与请求头比对。该比对必须在常量时间内完成;一旦其耗时与输入相关,攻击者即可将耗时差作为时序预言(timing oracle),通过反复请求把正确签名一字节一字节探测出来。Node.js 标准库的crypto.timingSafeEqual即为此类比对的标准做法。
2.2 根因:被优化掉的快路径
修复前的实现:
consthashBuffer=Buffer.from(hash);constsignatureBuffer=Buffer.from(signature);// Use constant-time comparison to prevent timing attacks.if(hashBuffer.length!==signatureBuffer.length){returnfalse;}returncrypto.timingSafeEqual(hashBuffer,signatureBuffer);
注释明确写着 constant-time comparison,但开头长度不等早返回已经将其常量时间属性一并破坏。它与return a.length === b.length && timingSafeEqual(a, b)这种&&短路写法在密码学时序意义上完全等价,原因如下:
首先,分支结构已经引入耗时差异。长度不等的分支直接return false,根本不会执行timingSafeEqual;长度相等的分支则要走完一次完整比较。两条路径在外部观察者眼里耗时差异显著,整体响应耗时与长度是否匹配强相关。
其次,判定标准只有一条。无论是if早返、&&/||短路,还是?:三元,只要存在一条长度不等即走快路径的分支,便属于同一类时序旁路。判定标准只有一个:timingSafeEqual是否在所有输入分支上都被实际调用。
因此,泄漏的并非签名内容本身,而是合法签名长度。攻击者据此把搜索空间收敛到唯一长度,正是逐字节探测的入口。

2.3 影响范围与风险定级
严重程度:中高。该缺陷本身不直接构成签名伪造,但属于
影响范围:所有启用 LINE 通道的 OpenClaw 部署,无前置版本约束。
命中条件:攻击者只需具备向部署方 Webhook 端点发起请求并测量 RTT 的能力,无需任何账号或密钥。
2.4 复现:差分计时实验
在内网环境下,固定 endpoint 与上层网络条件,向同一 Webhook 路径提交两组伪造签名,每组样本数
次:
组 A:长度恰为 64(合法 hex digest 长度);
组 B:长度刻意偏离 64(如 32、96)。
对两组 RTT 做差分检验:

修复前 显著为负:组 B 因长度早返而跳过timingSafeEqual,响应更快,差值远超网络抖动 区间,足以作为长度判定的远程预言。修复后两组分布完全重合,分布形态见下图。

2.5 修复策略与回归保证
修复的关键在于让timingSafeEqual从条件触发转为无条件触发:先将两侧 buffer padding 至统一长度,无条件调用一次受信原语,最后通过&&把长度判断与比较结果合并:
// Pad to equal length before constant-time comparison to prevent// leaking length information via early-return timing.constmaxLen=Math.max(hashBuffer.length,signatureBuffer.length);constpaddedHash=Buffer.alloc(maxLen);constpaddedSig=Buffer.alloc(maxLen);hashBuffer.copy(paddedHash);signatureBuffer.copy(paddedSig);// Call timingSafeEqual unconditionally to ensure constant-time execution// regardless of length mismatch (avoids && short-circuit timing leak).consttimingResult=crypto.timingSafeEqual(paddedHash,paddedSig);returnhashBuffer.length===signatureBuffer.length&&timingResult;
改动新增 11 行。配套测试覆盖三种场景:长度等且内容等、长度等内容不等、长度不等,并显式断言timingSafeEqual在所有分支上均被实际调用,以避免后续维护者出于代码风格考量重新引入长度快路径。
3. Nextcloud Talk 的更隐蔽侧信道:被 JIT 抹平的常量时间(#58097)
严重程度:中高影响范围:所有启用 Nextcloud Talk 通道的 OpenClaw 部署
3.1 现场
Nextcloud Talk 通道同样采用 HMAC-SHA256 校验 Webhook 签名。早期实现为减少对 Node.jscrypto的依赖,作者手写了一段按位 XOR 比对,并在注释中明确声明这是”常量时间比较”:
if(signature.length!==expected.length){returnfalse;}letresult=0;for(leti=0;i<signature.length;i++){result|=signature.charCodeAt(i)^expected.charCodeAt(i);}returnresult===0;
这段代码先以早返处理长度差异,再对每个字节做 XOR 累计,最终判定result是否为零。循环主体本身是常见的常量时间写法,但在 V8 + TypeScript 的运行环境中,仅凭这一点尚不足以保证常量时间。
3.2 根因:”常量时间”是可执行代码的属性,不是源码的属性
该问题包含两层语义:第一层在源码本身,第二层在运行时优化器。
3.2.1 第一层:长度旁路
循环之前的if (signature.length !== expected.length) return false;与#55663同型,常量时间属性在进入循环之前便已丢失。即使将该早返改写为diff |= signature.length ^ expected.length后再走完循环,循环主体仍只遍历较短一侧的字节数,长度差异越大耗时越短,长度旁路依然存在。
3.2.2 第二层:JIT 抹平
更隐蔽的是运行时行为。Node.js 的 V8 引擎会把热点函数交给 TurboFan 重新编译为本地代码;优化器允许在不改变语义的前提下消除冗余计算。从外部观察,循环主体唯一的语义是”收集差异比特,最终判 0″。在 V8 看来,”一旦发现差异即可终止”与”跑完所有字节”在语义上等价。
退化链可按编译阶段对照如下:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|

最终的攻击面与原始strcmp接近一致:源码层面是常量时间,执行层面则未必。结论由此清晰:常量时间是可执行代码的属性,而非源码的属性;当源码进入 JIT 流水线后,这一前提需要被重新验证。
3.3 影响范围与风险定级
严重程度:中高。同样是 HMAC 校验失去常量时间属性,且具备双重旁路(长度 + JIT),相较#55663更难凭代码评审发现。
影响范围:所有启用 Nextcloud Talk 通道的 OpenClaw 部署。
特征:是否被 JIT 抹平依赖具体运行环境(V8 版本、热路径状态、函数大小阈值)。在工程上不应将”是否被抹平”当作不可证伪的不确定项;只要源码上保留了被抹平的可能,便应视同已被抹平。
3.4 复现:换字节位置做差分计时
构造两组等长签名样本,长度均为 64 字节:
组 C:与正确签名仅首字节不同;
组 D:与正确签名仅末字节不同。
如手写循环已被 JIT 优化为提前退出形式,则
,且差值随样本量收敛到稳定区间。这是理想的常量时间实现中本不应出现的差异。组 C/D 与上一节的组 A/B 是正交实验:A/B 暴露长度旁路,C/D 暴露 JIT 抹平。
3.5 修复策略与回归保证
修复路径与#55663同源:以crypto.timingSafeEqual替换手写循环,复用等长 padding 模式将长度判断后置。修复后的控制流与 LINE 修复图 右半部分一致(padding → 无条件timingSafeEqual→ 长度后置判断),不再重复绘制。源码侧补一段注释,说明 expected 侧固定为 SHA-256 hex digest(恒为 64 字节),以及为何不能回退到手写循环。配套测试新增 95 行用例,覆盖等长内容相同 / 等长仅首字节不同 / 等长仅末字节不同 / 长度不等 / 全空 / 全 0 等边界,并显式断言受信原语在所有路径上均被实际调用。
4. 凭证日志的脱敏缺口:四个被忽略的前缀(#58162)
严重程度:中影响范围:所有以腾讯云/阿里云/HuggingFace/Replicate 为模型后端的 OpenClaw 部署
4.1 现场
OpenClaw 在 debug 级别会将请求/响应的完整 payload 写入日志,这是排障常用手段,也带来凭证明文落盘的风险。src/logging/redact.ts为此维护了一组默认脱敏正则,命中即将原值替换为[REDACTED:]。模块上线时已经覆盖 OpenAI、Anthropic、AWS、GCP 等主流厂商凭证前缀。
但当我们对照真实部署中出现过的凭证样本完整运行一遍后,发现仍有四类前缀未被覆盖。
4.2 根因:默认管线遗漏的四类前缀
修复前的默认正则集合未覆盖以下四类前缀,而这四类几乎全部出现在团队真实部署的日志样本中:
腾讯云:
阿里云:
HuggingFace:
Replicate:
只要部署方以这四家任一作为模型后端,并因排障将日志级别调至debug(生产侧通常不会触发,但调试态、故障复盘态、运维误操作均可能触发),凭证即会以明文落盘,并随日志聚合管线被复制到 ELK / 对象存储 / 监控平台等多个信任域。一次明文落盘等价于密钥跨多个信任域被静默复制;从 SOC/合规视角,这是高优先级问题。

4.3 影响范围与风险定级
严重程度:中。本身不直接构成 RCE 或未授权访问,但属于凭证以明文形式穿透日志边界,在 SOC/审计语境下构成高优先级合规问题。
影响范围:所有以腾讯云、阿里云、HuggingFace、Replicate 为模型后端的 OpenClaw 用户。
次生风险:日志的传播链通常远长于代码。一次明文落盘,意味着同一份密钥在多个保留期、多个访问主体之间静默复制;其撤销代价远高于初始泄漏成本。
4.4 复现:四步落到日志里
在 OpenClaw 配置中将四家厂商任一指定为模型后端;
临时将日志级别调高至debug;
触发一次正常推理调用;
在日志中检索凭证前缀(AKID、LTAI、hf_、r8_),观察其是否仍以明文出现,或已被替换为[REDACTED:*]。
4.5 修复策略与回归保证
修复初版的做法较为直接:在默认脱敏正则集合中追加上述四类前缀,模式统一为\b[A-Za-z0-9]{10,}\b。但合入后社区反馈了一类过度脱敏:Akida、akidney等英文单词在大小写不敏感匹配下会被误判为腾讯云 SecretId。这是脱敏规则的经典权衡:
脱敏规则的实际质量是三个维度的联合函数:召回率、误报率与业务可读性。
- 召回率:是否覆盖了所有实际出现的凭证前缀?任一遗漏即为漏洞。
- 误报率:是否会把正常业务子串误判为凭证?过度遮蔽会迫使运维绕过脱敏层,反而扩大攻击面。
- 业务可读性:日志在排障侧仍需可读;过度遮蔽与遮蔽遗漏同样不可接受。
后续 commit 据此将AKID收敛为大小写敏感、强制大写匹配,并新增针对Akida / akidney / Akihabara等英文常见词的负样本测试。脱敏规则的工程化要求在新增前缀、收敛误报、回归负样本三者之间反复校准,并非一次性追加正则即可完成。
5. Gateway 节点状态外层 Map 的内存长尾治理(#58179)
严重程度:中影响范围:所有以网关模式(
gateway.mode=local或集群模式)运行的 OpenClaw 部署
5.1 现场
前三项 PR 均可在静态评审中识别,前提是评审者具备相应的密码学或合规经验。#58179则属于运行时状态泄漏:在短周期测试中通常无从察觉,唯有在节点反复接入、断开的场景中,RSS 才会随时间持续爬升,最终触发 OOM。
5.2 根因:内层条目已清,外层键未回收
OpenClaw gateway 维护了一份按节点(node)粒度组织的 pending work 状态集,结构是两级 Map:
外层stateByNodeId: Map;
内层NodeState.itemsById: Map。
修复前的代码已在两条路径上正确清理内层条目(drain 队列完成、ack 完成),但从未在内层为空时回收外层stateByNodeId中的对应键。”内层为空”与”外层不存在”被错误地合并为同一种状态 —— 在 GC 意义上合并了,但在数据结构语义上并未合并。
正常长连接节点不会触发该问题。但部署中一旦出现大量短命节点(临时连接、推送一条任务、ack 完成后即断开),每个此类节点都会在stateByNodeId中残留一条悬挂条目:
外层键nodeId仍占据 hash 表槽位;
对应的NodeState对象虽然itemsById.size === 0,本体仍处于活跃引用链上,无法被 GC;
随时间推移,外层 Map 单调增长,进程 RSS 缓慢爬升,最终触发 OOM。

5.3 影响范围与风险定级
严重程度:中。慢速内存泄漏,影响长跑稳定性而非即时可用性;但 OOM 一旦触发,即等同于可用性归零。
影响范围:所有以网关模式运行的 OpenClaw 部署,节点连接抖动场景下尤为显著。
长跑特征:单元测试运行周期不足以让外层 Map 增长至可观测水位,监控视图上也难以定位到一个明确的”泄漏起点”。
5.4 复现:以 量级短命节点对单进程施压
启动 gateway 进程,模拟 N 万次一次性节点:每个节点接入、提交一条 pending work、立即 ack 后断开。观察进程 RSS:

修复前,RSS 与累计经过的节点数
严格线性相关;修复后,RSS 维持在基线附近,每轮 ack 完成后即回落,与上图右半部分的曲线吻合。
5.5 修复策略与回归保证
修复点置于会令内层变空的两条调用栈末尾:drain 队列完成与 ack 完成。其余分支(部分 ack、新增 item、状态更新等)即便接触到内层,也不会令其变空,因此无需触发 prune,避免引入无效开销:
functionpruneStateIfEmpty(stateByNodeId:Map<string,NodeState>,nodeId:string,state:NodeState,):void{if(state.itemsById.size===0){stateByNodeId.delete(nodeId);}}
判定流见下图:drain / ack 完成 → 询问itemsById是否为空 → 若为空则从外层移除 →NodeState异步进入 GC。配套 17 行测试覆盖三种状态分支:内层非空(保留外层)、内层为空但节点仍活跃(保留外层)、内层为空且节点已断开(回收外层),并以performance.memoryUsage断言长跑场景下 RSS 收敛于基线附近。

6. 三条可复用的代码评审检查项
将四项 PR 从具体语境中抽离,可提炼出三条直接纳入 Code Review 清单的检查项。
6.1 密码学防御原语必须无条件调用
原则:timingSafeEqual等原语的安全性,并非”调用即生效”,而依赖于比较过程在任何输入下都被完整执行。
典型反模式:
任何短路求值(&&、||、提前return、长度断言、参数早退)均会破坏常量时间属性。
即便写出”形式上常量时间”的手写循环,JIT 优化器也可能在语义等价的前提下将其编译为 early-exit。
评审动作:当评审中遇到密码学等值比较、签名校验、token 比对路径上的手写循环或长度快路径时,应默认替换为timingSafeEqual等受信原语,并以测试显式断言其在所有分支上被实际调用。
6.2 多层嵌套结构必须在同一调用栈上对齐生命周期
原则:只要状态集合采用了两层或更多的嵌套数据结构(典型如Map>),任何”清空内层”的操作都必须在同一调用栈检查”是否需要从外层移除”。
典型反模式:
仅清理内层而不回收外层键,外层 Map 会随短命对象的累积而单调增长。该泄漏在短周期测试中极难暴露,需待节点抖动累计到一定规模才会显现,监控视图上同样难以锚定泄漏起点。
评审动作:在涉及多层嵌套集合的清理路径上,要求实现方提供”何时移除外层键”的显式判定,并补充长跑断言(如performance.memoryUsage收敛在基线)覆盖回收路径。
6.3 脱敏规则按真实语料而非假想场景校准
原则:日志脱敏并非添加一条正则即可。一组合理的脱敏规则需在三项指标之间取得平衡。
评估维度:
召回率:覆盖所有实际出现在日志中的凭证前缀,遗漏即缺口。
误报率:避免将正常业务子串误判为凭证;过度遮蔽会迫使运维绕过脱敏层去查看原始流量,反而扩大攻击面。#58162初版将
可读性:过度遮蔽会令日志丧失排障价值。
评审动作:新增前缀时,必须同步回归已有规则的负样本,避免增量式扩张将日志推向不可读状态。
7. 结语
上述四项修复覆盖三类常见边界:签名校验、日志脱敏与运行时状态管理。落到评审动作上,即为:检查签名比较是否存在时序差异;检查 debug 日志是否覆盖真实凭证前缀;检查长跑进程是否残留悬挂状态。
后续评审遵循三条检查项即可:密码学防御原语必须无条件调用;多层嵌套结构必须在同一调用栈上对齐生命周期;脱敏规则必须按真实语料完成正负样本回归。腾讯云团队在 OpenClaw 上游还覆盖网关协议、插件 SDK、多通道适配等其他主线,本文仅取其中安全与稳定性方向的一个子集。
夜雨聆风