本地Scrubber:发送文本给LLM前的敏感信息清洗指南 📅 发布时间:2026/9/2 3:54:11 👁 浏览次数: 这个项目标题非常直白一个在把文本发送给 LLM 之前先做本地清洗的工具也就是本地 scrubber。它解决的核心问题很多人其实都遇到过——你从文档、邮件、聊天记录、数据库里复制一段内容准备粘进 LLM 对话框或者通过 API 发出去但这段内容里可能带着邮箱、手机号、内部项目代号、甚至密钥片段。发送之前如果不处理这些信息就离开了本机进入第三方服务的处理链路。本地 scrubber 的定位就是在这一步之前把敏感字段识别出来并替换掉。我最初看到这个项目名的时候第一反应是“这会不会只是套了一层正则的替换工具”。实际想了一圈之后我的判断是它真正值钱的地方不在匹配规则有多全而在于它把“发送前检查”从自觉行为变成了一个确定性的工程步骤。只要你的工作流里经常需要把本地文本交给外部 LLM这个环节就值得认真补上。下面按“为什么需要、怎么搭、怎么判断、怎么排错、边界在哪”的顺序拆一遍。1. 先想清楚本地清洗到底在防什么1.1 文本离开本机之后你控制不了它很多人对 LLM 调用有一个误区以为只有点击“发送”按钮之后文本才算离开电脑。实际上只要你把内容粘贴到聊天框、通过脚本发起请求、或者让 agent 工具读取本地文件后拼接上下文这段文本就已经进入了完整的网络链路。它会被传输、可能被记录、可能参与模型服务的日志分析在 agent 场景里还可能继续传给下一个工具。这里不是针对某一家模型服务商而是数据范围的问题。你的文本一旦发出去后续是否被存储、是否被用于训练、是否出现在日志里基本都不由你决定。对于个人笔记、实验性代码、公开资料风险可以接受但对于客户信息、内部文档、未发布的方案、数据库导出内容把“减少外发数据量”作为默认原则是成本最低的防护方式。本地 scrubber 做的事情很简单在数据离开本机之前把可以识别的敏感内容替换成占位符。它不改写你的意图只降低外发内容的敏感度。1.2 本地 scrubber 和在线脱敏平台的关键差异市面上有很多在线脱敏平台上传一段文本它帮你识别并打码然后你拿打码后的结果去发送。听起来流程一样但这里有一个逻辑漏洞为了脱敏你先把原始文本上传给了另一个第三方服务。如果这个服务本身也值得信任那为什么不让 LLM 服务商直接处理呢脱敏平台并不能消除信任问题只是把信任从一个厂商转移到了另一个厂商。本地 scrubber 的差异在于识别和替换都在本机完成整个过程不产生额外外发流量。它的优势是明确的不依赖网络离线环境也能跑。规则是你自己维护的敏感字段清单不会被平台模板限制。可以嵌入到本地脚本、编辑器和命令行工具里。每次清洗结果都有记录便于事后核对。如果你追求的是“数据在本地处理完再外发”那本地 scrubber 才是真正符合这个目标的做法。1.3 它适合谁不适合谁适合的人群很具体自己写脚本调用 LLM API 的开发者用 AI 处理客户资料和内部文档的运营或产品人员搭建个人知识库并希望控制输入内容的人以及团队里希望给“外发文本”加一道统一检查的技术负责人。不适合的情况也要说清。如果敏感信息没有固定形态比如一段对话里用昵称指代某人或者一段描述里隐含了项目进展这类内容靠规则无法识别。另外如果你的团队受严格合规约束需要的是认证的数据防泄漏方案那 scrubber 只能作为辅助工具不能替代正式的安全体系。它本质上是工程层的卫生习惯不是合规证书。2. 一个本地 scrubber 的最小结构和运行条件2.1 核心组成识别、替换、预览、确认一个能用的本地 scrubber最少要包含四部分。第一部分是规则库决定“哪些内容算敏感字段”。第二部分是匹配器在文本里找出符合规则的片段。第三部分是替换器把这些片段替换成占位符或者删除。第四部分是预览确认把清洗前后的文本展示出来让用户决定是否发送。有些工具还会加第五部分格式保护。因为输入内容不只有纯文本可能是 Markdown、JSON、代码块或者日志替换时需要保证原有结构不坏。这五个模块缺一不可但优先级不同——最开始可以只做前三个预览和格式保护在真正使用时很快会变成刚需。2.2 运行环境和依赖怎么选本地 scrubber 通常不需要 GPU也不需要大内存。纯规则实现的话一个 Python 脚本或 Node.js 脚本就够运行在 Windows、macOS、Linux 上都行。依赖越少越好最好只依赖标准库里的正则模块这样部署时不用处理复杂的环境问题。如果你的规则里包含“高熵字符串检测”也就是识别 API 密钥这类没有固定前缀的随机串那需要额外写一段字符分布判断逻辑或者引入一个轻量检测库。如果你打算用 AI 模型辅助识别语义层面的敏感信息就要考虑本地模型推理的资源和耗时这一步会明显增加复杂度。我的建议是第一版先做纯规则跑通之后再决定要不要上 AI 辅助。2.3 输入输出格式先定好定义清楚输入输出能避免后面大量返工。输入不一定是单段文本还可能是文件、目录、CSV 行、JSON 结构。输出也不应该只是“清洗后的文本”最好同时产出一份匹配报告命中了哪些规则、替换了多少处、分别在哪里。这里要特别说一下 Markdown。很多人写 Markdown 时会包含链接、加粗、代码块和表格清洗如果直接把链接里的域名改了或者把代码块里的字符串替换成带空格的占位符Markdown 结构就坏了。LLM 接收时对 Markdown 的敏感度不低一个坏掉的代码块可能让整个上下文解析出错。所以输入输出格式里至少要明确“哪些语法结构必须保留”。3. 自己搭一个发送前清洗流程3.1 第一步定义敏感字段清单不要一上来就想覆盖所有敏感信息。先列一个最小清单按照你实际发送的内容来定。最常用的是邮箱、手机号、IP 地址、API 密钥、内部代号和文件路径。如果你经常处理客户数据再加上姓名、身份证号、银行卡号如果你主要处理代码重点就是密钥、token、内网地址。清单不要贪多。规则越多误伤概率越大维护成本越高。先覆盖你过去一周里实际粘过 LLM 的内容类型比想象中所有可能性更有效。3.2 第二步写匹配规则确定性的字段用正则就够了不需要上模型。下面是一个规则示例实际使用时要根据你的语言和工具调整。# 邮箱 pattern: [A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,} replace: [EMAIL] # 手机号连续 11 位数字避免匹配身份证里的片段 pattern: (?!\d)1[3-9]\d{9}(?!\d) replace: [PHONE] # IPv4 地址 pattern: (?!\d)(?:\d{1,3}\.){3}\d{1,3}(?!\d) replace: [IP]正则有一个问题要提前注意边界条件。手机号规则如果写成1[3-9]\d{9}会把银行卡号里连续 11 位数字也匹配掉。所以要用前向和后向断言确定它前面不是数字、后面也不是数字。这类细节是清洗质量的关键也是新手最容易踩的坑。对于密钥类内容正则往往不够。OpenAI 格式的密钥有固定前缀可以用前缀匹配但很多内部 token 没有前缀这时候适合用高熵检测长度达到一定值字符类型足够混杂比如同时包含大小写、数字和符号就视为疑似密钥。高熵检测会有误报比如一段随机生成的测试数据也会被替换所以这类规则要单独控制开关并且放在预览阶段让用户确认。3.3 第三步决定替换策略替换方式直接影响下游效果。常见的做法有三种。第一种是替换成占位符例如[EMAIL]、[PHONE]。这种做法的好处是保持文本结构把敏感信息变成可读的标记LLM 仍然知道这里有一个邮箱或手机号只是不知道具体值。第二种是直接删除。适合你确定 LLM 不需要这部分信息的情况但删除会破坏句子结构比如“你可以发邮件到 xxx 联系我”删除后就变成“你可以发邮件到联系我”语义受损。第三种是哈希替换。把敏感值映射成一个固定长度的伪标识例如邮箱哈希成[HASH-abc123]。好处是同一个值在文本里多次出现时替换结果一致LLM 能识别出它们是同一个实体坏处是不可读而且哈希本身也可能被反向枚举。我建议默认用占位符。它兼顾结构、可读性和稳定性。替换规则里的占位符也要设计得足够独特避免和正文里真实存在的同类文本混淆。3.4 第四步加预览和二次确认清洗工具最怕的不是规则不强而是用户不知道规则做了什么。所以预览是必须的环节。把原始文本和清洗后的文本并排展示标出每一处替换位置显示每条规则的命中数量让用户扫一眼就能确认“这次发送前处理没有漏掉关键的也没有误伤太多”。预览还有一个作用建立信任。用户刚开始用 scrubber 时会对替换结果不放心担心把重要信息弄丢了。看到清晰的 diff 之后才敢放心用。等到信任建立起来再慢慢把预览设置成可跳过的快捷模式。原始文本: 请联系 adminexample.com 或 13800138000 清洗文本: 请联系 [EMAIL] 或 [PHONE] 命中统计: email x1, phone x13.5 第五步把清洗放进发送链路这一步决定了 scrubber 是“想起来才用”还是“每次都生效”。如果你只是手动复制文本到网页聊天框那清洗只能靠自觉但如果你是通过 API 或者脚本调用 LLM完全可以把清洗做成发送链路里的一层。# 伪代码仅示意流程 def send_to_llm(raw_text, rules): cleaned scrub(raw_text, rules) report generate_report(cleaned, rules) if not confirm(cleaned, report): return return api_call(cleaned)在 agent 场景里这一步尤其重要。因为 agent 的文本往往包含工具调用结果、文件内容和函数返回结构敏感信息更容易混在结构化数据里。把清洗逻辑放在“发起外部请求之前”的统一出口比在每个工具调用里单独处理要可靠得多。4. 怎么判断清洗结果“够干净”4.1 漏报和误报哪个更难接受清洗结果有两个基本指标漏报和误报。漏报是敏感信息没有被识别直接跟着文本发出去了误报是普通内容被当成敏感信息替换掉破坏了语义。在 scrubber 场景里漏报的代价更高。因为一旦漏掉敏感信息就真的外发了事后很难补救。误报的代价相对低最多是 LLM 看到的内容被替换影响回答质量你可以重新调整规则再发一次。所以设计规则时宁可稍微激进一点先保证不遗漏再通过预览来过滤误报。当然也不能走到另一个极端。如果规则把所有连续数字都替换掉那整段文本基本没法用。判断标准是敏感字段召回率高同时误报率控制在可接受范围。4.2 覆盖率、替换率和格式完整率怎么统计建立一套测试文本很重要。准备一小段包含邮箱、手机号、IP、密钥和内部代号的样例跑一遍清洗人工检查结果。这里可以统计三个指标首次命中率敏感字段被识别出来的比例建议达到 100%。替换准确率替换后的占位符是否准确对应原字段类型。格式完整率清洗后的 JSON 还能不能被解析Markdown 代码块是否完整。这三个指标不需要做成复杂的报表维护一组测试用例就够了。每次改规则后跑一遍对比结果就能知道这次修改是改善了清洗效果还是引入了新的误报。这个思路和写单元测试类似本质上是给规则加回归保护。4.3 本地处理性能的正常范围纯粹的正则替换处理几 KB 的文本几乎是瞬间完成的感觉不到延迟。处理几 MB 的日志文件时速度取决于规则数量和正则复杂度。一条正常的正则对整个文本扫描一遍几 MB 也就几百毫秒到一两秒。但这里有一个容易被忽略的问题发送链路的总耗时。如果你的 scrubber 在 API 请求之前同步执行清洗时间会叠加到总延迟里。有些 API 客户端本身有超时设置文本很长、规则很多时清洗阶段消耗的时间也可能把总耗时推高。所以清洗步骤尽量轻量不要在发送前做重计算更不要在这里调用外部模型做语义分析。如果确实需要 AI 辅助识别建议单独跑一个离线批次而不是阻塞在线请求。5. 批量文件和自动化场景5.1 批量清洗的输入输出规范把清洗从单条文本扩展到批量文件时最容易出问题的不是正则而是文件组织。批量处理一批.md或.txt文件时我一般会这样做输出到独立目录例如scrubbed/不覆盖原文件。保持原有目录结构方便对照检查。输出文件名和原文件一致或者加统一后缀比如_scrubbed.md。额外生成一个清单文件记录每个文件命中了几次、命中了哪些规则。这里有一个关键原则永远不要原地覆盖原文件。一旦规则误伤你没有原文件就无法恢复。批量场景下的失败重试也很重要。如果某个文件处理失败不要中断整个批次记录错误原因后继续处理下一个最后汇总报告。5.2 做成命令行工具还是本地服务使用方式需要提前设计。命令行工具适合一次性处理、定时任务和 CI 流程输入路径、输出目录、规则文件都可以用参数指定。本地服务适合经常从图形界面发送内容的场景比如编辑器插件或者内部小工具通过 HTTP 调用清洗接口。如果做成本地 HTTP 服务接口要尽量简单。一个典型的做法是POST /scrub请求体里带text和可选的rules参数返回清洗后的文本和命中统计。端口要注意别和本地其他服务冲突默认监听127.0.0.1不要暴露到局域网。{ text: 请联系 adminexample.com, rules: [email, phone, ip] }{ cleaned: 请联系 [EMAIL], report: [ {rule: email, count: 1} ] }5.3 日志、审计和样本留存清洗工具会产生一个矛盾你想记录“哪些内容被清洗了”但记录这些内容本身又等于把敏感信息留在了日志里。所以日志设计要刻意避开原始敏感值。更稳妥的做法是只记录规则名称、命中次数、字段类型和位置偏移不记录匹配到的具体内容。如果确实需要留存样本供后续检查可以把原值做哈希日志里只存哈希值。这样既能追溯清洗过程又不会在日志里新增一份敏感数据副本。这一点在团队环境里特别重要。如果团队要求审计每次外发前都走了清洗流程审计记录必须存在但审计记录不能成为新的数据泄露点。6. 常见问题和排查顺序6.1 该匹配的没匹配到现象是敏感信息没有被替换直接进入了发送内容。优先排查顺序是先看原始文本格式再确认规则文件是否生效最后单独跑一条样例验证。常见原因有这几类文本里包含全角字符或不可见字符正则按半角写的匹配不到。换行符不一致跨行内容没有匹配。正则边界条件写错比如手机号前有一个国家区号86导致(?!\d)判断失败。规则文件路径错误或者被版本管理忽略了。排查时不要直接改规则先在测试文本上跑一遍最小样例确定是规则问题还是输入问题。6.2 误伤太多现象是普通内容被大量替换清洗后的文本没法看。最常见的原因是规则写得太宽比如单纯用\d{4,}匹配所有四位以上数字结果年份、版本号、金额全部中招。解决办法是收紧边界。给数字类规则加上前后断言给“疑似密钥”的规则增加字符类型多样性要求给内部代号类规则维护一个严格名单。误伤太多时预览功能的价值就体现出来了每处替换都可视化之后你很快能看出是哪条规则太激进。6.3 结构化内容被改坏这是清洗 JSON、Markdown 和 agent 工具调用内容时最容易踩的坑。如果你直接把 JSON 值里的邮箱替换成[EMAIL]JSON 还是合法的但如果替换结果里带了空格或者引号JSON 解析器就会报错。更麻烦的是在 agent 的工具调用参数里如果你破坏了参数结构模型服务端可能直接拒绝请求常见现象就是类似 “provider rejected the request schema or tool payload” 的报错。处理结构化的原则是先解析结构再清洗值。JSON 就逐字段遍历只处理字符串值Markdown 就识别代码块并单独处理。不要用纯正则在整个文本上做替换那样很容易跨过语法边界。6.4 发送后才发现清洗无效这个现象最让人头疼因为数据已经出去了。常见的发生路径有两种一种是你没有通过统一发送入口发送而是直接复制文本去了网页聊天框清洗流程完全没生效另一种是敏感值不在你的规则清单里比如一个内部项目代号当时没有写进规则事后才意识到。针对第一种需要在工作流层面加约束比如统一通过脚本发送或者在发送前弹出一个检查提醒。针对第二种只能靠持续补充规则。每次发现漏网之鱼就把它补进规则库和测试用例避免下次再漏。6.5 规则多了之后维护困难当规则从 5 条变成 50 条你会遇到几个新问题规则之间互相干扰、新旧规则重复匹配、某些规则的优先级不明确。这时候需要把规则从代码里拆出来放到独立配置文件里并且每条规则带上名称、正则、替换值、启用状态和备注。维护技巧很简单给每条规则写一个或多个测试用例。这个习惯看起来增加了工作量实际上能省掉大量反复验证的时间。现在很多人建 LLM wiki 时会记录提示词和踩坑笔记把清洗规则和误报案例也整理进去是一个不错的做法因为这类知识不沉淀下来过几周就忘了当初为什么这么配。7. 边界、替代方案和长期做法7.1 本地 scrubber 解决不了什么需要把边界说清楚否则容易产生虚假安全感。本地 scrubber 只能处理“可识别的敏感字段”也就是有模式、有规则、有名单的内容。对于没有固定形态的信息例如一段描述里隐含的项目进度、客户关系的上下文信息、昵称背后的真实身份规则引擎是识别不出来的。另外它只能管住“经过清洗入口”的外发。如果你团队里有人直接复制内容到未接入清洗流程的网页端或者通过手机 App 发送那 scrubber 根本不会介入。它解决的是输入环节的工程化问题不是人的行为管理问题。还有一点不要把 scrubber 当成事后补救工具。它必须在数据发送前运行一旦数据已经发出清洗结果如何都没有意义。7.2 更稳妥的组合做法如果敏感信息类型复杂可以组合几种手段。第一层是本地 scrubber处理明确模式的敏感字段。第二层是本地模型辅助识别用于检测没有固定模式但语义上像敏感内容的部分这一步如果不需要可以不做。第三层是网络出口控制通过统一的 API 网关或代理在请求真正发出前再做一次强制检查这样即使调用方忘了清洗网关还能兜底。生产环境里我建议把 scrubber 做成一个独立模块而不是散落在各个脚本里。这样规则可以集中维护、集中测试、集中审计。处理批量数据时把清洗、发送、日志分开跑互不阻塞也便于定位问题。7.3 我的建议顺序如果你的场景比较简单建议按这个顺序推进。先写一个最小规则集只覆盖邮箱、手机号、IP 和密钥跑通单条文本清洗。然后加预览和命中统计确保清洗结果可见可控。接着把清洗封装成 API 调用前的统一入口让每次外发都自动经过清洗。最后再扩展批量文件、日志审计和规则配置化。不要一开始就追求识别所有敏感信息。先解决最确定的 80%再根据实际漏报逐步补规则。很多工具不是一开始做得不够多而是规则过度膨胀之后误报和性能问题反而把整个流程拖垮了。踩过几次之后我发现这类发送前清洗工具真正的价值不是把敏感字段识别得多完美而是让你每次把文本交给 LLM 之前都有一个确定的、可检查的、不会忘记的步骤。启动这个步骤比纠结某一条正则的边界条件重要得多。