贴给 AI 之前的 30 秒:新手最容易漏掉的三样东西
贴给 AI 的日志里常常带着 .env 的值。这篇讲三步固定动作——只留能定位问题的最小集、把值换成占位符、贴前搜一遍,并附一个剪贴板一键脱敏的 zsh 函数:为什么只按 sk_live_、AKIA 这类前缀特征匹配,会让一串 32 位十六进制的密钥整行漏出去。

我有个笔记仓库,几乎所有内容都会被 Claude 读到:工作日志、身份档案、正在做的项目、没想通的半成品。我不介意,那本来就是我写给自己看的东西。
但同一台机器上还有 App Store 的证书、几个 SaaS 的生产凭据。这些一行都不进对话框。
我一开始以为做这个区分需要点安全知识,后来发现不需要。谁都知道密码不能发出去。真正会出事的是那个动作:
报错了 → 终端 Cmd+A、Cmd+C → 粘贴 → 回车。
四步,两秒完成。而那段终端输出里,可能就带着你 .env 的值。
泄露的形状通常是这样:发出去的是日志,密钥恰好在日志里。
要做的事只有一个:在那两秒中间,插进一个 30 秒的固定动作。
漏点在动作上
大部分安全清单按"信息类型"来列:密钥、用户数据、公司代码。分类没错,但拦不住人——没人会主动去发密钥,出问题的是没意识到某个动作会把密钥一起带出去。
所以换个列法,按动作列:
| 你做的动作 | 顺手带出去的东西 |
|---|---|
| 报错了,整段终端复制 | .env 的值、数据库连接串、内部域名、本机绝对路径(里面有你的用户名) |
| 功能不对,把整个文件拖进对话 | 数据库表结构、内部接口地址、注释里留的测试账号 |
| 接口返回不对,把 response 原样贴 | 真实手机号、邮箱、Authorization 请求头 |
| 让 AI "读一下整个项目"、开仓库索引 | 一次性全带走,包括你忘了还在根目录的那个 backup.sql |
第四个最危险,因为它连"复制"这个动作都省了。你只说了一句"帮我看看这个项目哪里有问题"。
这四条都不需要判断力。它们的共同点是中间没有停顿。

留、改、扫

