最容易泄露隐私的那次,往往不是你在“传数据”,而是你觉得自己只是在“问个问题”。
线上一出事,人会本能地求快:Nginx 日志复制一段,后端错误栈贴一截,再把客服截图转写的文字补进去,丢给 LLM 问一句“这到底是鉴权、跨域还是回源超时”。动作很顺,风险也常常就在这一步里混进去。
前阵子我帮老大查一段异常登录问题,原本只想把报错上下文整理给模型看看。贴到一半我才发现,日志里不只有 path 和 status,还带着 Authorization: Bearer ...,错误栈里挂着用户邮箱,截图转写里甚至抠出了一个手机号。那一瞬间有点后背发凉:这些东西一旦发出去了,你很难再说清它后来经历了什么。
技术人最常在哪些场景把 PII/密钥塞进 prompt?
短答:不是你“主动写了隐私”,而是你复制的材料里,自带了“能追溯到具体人、账号或系统权限”的线索。
我把高频场景按“最容易被忽略”排了一下。你可以直接对照自己的工作流看,最常踩的是哪一类:
| 场景 | 你以为在发什么 | 实际可能夹带什么 | 为什么容易漏 |
|---|---|---|---|
| 工单/客服记录 | 用户描述 + 复现步骤 | 邮箱、手机号、订单号、地址、身份证后四位 | 工单模板字段太多,复制时不自觉全选 |
| 线上日志(Nginx/应用) | 请求路径 + status + latency(延迟) | Authorization、Cookie、Set-Cookie、IP、session id | 日志“看起来”像纯技术信息 |
| 错误栈/trace | 调用链路 | 请求参数、SQL 片段、用户名、内网域名、文件路径 | 错误栈太长,没人逐行读 |
| 截图转写(OCR) | 一张“报错截图” | 账号名、邮箱、手机号、工号、二维码内容 | OCR 会把你以为“看不清”的字补全 |
| CRM/表格片段 | 客户标签、跟进记录 | 全名、公司、电话、地址、合同编号 | 复制一行经常带出整列 |
| 配置片段/环境变量 | 一段 .env 或 CI 配置 | API Key、Token、Webhook(有事自动通知你的钩子)URL | 你只想问“这变量啥意思”,却把值也贴了 |
这里有个很容易误判的地方:很多“敏感信息”,外观看起来根本不像敏感信息。
一串随机字符,可能是 GitHub Token、Stripe Key、云厂商密钥;一个很普通的 URL,里面可能藏着 ?token=、?signature=,甚至是临时授权参数。你会觉得自己已经很谨慎了,因为你没贴身份证号。可真出事时,最先让人头疼的,常常是那串“你以为只是调试用”的 Token。
为什么“我没写隐私”也会中招?
短答:因为 PII 和 Secrets 往往不是你主动输入的,而是被上下文裹挟进来的。
你在排障时复制粘贴的对象,通常有三个共同点:
- 它们是机器生成的:日志、headers、trace、调试输出,本来就倾向把上下文吐全。
- 它们跨系统拼起来才有意义:单看一段 cookie 没什么,但和域名、路径、时间戳拼起来,就可能复现会话。
- 它们往往“差一点就有权限”:Token 不一定是 root,但足够访问你不想外发的系统。
所以我现在更愿意把“把内容发给 LLM”这件事,当成一次对外发包。它不是普通问答,而是出站数据。
有次在 Discord 里也有人问我:“我只是贴了个报错,为什么合规同事比我还紧张?” 我当时第一反应是,这是不是有点过度了。后来回头看几次事故复盘,我又不敢把话说太满:很多风险不是来自模型“记住了什么”,而是你在最忙的时候,把不该离开电脑的东西顺手发出去了。
发出前自检怎么做,才算真的拦在电脑上?
短答:用“本地扫描 + 分级拦截 + 脱敏替换”三件套,把风险从聊天框前移到输入口。
下面这段你可以直接转给同事,当成共识版说明:
在团队里做 LLM 安全,最有效的不是背规范,而是在“发送 prompt 之前”加一道本地扫描:用 PII 检测器抓邮箱、手机号、身份证类信息,用 Secrets 扫描器抓 API Key、Token、私钥等高危字段;命中红线就直接拦截,命中灰线给替换建议,并保留脱敏后的版本。这样不依赖云端策略,也不把原文上传给第三方,成本低,见效也快。
一套免费的“本地 PII + Secrets 扫描”工具组合
我不打算在这篇里硬塞一个“神秘新工具”。现实里,大多数团队把下面两类开源工具拼起来,已经够用:
- PII:Microsoft Presidio(开源 PII 识别与脱敏框架)
官方链接:https://github.com/microsoft/presidio - Secrets:Gitleaks(开源 Secrets 扫描)
官方链接:https://github.com/gitleaks/gitleaks
两者定位不一样:
| 工具 | 擅长抓什么 | 不擅长什么 | 更适合放哪 |
|---|---|---|---|
| Presidio | 邮箱、手机号、姓名、地址等“像 PII 的文本” | 各家供应商千奇百怪的 Key 形态 | 浏览器/IDE/CLI 的“文本入口” |
| Gitleaks | Token、API Key、私钥等“像 Secrets 的串” | 自然语言里的个人信息 | pre-commit / 粘贴前 / 工单导出前 |
你不一定两个都上。如果你眼下最怕的是“把 Key 发出去”,那先上 Gitleaks,收益通常是最直观的。
方法论:把“发送前体检”接进浏览器 / CLI / IDE
我这里给的是一套“最小可用”流程:今天下班前装上,明天就能少一次背锅概率。
第 1 步:先定红线字段,命中就拦截
先别追求完美识别,先把“命中就不能外发”的内容列出来。我的建议是:
- 禁止外发:
API Key / Token / 私钥 / cookie / Authorization header / 数据库连接串 / 真实手机号邮箱 / 身份证类 - 需脱敏后可发:IP、内网域名、用户名、订单号、精确地址
- 可直接发:不含用户数据的通用错误信息、经过裁剪的调用栈(去参数)、去标识化的统计数据
你可以用下面这张分级表当团队共识模板:
| 分级 | 规则 | 处理方式 | 例子 |
|---|---|---|---|
| L0 可发 | 不含任何可识别个人/账号/权限的信息 | 直接发 | 纯错误信息、无参数的栈顶几行 |
| L1 需脱敏 | 含弱标识或可关联信息 | 替换后发 | IP、用户ID、内网域名、订单号 |
| L2 禁止外发 | 含 Secrets 或强 PII | 直接拦截 | Token、私钥、cookie、手机号邮箱、身份证类 |
关键点就一句:L2 不讨论。
别把“我感觉应该没事”当成策略。
第 2 步:CLI 版——最快落地,粘贴前先跑一遍
如果你经常从终端复制日志、headers、调试输出,我更建议从 CLI 开始。成本低,不用改 IDE,也最容易先跑起来。
- 安装 Gitleaks:https://github.com/gitleaks/gitleaks#installation(按官方指引安装)
- 用法思路:把准备发出去的文本暂存到文件,先扫一遍,再决定要不要贴
示例(Mac/Linux):
bash# 1) 把要发的内容放到 prompt.txt(你可以用任意编辑器)
# 2) 扫描
gitleaks detect --no-git --source . --report-format json --report-path gitleaks-report.json
# 3) 如果 report 里有命中,就别发;先脱敏再说
cat gitleaks-report.json
Windows(PowerShell)思路一样:把内容存成文件,再跑 Gitleaks;命令参数不用变,主要差别只是你怎么创建和编辑 prompt.txt。
你做对了之后,大概会看到两种结果:
- 没命中:report 基本为空,或者没有 findings
- 命中:会列出规则名、位置、疑似 secret 片段
这里顺手提醒一句:别把 report 本身直接发出去。 有些报告会包含原始命中串,等于你又泄露了一次。
第 3 步:IDE 版——把扫描变成保存/复制时的习惯动作
IDE 里我更倾向两条路:
- 轻量方案:用
External Tool / Task绑一个脚本,对当前选中文本或临时文件做扫描 - 稳一点的方案:用 pre-commit,在提交前先拦住,避免你把 secrets 永久写进仓库
pre-commit 官方:https://pre-commit.com/
Gitleaks 的 pre-commit 集成说明,在它的仓库文档里也能找到。
你真正要的,不是“每次扫全仓库”,而是把最危险的外发材料先拦住。尤其是 .env、日志片段、CI 输出,这三类最容易在手忙脚乱时被顺手贴出去。
第 4 步:浏览器 / 聊天框版——做一个“粘贴拦截器”
这一步最贴近真实现场,也最接近“发送前体检”的完成形态。做法通常是:
- 监听粘贴事件(paste)
- 把剪贴板文本喂给本地扫描器
- 命中 L2 就弹窗拦截,命中 L1 给一键脱敏替换
我这里不展开贴浏览器扩展代码,一是会太长,二是每个人的技术栈差别很大。比起代码,我更建议你先把验收标准定死:
- 命中
Authorization:/Bearer/BEGIN PRIVATE KEY:必须拦截 - 命中邮箱/手机号:默认拦截或强提醒(看团队合规要求)
- 支持“替换后重新粘贴”:比如
sk_live_...→sk_live_[REDACTED]
判断这一步有没有真正做到位,只看一条:你能不能在最慌的时候,也被它拦住。
如果还需要你手动点一个“开始扫描”按钮,那大概率等于没有。
可复制:一份“脱敏替换”模板
你可以把下面这段直接当成自己的脱敏规则清单。不管你是写脚本、做插件,还是先让同事手动替换,思路都一样:
- 邮箱:
xxx@yyy.com→[EMAIL_REDACTED] - 手机号:
1xxxxxxxxxx→[PHONE_REDACTED] - 身份证/护照类:→
[ID_REDACTED] - Token/API Key:保留前后 3-4 位用于排查,其余打码
例:sk_live_1234567890abcdef→sk_live_1234...[REDACTED]...cdef - Header:整行替换
Authorization: Bearer ...→Authorization: [REDACTED] - Cookie:整段替换
Cookie: a=b; session=...→Cookie: [REDACTED]
如果你准备在团队里推这件事,我建议把“保留前后几位”写进规范。要不然真到排障时,大家很容易图省事,又把完整串贴出来求快。
这事怎么和日志打码、Secrets 管理接起来,才不打架?
很多人会把“提示词防泄露”当成一次性项目:做完一个扫描器,事情就算结束。过几周再看,才发现最该打码的源头还在持续往外喷。
我更推荐这个顺序:
- 先在“外发口”加扫描,立刻止血
- 再在“日志源头”做打码,减少以后每次复制都踩雷
- 最后把 Secrets 收进专门的管理工具,别继续散落在文档、聊天群和脚本里
Secrets 管理这块,你们公司可能已经有现成方案,比如云厂商的 Secret Manager、Vault 之类。我不替你选型,但我有个很现实的判断标准:
如果一个 Token 能轻易被复制进聊天框,它也一定能被复制到别的任何地方。
所以“外发前扫描”不是用来替代 Secrets 管理的,它只是承认一件事:人会犯错,而且通常是在最赶的时候犯错。
最后,先别追求完美
你不需要把每个同事都训练成“永不手滑的人”。你只需要先把第一道门装上,让“手滑”发生时,内容还没离开电脑。
如果你现在还没开始做,这周最值得先落地的一步,可能不是上完整平台,而是先挑一个最常用的出口——终端、IDE,或者聊天框——把 L2 拦截跑起来。等它第一次真拦下一段 Bearer 或 cookie 时,你对这件事的优先级判断,大概率会立刻变掉。
FAQ
Q: 我用的是本地模型(Local LLM),还需要做 PII 扫描吗?
A: 也需要。本地不等于天然安全:内容可能进日志、缓存、共享盘,也可能被同事转发。扫描的价值,是把“敏感内容出现了”这件事提前显性化。
Q: 扫描会不会误报到我没法工作?
A: 会有误报,所以建议分级:L2 直接拦,L1 提示但可继续。先跑起来,再按“最常被拦住的内容类型”去补白名单和模板脱敏。
Q: 我能不能只靠提示词,让模型别记住/别处理敏感信息?
A: 不太靠谱。提示词管不住你已经发出去的原文,也管不住供应商侧的留存策略。更稳的还是“发出前本地拦截 + 脱敏替换”。