我们今天登上了 Product Hunt! 留下评论
Authenticator 1.16.0 已发布。 查看更新内容
面向安全审阅者的技术说明。这里的每一条都可以对照源码核实——包括那些对我们不太有利的部分,我们选择写出来而不是略过。
本扩展是一个 Manifest V3 的 Chrome 扩展,使用 OTPAuth 在本地生成 TOTP 验证码。生成验证码、保存账户以及对账户加密都不涉及任何后端,也没有用户账号。我们只为免费版和 Pro 运营一台服务器 api.authenticator.sh(详见下文),它从不接收任何密钥、账户名称、服务名称或验证码。
这台服务器不持有任何人账户的密钥材料:没有密钥托管,也没有由我们掌控的找回途径。如果用户忘记密码又丢失了恢复码,我们无能为力——这是设计使然。
密码保护是可选项,默认关闭。开启后:
数据从不直接用密码派生出的密钥来加密。记录由一个随机主密钥加密;PBKDF2 的输出只负责包装这个主密钥。由此带来两点结果,两点都很重要:
开启密码保护后,除标识符和指纹外,整条账户记录都会被加密——服务名称也在其中,因此即便配置文件被窃取,也看不出用户在哪些服务上有账户。启用时,扩展会先加密记录,再通过解密比对来校验,确认无误后才删除所有明文副本:浏览器本地存储、同步存储,以及全部七份轮转备份快照。
直说无妨,因为这关系到你的评估:
简而言之:它保护的是静态存储的数据。对于使用期间已经落入攻击者控制的设备,它无法防护,也做不到防护。
清单文件声明了五项权限,且不含任何主机权限:
没有任何内容脚本,没有 tabs、cookies 或 webRequest 权限,也没有可供网页访问的资源。在你于某个页面上索要验证码之前,页面里不存在任何属于我们的东西;届时运行的代码,是在你自己那次点击所授予的权限下,只为那一个标签页、那一次调用而注入的,事后不留下任何东西。
用摄像头扫描二维码时,运行的是在标签页中打开的扩展页面,而不是弹出窗口——弹出窗口一旦失去焦点就会被销毁,而权限提示恰恰会夺走焦点。这无需在清单文件中声明任何权限:它使用浏览器标准的摄像头授权提示,按扩展来源授予,可在网站设置中撤销,并且在用户打开扫描器之前绝不会请求。视频在本地解码,从不传输也不存储。
自 1.14.0 起,扩展每天会与 api.authenticator.sh 通信一次——在浏览器启动时,或在弹出窗口渲染完成之后进行,绝不会插在显示验证码之前。请求由一份封闭的字段清单构成,全部在 src/billing/sync.ts 中的一个函数里构建,并由一项测试确保它不超出这份清单:
从不发送:密钥、账户名称、服务名称、验证码,或使用它们的网站。服务器在其边缘节点根据请求得出国家/地区,不保存 IP 地址。卸载页面会以 ?i= 的形式收到同一个安装 ID:它会告知服务器该安装已被卸载,并把可选的问卷回答与该安装关联起来。
为此保存在 chrome.storage.local 中的内容:安装 ID、最近一次收到的已签名策略和许可证、本次安装持有的许可证密钥(手动输入的,或在本安装上购买后收到的)、服务器收到之前的每日打开次数,以及排队中的事件——不含任何账户数据。同步开启时,该安装是否早于计费功能,以及这个许可证密钥,也会写入 Chrome 同步,以便用户的其他 Chrome 配置文件获取它们并自动开启 Pro;关闭同步会把两者从 Chrome 同步中删除,重新开启则会再写回去。
服务器返回什么,以及扩展如何对待它:
这个上限不是 DRM:代码是开源的,任何分叉都可以把它去掉。绝不能发生的是:一个人获取或使用另一个人的许可证,或者让别人的安装受到限制——那属于漏洞,在我们安全政策的范围之内。
有两类请求是扩展自行发出的:时钟检查和每日计费通信。下面其余各项都是用户点击之后才发生的。Manifest V3 禁止远程代码,而且这里没有任何一项会携带账户、密钥或生成的验证码。完整清单如下:
| 主机 | 何时 | 是否携带用户数据? |
|---|---|---|
| time.akamai.com timeapi.io cloudflare.com | 时钟偏差检查,带缓存;若所有来源都联系不上,设置中会说明。时钟偏了 TOTP 就会失效。 | 否 |
| authenticator.sh | 安装时打开欢迎页;卸载时打开反馈页,其地址中带有安装 ID;从弹窗打开帮助页和支持页。 | 仅卸载页面携带安装 ID;其余情况为否(仅普通 Web 服务器日志) |
| api.authenticator.sh | 每天一次计费通信;用户购买 Pro、输入密钥、恢复购买或管理订阅时,发出结账、许可证和客户门户相关的请求。 | 上文列出的字段——绝无账户数据。只有在用户亲自输入时,才会携带许可证密钥或邮箱地址 |
| checkout.stripe.com billing.stripe.com | Stripe Checkout 和客户门户,在用户购买 Pro 或管理订阅时于新标签页中打开。 | 买家在那里输入的内容。适用 Stripe 和 Link 各自的条款 |
| chromewebstore.google.com | 商店详情页,只在点击时新开标签页:评分提示进入评价表单。 | 否(在 Google 看来与访问任何一个商店页面无异) |
字体、脚本、样式和图片都不会从第三方主机加载。渲染界面所需的一切都随扩展包一起提供。
以上内容都不必只凭信任。扩展是开源的,每个版本都可复现:
.crx 并解压npm ci && npm run builddist/ 与解压出来的包进行比对每个 GitHub 版本都会随附构建产物 dist/ 中每个文件的 SHA-256,文件名为 SHA256SUMS-v<version>.txt,采用 sha256sum 格式,所以第 4 步只需运行一次 sha256sum -c。差异应当仅限于压缩包内的文件顺序,以及不同 Node 补丁版本下压缩输出中的空白字符。
运行时依赖刻意保持很少——OTPAuth、React、jsQR、Zustand、Framer Motion 和 Lucide——并由提交入库的 lockfile 固定版本。如有需要,可提供 CycloneDX 格式的 SBOM。
查看源码我们是一家独立的小团队。如果下面任何一项是贵方流程中的硬性要求,与其评估进行到第三周才发现,不如现在就知道:
我们能提供的是:完全公开的源码、可验证的构建、最小化的权限面,以及一台从一开始就不会收到你的密钥、账户名称或验证码的服务器。
漏洞报告与安全问题请联系 security@authenticator.sh。我们的披露政策、适用范围、响应时限和安全港条款见安全政策页面;哪些数据会被保存、哪些会离开设备,则写在隐私政策中。
我们乐于填写书面的供应商评估问卷,也可以陪同安全团队一起走查代码。
凡与安全无关的事项——安装、账户找回、部署中的计费问题——请见支持页面。请不要把漏洞报告发到那里;那个邮箱不按机密方式处理。