第一步 · 留:只给能定位问题的最小集
怕上下文不够,于是整个文件、整个日志一起贴上去——这是最常见的做法。其实 AI 要的是结构,具体的值它用不上。
| 该给 | 不用给 |
|---|---|
| 报错的那个函数本体 | 整个文件、整个仓库 |
| 错误栈里的关键几行 | 从启动到崩溃的全部日志 |
| 字段名和类型 | 字段的真实值 |
| 运行环境、复现步骤、预期与实际 | 生产环境地址和凭据 |
判断标准就一句话:删掉它,AI 还能不能回答这个问题? 能,就删掉。
第二步 · 改:把值换成占位符,格式留着
不要直接删字段——删了 AI 就看不出配置的结构关系了。要替换:
# 不要这样
POSTGRES_URL=postgres://tinypa:[email protected]:5432/tinypa
APPSTORE_SHARED_SECRET=8f2c41ba9d7e4c1fa0b35e6d92c8a71b
HARMONY_CLIENT_SECRET=hm_sec_9K2mQx7ZpLd4RtV
# 这样
POSTGRES_URL=postgres://<DB_USER>:<DB_PASSWORD>@<DB_HOST>:5432/<DB_NAME>
APPSTORE_SHARED_SECRET=<APPSTORE_SHARED_SECRET>
HARMONY_CLIENT_SECRET=<HARMONY_CLIENT_SECRET>
.env 整个文件一键处理,一条命令:
sed -E 's/^([A-Za-z_][A-Za-z0-9_]*)=.*/\1=<VALUE>/' .env
输出长这样:
POSTGRES_URL=<VALUE>
APPSTORE_SHARED_SECRET=<VALUE>
DEBUG=<VALUE>
注意连 DEBUG=true 都被打掉了。这是故意的——宁可多打。你让 AI 看 .env,通常是想问"我是不是少配了一项""这个字段名对不对",这两个问题都只需要键名。
日志和接口响应更麻烦,因为敏感值散在中间。我给自己写了个 zsh 函数,从剪贴板读、脱敏、再写回剪贴板,贴到 ~/.zshrc 就能用:
scrub() {
pbpaste | sed -E \
-e 's/("?[A-Za-z_]*(secret|key|token|password|passwd)[A-Za-z_]*"?[[:space:]]*[=:][[:space:]]*"?)[^[:space:]",}]+/\1<VALUE>/gI' \
-e 's/(Bearer |bearer )[A-Za-z0-9._-]+/\1<TOKEN>/g' \
-e 's/(sk|pk|rk)_(live|test)_[A-Za-z0-9]+/\1_\2_<APIKEY>/g' \
-e 's/AKIA[0-9A-Z]{16}/<AWS_KEY>/g' \
-e 's#(://[A-Za-z0-9._-]+):[^@/]+@#\1:<PASSWORD>@#g' \
-e 's/[0-9]{17}[0-9Xx]/<IDCARD>/g' \
-e 's/1[3-9][0-9]{9}/<PHONE>/g' \
-e 's/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/<EMAIL>/g' \
| pbcopy
echo "已脱敏,可以粘贴了"
}
用法:复制日志 → 终端敲 scrub → 直接粘贴给 AI。
实测效果,输入:
{"uid":"usr_7f3ad2","plan":"pro_monthly","bindPhone":"18700001234","notifyEmail":"[email protected]","authorization":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.pR0d-t0k3n_x"}
输出:
{"uid":"usr_7f3ad2","plan":"pro_monthly","bindPhone":"<PHONE>","notifyEmail":"<EMAIL>","authorization":"Bearer <TOKEN>"}
三个说明,都是我实际踩出来的。
一是第一条规则必须放在最前面,而且它是最重要的那条。我最早的版本没有它,只按格式特征匹配(sk_live_ 开头、AKIA 开头、Bearer 后面),结果这一行完整地漏了出去:
APPSTORE_SHARED_SECRET=8f2c41ba9d7e4c1fa0b35e6d92c8a71b
它就是一串 32 位十六进制,没有任何前缀特征,正则认不出它和一个 commit sha 有什么区别。补上"字段名里带 secret / key / token / password 就把值打掉"这条,才抓住。代价是它比较粗:APPSTORE_SHARED_SECRET=sk_live_xxx 也会被整体打成 <VALUE>,看不出原来是 live 还是 test 环境。我认这个代价——漏一个密钥比丢一点上下文贵得多。
顺带说一句,这条规则不会误伤 commit 42f3fb57... 和 userId,因为它只看字段名,不看值长什么样。
二是身份证那条必须排在手机号前面。反过来的话,18 位身份证号中间会先被 1[3-9][0-9]{9} 吃掉 11 位,输出变成 310101<PHONE>4 这种半残状态,比不脱敏更难看出问题。我第一版就是这个结果。
三是内部域名它管不了。db-prod-sg.internal 这种得你自己改,正则没法替你判断哪个域名是内部的。
Linux 上把 pbpaste/pbcopy 换成 xclip -o / xclip -selection clipboard。
第三步 · 扫:贴之前搜一遍
习惯总有漏的时候,最后用工具兜一遍:
rg -n -i --hidden -g '!.git' \
'secret|passwd|password|api[_-]?key|client[_-]?secret|shared[_-]?secret|token|cookie|authorization|BEGIN [A-Z ]*PRIVATE KEY' .
没装 rg 就用系统自带的:
grep -rniE 'secret|passwd|password|api[_-]?key|client[_-]?secret|token|cookie|authorization' .
如果只想检查本次改动:
git diff | grep -niE 'secret|password|token|api[_-]?key'
搜出结果不代表有问题,变量名叫 token 很正常。要看的是等号右边有没有真值。
打了码也可能还是认得出来
把手机号换成 138****0000 就算处理完了——这是最容易误判的地方。
看这段,一个人名都没有:
用户在 2026 年 8 月 20 日 18:32 下单,收货地址某市某区某街道 18 号,
金额 38,000 元,商品是某客户专属定制设备。
时间 + 地址 + 金额 + "专属定制",四个信息叠起来,在公司内部足够定位到具体是哪个客户。单看每一项都不算隐私,组合起来就是。

脱敏要达到的效果,是让数据没法指回某个具体对象,同时保住排查问题需要的结构。"看起来不像真数据"这个标准太松。三条具体做法:
- 删:跟当前问题无关的字段直接删,别留着"以防万一"。
- 降精度:精确时间 → "下单后约 3 分钟";详细地址 → 城市级;真实金额 → 数量级相近的整数。
- 断关联:占位符之间不要留能对上的编号。三条日志都写
<ORDER_ID>就行,别写<ORDER_1><ORDER_2>。
还有一个高频漏点:只改了正文,忘了请求头。手机号改了,Authorization: Bearer ... 还在下面躺着。
一个能直接抄的提问模板
线上接口报 500。不要这么问:
线上炸了,日志在这儿,你帮我看看:
[粘贴 800 行]
换成这样(照抄改字段就行):
帮我查一个订阅状态接口的 500。
接口用途:客户端拉取当前订阅状态,服务端要先校验 App Store 收据
运行环境:Node.js 22 + Postgres 16
复现条件:同一张收据连续请求两次,第二次必然 500,第一次正常
预期结果:返回 { plan, expiresAt }
实际结果:500,响应体是空的
脱敏后的错误栈(密钥、Token、手机号已替换成占位符):
[粘贴关键的那几行]
相关代码(只有报错的那个 handler 和它调的收据校验函数):
[粘贴]
请先按可能性排序列出原因,再告诉我按什么顺序验证。
信息不够就直接说还缺什么,不要替我猜测配置和环境。
最后那句很重要。不加它,AI 会自己编一套配置往下推,你还得花时间分辨哪句是它猜的。
如果已经贴出去了
第一反应通常是删聊天记录。但删掉记录只是你自己看不见了,东西已经出去了。已经发出去的密钥,就当它已经公开。
正确顺序,第一步就是让它失效:
- 立刻轮换或禁用这个密钥,先做这一步,别先查原因。
- 查这个凭据的访问日志和账单,看有没有异常调用。
- 把旧值从代码、日志、提交记录里清掉(
git log里也可能有)。 - 按团队流程报备,不要自己捂着。
- 新密钥配上去,顺手确认它没再进版本库。
顺手把工具设置确认一次
一次性检查,之后不用天天做:
- 现在登录的是个人账号还是公司账号?
- 对话内容会不会被用于改进模型,开关在哪?
- 编辑器插件的索引范围是当前文件还是整个 workspace?
- 团队到底允许不允许用这个工具处理源码?
把隐私开关全关严了,能贴什么仍然由公司制度和合规要求说了算。
贴墙清单
发之前扫一眼,全是勾就发:
- 没有密码、Token、Cookie、私钥
- 没有真实用户的手机号、邮箱、身份证、地址
- 没有生产环境的连接地址和内部域名
- 跟这个问题无关的内容已经删了
- 只改了正文的话,回头看一遍请求头
- 用的是符合团队规定的账号和工具
- 换成假数据之后,这个问题依然问得清楚
少贴一点、说得含糊一点,感觉上是更安全了,换来的是同样含糊的回答。问题没解决,下次只会更想把整个项目直接丢过去。
所以清单最后那条我用得最多:换成虚构数据还问得清楚吗。问不清楚,说明我自己还没想明白问题在哪,这时候贴多少上下文都没用。