e2e 报告中的 secret 泄露防护:redaction 脱敏机制源码解读
【免费下载链接】e2eNext generation e2e testing framework for web and mobile apps.项目地址: https://gitcode.com/GitHub_Trending/e2e6/e2e
e2e 是一个面向 Web 与移动端的下一代 e2e 测试框架,支持 AI 驱动的智能测试。它的 secret 泄露防护(redaction 脱敏机制)能确保测试报告中绝不出现明文密码与 API 密钥:所有已注册的敏感值都会被替换为<secret:name>标记,即使它们藏在 JSON 转义、URL 编码或流式输出中间。本文带你从源码层面快速理解这套脱敏机制的设计与边界。
🎯 为什么 e2e 测试报告需要 secret 脱敏?
e2e 的运行产物(report.json、失败现场树、AI 模型输入、控制台输出、下载文件)会携带大量应用内容。一旦测试涉及登录、支付等场景,密码或密钥很容易出现在:
- 测试标题、步骤标签、
fill的输入值里; - 页面的 URL、无障碍树文本、断言失败信息里;
- AI agent 接收的屏幕观察中。
如果这些产物不做处理,一份失败报告就等于一份"密码泄露报告"。e2e 的答案是:运行器(runner)侧统一脱敏,敏感值永远以不透明句柄流转。
🔑 第一层:Secret 是不透明句柄,读不到明文
框架把密码和 API 密钥包装成Secret句柄,源码见 packages/e2e/src/secrets.ts:
secrets.get('stripe-key')返回的不是字符串,而是一个带品牌标记的对象;- 一旦尝试把它当字符串用(模板字符串、
String()、JSON.stringify),会直接抛出INVALID_CONFIG错误——配置里只允许持有"名字",不允许持有"值"; - 密码只能交给三个"汇点":定位器的
fill、agent 步骤的参数、声明过 secret 的引擎选项(如web({ basicAuth: { password } }))。
也就是说,测试代码从语法上就无法"打印"出一个密钥。用法参考官方文档 docs/authentication.mdx。
🛡️ 第二层:SecretLedger 台账 + 全编码形态匹配
真正干活的是脱敏台账 packages/e2e/src/internal/redact.ts 中的SecretLedger类。每个会话持有一份,登记该会话可能见过的一切敏感值:
- 静态值:配置
secrets里写死的字符串,在会话创建时预注册; - provider 动态值:如每次填充时现算的 TOTP 验证码,在
resolveSecretValue(见 packages/e2e/src/run/secrecy.ts)里"注册在先、取值在后"——值一诞生就已进入脱敏范围; - 轮换不失效:同一个名字的新旧值都会保留在台账里,防止上一轮验证码残留在截图或文本中泄漏。
匹配的聪明之处在于"多形态正则"(valuePattern函数):它不是简单查找原始字符串,而是为每个值生成一条能同时命中下列写法的模式:
- JSON 转义(
\"、\uXXXX)、HTML 字符引用(&、/)、百分号编码(%2f); - 任意大小写变体(CSS
text-transform藏不住它); - 空白折叠后的形态(渲染引擎会把连续空白压成一个空格)。
命中后统一替换为<secret:名字>标记,且标记本身绝不会被二次改写——脱敏两次等于脱敏一次。
⏳ 流式输出怎么办?StreamRedactor 的"扣尾巴"策略
终端输出是一块一块写出来的,密钥可能被拆在两次write中间。StreamRedactor(同一文件,约 676 行处)的解法非常优雅:
push(chunk)先拼接缓冲,再调用ledger.holdFrom(text)算出一个安全切点:从该位置往后的文本可能被后续片段补全成一个敏感值或一个标记,于是扣下不输出;- 之前的安全部分照常脱敏后输出;
flush()在流结束时释放并脱敏剩余部分。
最终效果:分段流式脱敏的结果与整段一次性脱敏完全一致。stdout 和 stderr 各挂一个独立实例,实现见 packages/e2e/src/run/process-output.ts。
📸 第三层:填充过密码就"扣住像素"
脱敏不止针对文本。packages/e2e/src/run/secrecy.ts 中的SecretExposure记录会话的暴露等级:只要密码被真正fill进应用,后续整个尝试周期内——截图工具被禁用、断言与失败截图被省略、模型也收不到像素。因为应用可能把密码渲染在掩码字段之外的任何地方,文本脱敏救不了画面。
⚠️ 脱敏的边界:官方安全模型明示的局限
docs/security.mdx 专门列出了"红action 在哪里停下",值得团队知晓:
- 值的变形形态(如只露后 4 位)不视为密钥,会放行;
- 整体 base64 编码、哈希后的值是"另一个字符串",会放行(但下载文件中的 base64 片段会被尝试解码后重写);
- 视频不做任何像素级脱敏,报告中标注
redaction: "incomplete"; command.log捕获的是应用进程的原始 stdout,运行器不脱敏——敏感值请别打印到应用标准输出。
📚 关键文件导航
| 模块 | 路径 |
|---|---|
| 脱敏核心(台账 + 正则 + 流式脱敏) | packages/e2e/src/internal/redact.ts |
| 不透明句柄与注册表 | packages/e2e/src/secrets.ts |
| 会话机密状态与暴露等级 | packages/e2e/src/run/secrecy.ts |
| 流输出逐块脱敏 | packages/e2e/src/run/process-output.ts |
| 长度下限等配置规则 | packages/e2e/src/config/secrets.ts |
| 安全模型官方文档 | docs/security.mdx |
一个细节值得玩味:静态密钥短于 6 个字符会直接报INVALID_CONFIG——因为太短的值(PIN、端口号)全局替换会误伤正常文本。脱敏的代价可控,比脱敏的漏网之鱼更值得优先。
【免费下载链接】e2eNext generation e2e testing framework for web and mobile apps.项目地址: https://gitcode.com/GitHub_Trending/e2e6/e2e
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考