用户报告"MPChat 卡付款没成功"。值班工程师开始捞日志。
然后他看到了最不想见的东西:某个半年前赶工的模块里赫然写着log.info(request_body)。完整卡号、CVV、用户邮箱,全部明文躺在那儿。更要命的是,这条日志已经顺着链路流进了死信队列、APM 监控面板,甚至挂在了客服工单的附件上下文里。
正式链路大家都知道小心处理敏感字段。但排障环节往往是另一套逻辑——出了事,先把信息捞全再说。信息是捞全了,合规的底线也一起捞穿了。
以下以 MPChat 卡片交易排障为业务场景,讨论支付日志脱敏的通用设计落地。仅供架构说明,不代表 MPChat 现网接口或内部实现。
支付日志到底需要记录哪些字段
工程师真的需要看完整请求体吗?
以 MPChat 的卡片交易为例,第一轮排查通常只问这几件事:什么时候发生的,多少钱什么币种,走的哪条渠道,归一化状态是什么,错误码是哪个,以及一个能串联上下文但看不出原始交易号的追踪键。
六样东西。没有一样需要 CVV,没有一样需要完整卡号。
按照 PCI DSS 合规要求和 OWASP 日志安全建议,字段可以分三层处理:
| 层级 | 处理方式 | 典型字段 |
| 可记录 | 明文写入排障日志 | 时间戳、金额、币种、归一化状态、受控错误码 |
| 需变换 | HMAC 单向哈希后记录 | 内部交易 ID → trace_key |
| 禁止记录 | 不进入排障日志 | CVV、完整卡号(PAN)、验证码、完整请求体、用户邮箱 |
容易踩坑的是那些看起来人畜无害的自由文本。商户显示名、报错描述,随时可能夹带用户的邮箱或住址。把自由文本直接视为安全字段,是一类很隐蔽的支付系统隐患。
PCI 安全标准对这件事没有商量余地:CVV 等敏感认证数据,授权完成后不得留存。"我加密保存了"不构成保留理由。OWASP 的日志指引同样将令牌、卡片数据和个人标识划在红线之外。拿"排查 bug 需要"做挡箭牌,在安全审计面前站不住脚。
Python 实现:白名单在生成侧,不在展示侧
很多团队的做法是先把全量数据存下来,再依靠前端展示时打码。
这个思路反了。
打码是一层表皮,数据库里躺着的还是明文。运维有权限,死信有备份,监控有快照。只要底层存了,总有一天会被不该看到的人看到。更可靠的做法是,在排障事件产生的那一刻,就只放行白名单里的字段。
以下是一段 Python 数据脱敏的参考实现:
python
import hashlib import hmac import re ALLOWED_STATUS = {"DECLINED", "AUTHORIZED", "SETTLED"}ERROR_CODE = re.compile(r"^[A-Z0-9_]{1,40}$") def support_event(raw_event: dict, secret: bytes) -> dict: """ 仅供架构说明,不代表 MPChat 现网接口或内部实现。 secret 应由受控密钥系统提供,与业务代码物理隔离。 """ status = raw_event.get("status") code = raw_event.get("error_code") if status not in ALLOWED_STATUS: raise ValueError("unsupported status") if not isinstance(code, str) or not ERROR_CODE.fullmatch(code): raise ValueError("invalid error code") internal_id = str(raw_event["internal_transaction_id"]) trace_key = hmac.new( secret, internal_id.encode("utf-8"), hashlib.sha256, ).hexdigest()[:24] return { "trace_key": trace_key, "occurred_at": raw_event["occurred_at"], "amount": raw_event["amount"], "currency": raw_event["currency"], "status": status, "error_code": code, }看输出字典:没有 cvv,没有 pan,没有 email,没有 request_body,也没有自由文本的错误描述。哪怕上游对象带着这些雷区字段过来,输出这一层也不接收。
trace_key只做内部哈希关联,不当做可公开的流水号外传。密钥管理、访问控制、保留周期、导出审计需要另行设计,不在这段代码的射程里。
客服截图:日志之外的敏感数据暗门
后端日志做干净了,客服系统大概率还在漏。
MPChat 的用户提交一张报错截图,经常连着收件地址、短信验证码和其它消费记录一起截了进来。这条数据入口,很多团队的威胁建模里根本没有它的位置。
处理截图的入水口,要设三道关卡:
提交前——前端强提示用户自行裁剪与遮挡,明确告知不收集 CVV 和验证码。数据进来之前拦,成本最低。
接收后——图片直接进受限存储桶,设置独立权限与保留期限。绝不能混入通用日志池,也不能被全文检索引擎爬到。
查看时——客服按工单维度拉取必需材料,访问与导出全程留审计痕迹。
自动 OCR 识别可以做辅助预警,但别承诺"机器已经全部识别干净"。检测失败的时候,人工复核与硬删除的路径必须可用。承诺百分百识别率,等于给自己埋了一份免责失效的隐患。
卡片交易状态判读:脱敏之后照样能排查
最小暴露原则不是让工程师蒙着眼睛修 bug。
排障系统里仍然要保留足以区分业务走向的信息。以 MPChat 卡片交易的三种典型状态为例,掩码做到位,工程师和客服依然能凭状态给出准确判断:
| 状态 | 卡片侧含义 | 不能直接推出的结论 |
| DECLINED | 商户未收到该笔付款 | ≠ "系统故障",需查具体拒绝原因 |
| AUTHORIZED | 资金处于挂账占用期 | ≠ "交易完成",不能催用户重新付款 |
| SETTLED | 底层资金已结算 | ≠ "业务权益已发放",订单侧需另行核对 |
客服看到 SETTLED 就回复"会员已开通",看到 AUTHORIZED 就让用户重试——这两种操作在生产环境里都出过事故。状态和业务结论之间隔着一层,跳过去就是误判。
一份合格的排障材料,底色是让工程师迅速查明异常,同时让用户体面地保留自己的隐私。两件事不矛盾,只是需要在系统设计的时候就想清楚,而不是出了审计问题再补。