独立黑客手绘封面

Issue翻译机器人有两种容易被忽略的破坏方式:把代码里的标识符翻译掉,以及同一次投递重复写出多条评论。翻译质量当然重要,但入口真实性和投递幂等要先站稳。

GitHub官方Webhook说明要求使用原始请求体验证签名,并提供投递标识帮助追踪。一个翻译服务即便不修改源码,仍会处理外部文本和凭证,不能跳过这层检查。

验证原始字节,再解析JSON

GitHub的X-Hub-Signature-256是对请求体计算的HMAC。若先解析再重新序列化,空白或字段顺序可能变化,签名就不再对应收到的字节。秘密应由服务配置注入,不能硬编码进仓库。

先检查签名格式和长度,再做恒定时间比较。验签失败不进入翻译队列;响应日志也不应包含Webhook秘密。

保留代码、链接与术语

图中的PROTECT CODE与RESTORE CODE表示完整的预处理和还原步骤。真实Markdown包含围栏代码、行内代码、链接、HTML和引用,简单替换几个反引号容易漏掉嵌套情况。

术语表同样需要版本。函数名、配置键和错误码通常原样保留;解释性自然语言再翻译。输出先作为草稿,让维护者能看到原文与译文差异。

社区Issue翻译机器人:签名、术语保护与投递幂等缺一不可:机制与边界示意

这个样例只证明签名拒绝路径

为了保持例子自包含,下面只验证HMAC和原始字节变化的影响。它不假装实现完整Markdown解析器或翻译模型。

import assert from 'node:assert/strict';
import {createHmac,timingSafeEqual} from 'node:crypto';
const sign=(raw,secret)=>'sha256='+createHmac('sha256',secret).update(raw).digest('hex');
function verify(raw,header,secret) {
  if (!/^sha256=[0-9a-f]{64}$/.test(header)) return false;
  const a=Buffer.from(header.slice(7),'hex');
  const b=Buffer.from(sign(raw,secret).slice(7),'hex');
  return a.length===b.length && timingSafeEqual(a,b);
}
const raw=Buffer.from('{"title":"demo"}');const secret='local-test-fixture';
const header=sign(raw,secret);
assert.equal(verify(raw,header,secret),true);
assert.equal(verify(Buffer.from('{ "title": "demo" }'),header,secret),false);
assert.equal(verify(raw,'sha256=bad',secret),false);
assert.equal(verify(raw,header,'other-fixture'),false);
console.log('raw webhook signature: ok');

本地运行输出:

raw webhook signature: ok

幂等记录要跨越进程生命周期

用delivery id关联接收、排队、翻译和写回状态。只在内存Set里去重,重启以后会失效。也不能在工作还没可靠入队时就永久标记完成,崩溃会丢任务。

最后,机器人应识别自己的评论,避免翻译译文形成循环。订阅必要事件、限制文本长度、设置人工出口,这些约束比“全自动支持所有语言”更适合初版。

适用范围与未测部分

只验证本地HMAC函数,没有启动HTTP服务、接入GitHub、持久化去重、解析完整Markdown、调用翻译模型或写回评论。

参考资料

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