feat(chv): 对齐 Muse P10 支付安全——凭证隔离 + 结账检测 + 每次购买人工审批 + 一次性卡号

- 支付凭证隔离:PaymentCredentialVault(只活内存、白名单
  stripe-link/shop-pay、只存不透明 id + 掩码标签、原始卡号
  PAN 拒绝入库、按次释放绑定商家/金额/货币、单次 + TTL)
- 结账页检测:detect_checkout(URL + 无障碍树信号,确定性
  规则、不读原始 DOM、与 P4 detect_form_risk 同输入形状)
- 每次购买人工审批:PaymentGate 经 P5 对话外通道发审批,
  payload 含确切购买详情;完成时先验签、再验详情摘要
  (篡改即拒且审批保持待处理)、最后单次消费答复
- 一次性卡号:OneTimeCard 绑定商家/金额/货币/有效期,单次
  兑现;SimulatedWalletBackend 沙箱钱包(假 PAN 只在后端
  私有内存、charge 只记账不联网、取消即 void)
- 限额:SpendLimits 三档 minor units 比对,限额不可用时
  不编造上限(limits_checked=False)
- 审计:PaymentAudit 只留允许字段,秘密字段名丢弃 + redacted
- 审批通道最小扩展:ApprovalRequest 增加可选 details 字段,
  不传时 payload 与 P5 完全一致
- 真实支付与扣款不做:全部沙箱验证,真实钱包集成待用户
  另行批准;新测试 79 项,全量 cloud 套件 2037 OK

