ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

凌晨四点,我的 Claude Trace 日志泄露了用户手机号:一次 GDPR 合规排查与配置加固实录

凌晨四点,我的 Claude Trace 日志泄露了用户手机号:一次 GDPR 合规排查与配置加固实录 1. 凌晨四点的告警Claude Trace 日志为什么会写进手机号凌晨四点被安全组电话叫醒说生产环境的 Trace 日志里出现了完整的用户手机号这种体验一次就够了。问题不在 Claude 本身而在于我们默认把「调试方便」放在了「数据最小化」前面Trace 会把 Prompt、补全内容、工具调用参数、甚至中间变量原样落盘而用户画像、订单备注、客服对话里天然带着手机号、身份证号、邮箱这类 PII。GDPR 第 32 条要求的是「适当的技术与组织措施」日志里明文存 PII 基本等于没措施。这篇记录的是我实际走过的一遍排查与加固流程先定位是哪条链路把 PII 写进 Trace再用可复制的脱敏配置把风险压下去最后用 Trace 回放验证脱敏真的生效而不是「配置写了就当生效」。适合正在用 Claude 做生产应用、又需要过 GDPR 或内部合规审计的团队。核心检索词就三个Claude Trace 日志、GDPR 合规、日志脱敏配置。先说结论避免你走弯路Trace 脱敏不能只靠「采样率」或「少记点」结构化数据JSON、XML在语义分块下往往整块保留采样对它几乎无效。真正有效的是在写入前做字段级脱敏并且把脱敏逻辑放在统一入口而不是散落在各个业务代码里。2. 前置准备用 TaoToken 统一 Key 接入把脱敏放在入口排查时最痛的一点是Key 散落在多个服务、多个环境变量里你根本不知道哪条链路在写日志。所以第一步不是改代码而是把模型调用收敛到一个统一入口。我用 TaoToken 做统一接入官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 这个不加 UTM。统一入口的好处是脱敏、审计、限流都能在一层做Trace 写盘前必然经过它。你需要先拿到 Key去控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 然后在 API Keys 页面生成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。Key 只放服务端环境变量别写进前端或提交到仓库。接入文档在这里https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。如果你用的是 Claude Code 这类编码 Agent长期跑建议用 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 比按次调用更稳。想先验证模型行为可以直接在模型对话里试https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。注意统一 Key 不等于自动合规。它只是让你有一个「必经之路」可以挂脱敏中间件。脱敏规则本身还得你自己定义。3. 可复制配置settings.json 与 config.toml 脱敏骨架下面这套配置是我实际在用的骨架分两部分Claude 客户端侧的 settings.json 控制 Trace 行为服务侧的 config.toml 控制脱敏规则。你可以直接抄结构把正则换成你们业务里的 PII 模式。3.1 settings.json关掉全量 Trace开启写入前脱敏钩子{ trace: { enabled: true, mode: redacted, dir: /var/log/claude/trace, file_permission: 440, max_field_length: 512, drop_raw_prompt: true, redaction_hook: /opt/claude/hooks/redact.py, sampling: { enabled: false, note: 结构化数据下采样不可靠改用字段级脱敏 } }, api: { base_url: https://taotoken.net/api, key_env: TAOTOKEN_API_KEY, timeout_ms: 30000 } }关键点解释drop_raw_prompt设为 true意味着原始 Prompt 不落盘只落脱敏后的版本redaction_hook指向一个可执行脚本Trace 在写盘前会调用它file_permission设成 440避免日志被其他进程读取。sampling.enabled我直接关了因为前面说过采样对结构化 PII 基本无效留着只会给人虚假的安全感。3.2 config.toml字段级脱敏规则[redaction] mode strict fail_closed true [[redaction.rules]] name cn_mobile pattern (?!\\d)1[3-9]\\d{9}(?!\\d) replace [PHONE] [[redaction.rules]] name email pattern [A-Za-z0-9._%-][A-Za-z0-9.-]\\.[A-Za-z]{2,} replace [EMAIL] [[redaction.rules]] name id_card pattern (?!\\d)[1-9]\\d{5}(19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx](?!\\d) replace [ID] [[redaction.rules]] name json_pii_field pattern \(phone|mobile|id_card|email|address)\\\s*:\\s*\[^\]*\ replace \$1\:\[REDACTED]\ [redaction.audit] enabled true log_redaction_events true store_original falsefail_closed true很重要如果脱敏脚本执行失败Trace 直接拒绝写盘而不是「失败就写原文」。这是合规里典型的 fail-safe 设计。store_original false保证不保留原文副本否则脱敏等于白做。3.3 脱敏钩子脚本骨架#!/usr/bin/env python3 import json, re, sys RULES [ (re.compile(r(?!\d)1[3-9]\d{9}(?!\d)), [PHONE]), (re.compile(r[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,}), [EMAIL]), (re.compile(r(?!\d)[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx](?!\d)), [ID]), ] def redact_text(s: str) - str: for pat, rep in RULES: s pat.sub(rep, s) return s def walk(obj): if isinstance(obj, dict): return {k: walk(v) for k, v in obj.items()} if isinstance(obj, list): return [walk(v) for v in obj] if isinstance(obj, str): return redact_text(obj) return obj def main(): raw sys.stdin.read() try: data json.loads(raw) except json.JSONDecodeError: sys.stdout.write(redact_text(raw)) return sys.stdout.write(json.dumps(walk(data), ensure_asciiFalse)) if __name__ __main__: main()这个脚本对嵌套 JSON 做递归遍历字符串字段统一过正则。实测下来它对「手机号藏在 address 字段里」这种拼图式泄露也能拦住因为它是按值匹配不依赖字段名。4. 验证请求用 Trace 回放确认脱敏真的生效配置写完不代表生效必须用真实请求验证。我用的方法是构造一条带 PII 的测试请求走统一入口然后回放 Trace 日志检查落盘内容里还有没有明文。4.1 构造测试请求export TAOTOKEN_API_KEY你的Key curl -s https://taotoken.net/api/v1/messages \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet, messages: [ {role: user, content: 用户张三手机号13812345678邮箱zhangsanexample.com请生成一段客服回复} ] }4.2 回放 Trace 并检查TRACE_FILE$(ls -t /var/log/claude/trace/*.jsonl | head -1) echo 检查文件: $TRACE_FILE grep -E 13812345678|zhangsanexample.com $TRACE_FILE echo 存在明文PII脱敏未生效 || echo 未发现明文PII脱敏生效 grep -o \[PHONE\]\|\[EMAIL\]\|\[ID\] $TRACE_FILE | sort | uniq -c预期结果是第一条 grep 无输出第二条能看到[PHONE]和[EMAIL]的计数。如果第一条有输出说明脱敏钩子没被调用或者规则没覆盖到。4.3 用模型对话做交叉验证有时候 Trace 脱敏生效了但模型返回内容里还带着 PII这属于另一类风险。可以在模型对话里直接测https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 输入同样的测试数据看返回是否回显了手机号。如果回显了说明你的 Prompt 里就不该带原始 PII应该在调用前先脱敏。5. 本篇常见错排查5.1 脱敏脚本没执行日志里还是明文最常见原因是redaction_hook路径不对或者脚本没有执行权限。检查ls -l /opt/claude/hooks/redact.py chmod x /opt/claude/hooks/redact.py另外确认 Trace 进程有权限读取该脚本容器环境下要挂载进去。5.2 采样开了但 PII 还是泄露这就是我踩过的坑以为降低采样率能减少 PII结果结构化数据整块保留。解决办法是关掉采样改用字段级脱敏。如果你确实需要采样来控制日志体积至少要对结构化字段做单独处理。5.3 正则匹配不到嵌套 JSON 里的手机号如果手机号是数字类型而不是字符串正则匹配不到。需要在脱敏脚本里先把数字转成字符串再匹配或者对字段名做白名单处理。我的做法是对phone、mobile、id_card这类字段名直接整字段替换不依赖值匹配。5.4 脱敏后日志无法调试这是合规和可调试性的经典矛盾。我的折中方案是保留字段名和结构只替换值并且记录original_length元数据。这样你还能看出「这里原本有个 11 位的东西」但不暴露具体内容。审计时如果需要可以走单独的审批流程去查原始数据而不是让原始数据默认躺在日志里。5.5 GDPR 合规验证动作清单排查完配置建议按这个清单过一遍确认 Trace 目录权限是 440确认store_original为 false确认脱敏钩子 fail_closed确认日志保留周期有上限确认访问日志的人有审计记录确认 DPO 知道这套流程。这几条做完基本能应对内部审计。6. 长期编码与 Agent 场景的接入建议如果你是用 Claude Code 或类似 Agent 做长期编码Trace 日志量会更大PII 风险也更高。建议用 Coding Plan 做统一接入https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 把脱敏中间件挂在 Agent 和模型之间。Claude Code 的 Anthropic 兼容接入可以参考https://taotoken.net/claude-code?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。最后说一个我实际用下来的小技巧把脱敏规则做成可热更新的配置文件而不是硬编码在脚本里。这样业务新增 PII 类型时不用重启服务就能生效。另外每周跑一次 Trace 回放扫描用 grep 找明文 PII比等安全组告警要主动得多。凌晨四点的电话能少接一次是一次。
返回列表