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:
1 parent
90c42f6e1d
commit
b55b49b358
7 files changed
+1939
-8
No files matched your search
@@ -11,6 +11,9 @@
|
||||
"reasons": [str, ...], "expires_at": float}
|
||||
``target`` 只有目标描述(连接器/方法/scheme/主机/端口/路径),
|
||||
``reasons`` 只有原因名;载荷里绝无凭证值与用户数据内容。
|
||||
P10 扩展:可选 ``details`` 字段(支付审批的确切购买详情,
|
||||
商家/商品/金额/货币,供客户端 UI 展示;不传时 payload 与
|
||||
P5 完全一致)。
|
||||
``nonce`` 是随机 16 字节 hex(32 字符),每次请求唯一。
|
||||
``expires_at`` 默认请求后 15 分钟(900 秒),可注入时钟。
|
||||
- 答复(``Answer``,客户端答完直接回 Sentinel)::
|
||||
@@ -72,38 +75,50 @@ class ApprovalRequest:
|
||||
reasons: Iterable[str] | None,
|
||||
nonce: str | None = None,
|
||||
now: float | None = None,
|
||||
ttl_seconds: int = DEFAULT_TTL_SECONDS) -> None:
|
||||
ttl_seconds: int = DEFAULT_TTL_SECONDS,
|
||||
details: dict | None = None) -> None:
|
||||
self.pending_id = pending_id
|
||||
self.target = dict(target)
|
||||
self.reasons = list(reasons or [])
|
||||
self.nonce = nonce or secrets.token_hex(16)
|
||||
at = now if now is not None else time.time()
|
||||
self.expires_at = float(at) + ttl_seconds
|
||||
# 可选的确切详情(P10 支付审批用:商家、商品、金额、
|
||||
# 货币——供客户端 UI 展示;无 details 时 payload 与
|
||||
# P5 完全一致)。只含描述字段,无凭证值。
|
||||
self.details = dict(details) if details is not None else None
|
||||
|
||||
def payload(self) -> dict:
|
||||
"""可外发给客户端的请求载荷(只含描述字段 + nonce +
|
||||
过期,无密钥、无凭证值、无用户数据内容)。"""
|
||||
return {"pending_id": self.pending_id, "nonce": self.nonce,
|
||||
"target": dict(self.target),
|
||||
"reasons": list(self.reasons),
|
||||
"expires_at": self.expires_at}
|
||||
过期,无密钥、无凭证值、无用户数据内容)。有 details
|
||||
时带上(支付审批的确切购买详情)。"""
|
||||
out: dict = {"pending_id": self.pending_id, "nonce": self.nonce,
|
||||
"target": dict(self.target),
|
||||
"reasons": list(self.reasons),
|
||||
"expires_at": self.expires_at}
|
||||
if self.details is not None:
|
||||
out["details"] = dict(self.details)
|
||||
return out
|
||||
|
||||
|
||||
def request_approval(queue_record: dict, secret: str,
|
||||
now: float | None = None,
|
||||
ttl_seconds: int = DEFAULT_TTL_SECONDS
|
||||
ttl_seconds: int = DEFAULT_TTL_SECONDS,
|
||||
details: dict | None = None
|
||||
) -> ApprovalRequest:
|
||||
"""为一条队列记录生成通道请求并登记(nonce + 过期)。
|
||||
|
||||
``secret`` 是调用方持有的通道密钥:本模块不保存它、不进
|
||||
载荷、不进日志;答复方用同一密钥签名,校验时对上才算数。
|
||||
``details`` 可选(P10 支付审批的确切购买详情,进 payload
|
||||
供客户端 UI 展示;不传时行为与 P5 完全一致)。
|
||||
返回的 ``ApprovalRequest.payload()`` 即发给客户端的东西。
|
||||
"""
|
||||
pending_id = queue_record["pending_id"]
|
||||
req = ApprovalRequest(
|
||||
pending_id, _target_from_record(queue_record),
|
||||
queue_record.get("reasons", []),
|
||||
now=now, ttl_seconds=ttl_seconds)
|
||||
now=now, ttl_seconds=ttl_seconds, details=details)
|
||||
_REQUESTS[pending_id] = {"nonce": req.nonce,
|
||||
"expires_at": req.expires_at}
|
||||
return req
|
||||
|
||||
Reference in new issue
Block a user