round-12 集成说明:本提交为 acf98f8 的 cherry-pick(只含 P10
自有 diff,基线 25bcd35 已含的 P3/P5 内容未重复合入;
approval_channel.py 仅加可选 details 字段)。
This commit is contained in:
host committed 2026-11-07 00:09:55 +00:00
1 parent 90c42f6e1d
commit b55b49b358
7 files changed
+1939 -8

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
# 对齐 Muse P10 差距表:支付安全(凭证隔离 + 结账检测 + 每次购买人工审批 + 一次性卡号)
依据:`~/workspace/research_notes/meta-muse-ai-architecture-20261101-0755/report.md`
(官方以 Meta security.muse.ai 2026-09-08 为准;第三方转述要标注
来源性质,不得把转述写成官方承诺)。Muse 官方支付安全要求
(报告 §3.3,):购物是最常见浏览器用例之一,Meta 对此
格外谨慎——已保存支付凭证的网站,浏览器内置结账页检测,每次都
弹出人工审批并展示确切购买详情;未购物过的网站,Muse 内置合规
钱包,首发合作 Stripe Link,Shop Pay 即将来临;支付时发行一次性
卡号给商家,而非真实卡号;该凭证绑定特定商家、特定金额、有限
有效期,即使经提示注入被盗也“不太有用”;Muse 是首个被 Link 购买
保障覆盖的 AI 代理(损坏/丢失商品免费保障、降价保障、免费退货、
符合条件购买的退货保证——保障条款以 Link 官方为准,本轨不编
细节、不实现理赔流程)。
连接器文档补充(wallet tools 的实际口径,见
`/opt/hatch/connectors/stripe-link/CONNECTOR.md`、
`shop-pay/CONNECTOR.md`、`shopify-ucp/CONNECTOR.md`):
Link 在一次购买获批后注资一张一次性虚拟卡,商家永远收不到真实
卡号;Shop Pay 原生路由用一次性 token、卡片路由在审批后才由浏览
器填卡字段;每次购买走独立 browser task 与独立审批;审批被拒即
停止、不扣款;结果未知时不重试、不换卡、不换路;取消待支付会
void 虚拟卡;已完成扣款无钱包退款、走商家退款流程;直接结账须
先查限额(每笔/每日/30 天,minor units、无货币、不换算),限额
不可用时不编造上限;step-up 未完成时 Link 要求用户先验证身份;
子代理、群聊、定时任务没有 wallet tools,不能替用户设 Link 或选
支付方式。**本轨的结论:钱包能力在主应用运行时侧,不在
`cloud/chv` 代码里;本轨只做客内 agent 侧的支付安全门(检测、
审批、绑定、审计),真实钱包调用是待批准的集成点,见 §3。**
本轨范围只限 P10:支付安全。起点是 P3(authd 替身凭证)与 P5
(对话外审批通道),已合入本分支(合并 feat-chv-muse-p5,
25bcd35)。不碰 P1(常驻 VM、气球、vsock)、不碰 P2(nspawn
单元、浏览器子代理本体)、不碰 P4(浏览器完全对齐——结账检测
与它接线,不重复造)、不碰 P6(privsep worker)、不碰 P7/P8/P9。
分支 `feat-chv-muse-p10`(从基线 `06c3e17` 分出,合入 P5 后
HEAD 为 `25bcd35`)。
## 0. 起点(P3/P5 已做的,本轨不重写)
- P3 `cloud/chv/authd.py`:`CredentialVault`(只活内存、白名单、
按次释放绑定 {连接器, 主机, 仓库路径, 操作}、单次 + 300 秒
TTL、审计绝无凭证值)+ `mint_gitea_token`;白名单只认
`gitea`/`GITEA_TOKEN`,支付不在其中——本轨不改 authd 的
白名单,支付凭证另立 `PaymentCredentialVault`(同模式,不
同库,见 §1),避免把支付键混进代码仓库凭证的白名单。
- P3 `cloud/chv/sentinel.py`:`ApprovalQueue`(ask 记入内存态
待审批,`resolve` 只标记不放行);`EgressRequest.describe()`
只含连接器/目标/方法——支付审批借用这条队列记账,目标描述
为 `connector=payment, method=purchase, scheme=https,
host=<商家主机>, port=443, path=/checkout`。
- P5 `cloud/chv/approval_channel.py`:对话外审批通道
(`ApprovalRequest.payload()` + `Answer` + HMAC-SHA256 签名
+ `verify_answer` 五项校验 + `resolve_via_channel` 单次消费;
对话内容里出现的“批准”字样一律无效)。本轨只给它加一个
可选 `details` 字段(确切购买详情进 payload、供客户端 UI
展示),校验与消费逻辑一字不动。
## 1. 现状 vs Muse
| 项 | 现状(实测/代码为准) | Muse 目标 | 本轨做法 |
|---|---|---|---|
| 支付凭证隔离 | 无。`cloud/chv` 里没有任何支付代码;agent 若要支付,只能把卡号/钱包凭证以明文塞进环境或对话——这正是 Muse 明令禁止的形态 | 凭证与 OAuth token 存于用户 VM 内独立隔离容器,不在其他服务中;登录凭证存入单元外的安全凭证存储,代理不可见,仅在需要时注入 | `cloud/chv/payments.py`:`PaymentCredentialVault`(只活内存、白名单 `stripe-link`/`shop-pay`、只存不透明支付方式 id + 掩码标签;原始卡号 PAN 拒绝入库——13–19 位数字串 + Luhn 校验命中即拒;按次释放绑定 {商家, 金额, 货币}、单次 + TTL;`__repr__`/审计绝无凭证值;`forget` 删凭证并作废未兑现释放) |
| 结账页检测 | 无。浏览器侧 P2 的 `chv-browser.py` 只有 open/snapshot/screenshot/close/ping,无结账概念;P4 在做高风险表单拦截(`detect_form_risk` 扫无障碍树,密码/支付字段即标 `form_risk`),尚未提交 | 浏览器内置结账页检测 | `cloud/chv/checkout_detect.py`(纯函数,标准库):`detect_checkout(url, a11y_nodes)`——URL 信号(/checkout、/payment、/pay、/order-review 等)+ 无障碍树信号(卡号/有效期/安全码字段、“下单/支付”按钮、金额文本);返回 `is_checkout`/`confidence`/`reasons`/`form_risk`/`amount_text`;确定性规则,不冒充模型分类器;只读 a11y 节点,绝不读原始 DOM;与 P4 的 `detect_form_risk` 同输入形状,P4 的 broker 在高风险表单提交前调本检测(接线点见 §4,P4 未提交前先留接口 + 测试) |
| 每次购买人工审批 | 无。没有任何“购买前停下”的机制 | 已保存支付凭证的网站,每次都弹出人工审批并展示确切购买详情 | `PaymentGate.begin_purchase`:结账检测未命中即拒;路由(见下行);限额检查(见下行);经 P5 通道发审批,payload 的 `details` 含确切购买详情(商家、商品、数量、金额、货币、钱包提供方);审批只走通道答复,对话内“批准”无效;`complete_purchase` 先验签(`verify_answer`,不消费)、再验购买详情摘要(`PurchaseDetails.digest()`,篡改即拒且审批保持待处理)、最后 `resolve_via_channel` 单次消费;审批被拒即停止,不扣款 |
| 一次性卡号 | 无 | 支付时发行一次性卡号给商家而非真实卡号;绑定特定商家、特定金额、有限有效期;被盗也“不太有用” | `OneTimeCard`(绑定 {商家, 金额, 货币, 有效期},单次兑现;换商家/超额/过期/复用一律拒 + 审计);`SimulatedWalletBackend`(沙箱钱包:假 PAN 只在后端私有内存里,调用方只见掩码标签与 card_id;`charge` 只记账不联网;取消即 void);真实一次性虚拟卡由 Link 在其侧发行(连接器文档原文),本轨的卡是绑定与兑现校验的等价模型,不是发卡行——见 §3 |
| 钱包路由 | 无。连接器文档有 stripe-link / shop-pay / shopify-ucp 三条路由,但 `cloud/chv` 里无路由逻辑 | 未购物过的网站走内置合规钱包,首发 Stripe Link,Shop Pay 即将来临 | `PaymentGate` 路由:商家有已保存凭证 → 已保存凭证路由;无 → 合规钱包路由,stripe-link 优先、shop-pay 次之;两家都不支持 → 拒绝(`no_wallet_route`),不编造路由。**来源时间差**:报告写于 2026-11-01 时 Shop Pay 是“即将来临”,连接器文档(当前)显示 Shop Pay 已可用;本轨按连接器文档现状写优先级,并在此标注时间差 |
| 限额 | 无 | (连接器文档)直接结账须先查限额:每笔/每日/30 天,minor units、无货币、不换算;限额不可用时不编造上限 | `SpendLimits.check(amount_minor)`:三档逐项比 minor units;超任一档即拒(`over_limit`,审批前就拒);三档全为 None(不可用)→ 通过但记 `limits_checked=False`,绝不编造上限 |
| 支付审计 | 无 | 审计轨迹展示已做与计划做的事 | `PaymentAudit.record`:事件(`payment_approval_requested` / `payment_approved` / `payment_denied` / `payment_completed` / `payment_refused` / `payment_cancelled` / `payment_card_voided`)+ 允许字段(商家、金额、货币、掩码标签、pending_id、原因);字段名命中 `card_number/pan/cvv/token/secret/password` 一律丢弃并标 `redacted=True`;卡号与凭证值绝不进审计 |
## 2. 改了什么(本轨交付物)
- `cloud/chv/checkout_detect.py`(新,标准库):`CheckoutVerdict` + `detect_checkout(url, a11y_nodes)`(URL 信号 + 无障碍树信号 + 金额文本抽取;支付字段即 `form_risk=payment`、密码字段即 `form_risk=password`;页面文本只作展示信号,扣款金额永远以 `PurchaseDetails` 为准——页面文本是不受信任输入,见 P6 口径)。
- `cloud/chv/payments.py`(新,标准库):`PaymentCredentialVault` + `PaymentRelease` + `OneTimeCard` + `SimulatedWalletBackend` + `PurchaseDetails`(`digest()` 防篡改)+ `SpendLimits` + `PaymentAudit` + `PaymentGate`(编排:检测 → 路由 → 限额 → 审批 → 铸卡 → 兑现校验 → 记账 → 审计)。
- 接线(`cloud/chv/approval_channel.py`,最小扩展):`ApprovalRequest` 增加可选 `details` 字段,`payload()` 有 details 时才带上;`request_approval` 增加可选 `details` 参数;`verify_answer`/`resolve_via_channel` 一字不动;P5 既有测试不受影响。
- `cloud/tests/test_chv_muse_p10_checkout.py`、`test_chv_muse_p10_payments.py`、`test_chv_muse_p10_gate.py`(新测试,含否定测试:无审批不支付、审批详情被篡改即拒、一次性卡号换商家/超额/过期/复用被拒、原始卡号拒绝入库、凭证泄露面为零、对话内伪造批准无效、答复重放无效、超限额审批前即拒、限额不可用时不编造上限、取消即 void 卡)。
## 3. 诚实边界(本轮不宣称的东西)
1. **真实支付不做,零真实扣款**:全部用沙箱/模拟钱包与假卡号
(Stripe 测试卡号段 `400000000000xxxx`)验证,不联网、不调
任何真实钱包接口。真实 Link / Shop Pay 集成(wallet tools
是主应用运行时能力,不在 `cloud/chv` 代码里)列为**待用户
另行批准**的事项,本轨不产生任何真实扣款、不持有任何真实
卡号。
2. **一次性卡号的“发行”在本轨是模拟**:真实一次性虚拟卡由
Link 在其侧发行(连接器文档原文:“Link funds a one-time
virtual card”);本轨的 `OneTimeCard` 是绑定(商家 + 金额
+ 货币 + 有效期)与兑现校验的等价模型,不是发卡行。不要把
“模拟钱包铸了测试卡号”写成“已接通 Link 发卡”。
3. **购买保障只作口径引用**:Link 购买保障(损坏/丢失、降价、
免费退货、退货保证)的 eligibility 由 Link 判定、理赔走
Link;本轨不实现理赔流程、不承诺任何一笔购买受保障,保障
条款以 Link 官方页面为准。
4. **结账检测是启发式,不是 100% 检出**:URL 特征 + 无障碍树
特征;商户自定义结账页、单页应用内嵌结账可能漏检。漏检时
的安全底线是:无审批的支付一律拒绝(`PaymentGate` 拒绝
任何未经审批的扣款),不宣称“所有结账页都会被拦下”。
5. **限额检查依赖钱包侧返回的限额**:限额不可用时不编造上限,
只记 `limits_checked=False`;审批仍须过。不要把“限额未知”
写成“限额已校验通过”。
6. **钱包路由优先级按连接器文档现状**:报告(2026-11-01)写
Shop Pay“即将来临”,连接器文档(当前)显示 Shop Pay 已可
用;本轨按连接器文档现状写 stripe-link 优先、shop-pay 次之,
并标注了这一时间差;若连接器文档滞后于实际,以实际钱包
能力为准。
## 4. 真机待验证清单(未做,排队在 round-11 与 P4~P9 之后)
1. P4 的 broker 接线:高风险表单提交前调 `detect_checkout`,
结账页命中时 `open` 导航返回 `ask` 并暂停(与 P4 差距表
§4 的 `form_submit` ask 种类对齐)。
2. 真实钱包集成(待用户另行批准):wallet tools 的
`list_payment_methods` / 审批单 / `approved_card` 回包与
`PaymentGate` 的对接;`payment_method_changed`(用户在
审批单里换卡)时详情摘要重算与重新审批。
3. 真实商户结账页上的检测命中率抽查(Shopify / Stripe
Checkout / 常见电商各一例),漏检样例记入回归。
4. 限额读取:`wallet.get_user_info` 的三档限额与本轨
`SpendLimits` 的对接;限额不可用时的表现。
5. 取消与未知结果:取消待支付 void 虚拟卡;结果未知时不重试
的端到端表现(须真实钱包侧配合)。