Prompt 敏感信息脱敏:在发送给 LLM 前自动识别并替换 PII 和密钥
Prompt 敏感信息脱敏在发送给 LLM 前自动识别并替换 PII 和密钥一、深度引言与场景痛点大家好我是赵咕咕。几个月前我们做了一次日志审计发现一件让我后背发凉的事有用户在我们的 RAG 系统中粘贴了一段代码里面包含了一个硬编码的 AWS Access Key。那段代码被当作检索上下文的一部分原封不动地拼进了 Prompt然后发给了 GPT-4。更可怕的是我们根本不知道这件事——直到日志审计才发现。而 OpenAI 的 API 日志里这个密钥已经被保存了。这不是某个马虎的开发者的锅。大模型应用的本质就是把用户的任意输入拼成 Prompt 发给第三方 API。在这个过程中用户可能不小心贴进来任何东西手机号、身份证号、API Key、数据库密码、内部文档的敏感段落。你必须假设发给 LLM 的每一段文本都可能包含敏感信息然后在发送之前做脱敏处理。二、底层机制与原理深度剖析2.1 敏感信息的分类在 Prompt 场景下敏感信息可以分为三类个人身份信息PII手机号、身份证号、邮箱、银行卡号、家庭住址。识别难度低有明确的正则模式。凭证类信息API Key、Access Token、数据库密码、SSH 私钥。这些通常有特定的格式前缀如sk-、AKIA、ghp_而且有高熵特征。业务敏感信息公司内部项目代号、未发布的版本号、客户真实姓名。这类信息无法通过规则识别需要靠自定义字典或 NERNamed Entity Recognition模型。2.2 脱敏流水线的架构脱敏流水线采用串行与并行结合的处理方式整体流程从用户输入与检索文档的合并开始经过规则引擎筛选后依次完成检测、替换与还原三个阶段最终将结果返回给用户。整个流水线的核心逻辑是检测用多引擎并行检测文本中的敏感信息。规则引擎负责模式匹配正则高熵检测负责发现看起来不像正常文本的字符串如随机生成的 Token。替换检测到的敏感信息用占位符替换如[PHONE_01]、[APIKEY_01]。保留原始值到映射表中。还原LLM 返回结果后把占位符还原为原始值。这样用户看到的结果跟实际数据一致但 LLM 和 API 日志里只有占位符。关键是第三步还原必须在最终输出给用户之前完成否则用户看到的答案是您的电话是 [PHONE_01]而不是真实的138xxxx1234。2.3 高熵检测的原理有经验的工程师可能会绕过正则把 API Key 拆成两段、中间加空格、用 Base64 编码后再贴进来。正则匹配不到。这时候需要用信息熵来检测。正常英文文本的香农熵通常在 3.5-4.5 之间而随机生成的 Token如sk-proj-abc123def456...的熵通常超过 4.8。算法很简单对每个连续的非空白字符串计算字符频率分布的熵值。超过阈值的标记为疑似凭证。三、生产级代码实现import asyncio import re import math import logging from collections import Counter from dataclasses import dataclass, field from typing import Any logger logging.getLogger(__name__) dataclass class SensitiveMatch: 检测到的敏感信息。 original: str # 原始值 placeholder: str # 占位符 type: str # phone / email / apikey / pii / high_entropy start: int # 在原文本中的起始位置 end: int # 在原文本中的结束位置 class PromptSanitizer: Prompt 敏感信息脱敏器。 # ---- PII 正则模式 ---- _PII_PATTERNS: list[tuple[str, str]] [ (phone, r\b1[3-9]\d{9}\b), # 中国手机号 (idcard, r\b[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b), # 身份证 (email, r\b[A-Za-z0-9._%-][A-Za-z0-9.-]\.[A-Za-z]{2,}\b), # 邮箱 (bankcard, r\b\d{16,19}\b), # 银行卡号 (ip_addr, r\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b), # IP 地址 ] # ---- 凭证前缀模式 ---- _CREDENTIAL_PREFIXES: list[tuple[str, str]] [ (openai_key, rsk-[A-Za-z0-9]{20,}), # OpenAI (aws_key, rAKIA[A-Z0-9]{16}), # AWS Access Key (github_token, rgh[ps]_[A-Za-z0-9]{36,}), # GitHub Token (jwt, reyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}), # JWT (private_key, r-----BEGIN [A-Z ] PRIVATE KEY-----), # SSH/TLS 私钥 (connection_string, r(?:mongodb|mysql|postgresql|redis)://[^\s]), ] def __init__( self, entropy_threshold: float 4.5, custom_patterns: list[tuple[str, str]] | None None, custom_keywords: set[str] | None None, ): self._entropy_threshold entropy_threshold self._custom_keywords custom_keywords or set() # 编译所有正则 self._compiled [] for name, pattern in self._PII_PATTERNS self._CREDENTIAL_PREFIXES: try: self._compiled.append((name, re.compile(pattern, re.IGNORECASE))) except re.error as e: logger.error(正则编译失败 (%s): %s, name, e) if custom_patterns: for name, pattern in custom_patterns: try: self._compiled.append( (fcustom_{name}, re.compile(pattern, re.IGNORECASE)) ) except re.error as e: logger.error(自定义正则编译失败 (%s): %s, name, e) # ─── 公开接口 ─── async def sanitize( self, text: str, timeout: float 5.0 ) - tuple[str, dict[str, str]]: 对文本做脱敏处理。 返回: (脱敏后的文本, 占位符→原始值的映射) try: return await asyncio.wait_for( self._sanitize_impl(text), timeouttimeout ) except asyncio.TimeoutError: logger.error(脱敏处理超时返回原文未脱敏) return text, {} async def desanitize( self, text: str, mapping: dict[str, str] ) - str: 将占位符还原为原始值。 result text for placeholder, original in mapping.items(): result result.replace(placeholder, original) return result async def _sanitize_impl( self, text: str ) - tuple[str, dict[str, str]]: matches: list[SensitiveMatch] [] # 1) 正则匹配 for name, pattern in self._compiled: for m in pattern.finditer(text): matches.append(SensitiveMatch( originalm.group(), placeholder, typename, startm.start(), endm.end(), )) # 2) 高熵检测 high_entropy_matches self._detect_high_entropy(text) matches.extend(high_entropy_matches) # 3) 自定义关键词检测 keyword_matches self._detect_keywords(text) matches.extend(keyword_matches) if not matches: return text, {} # 4) 去重叠较长的匹配优先 matches.sort(keylambda m: (m.start, -(m.end - m.start))) deduped: list[SensitiveMatch] [] for m in matches: if not any( e.start m.start and e.end m.end for e in deduped ): deduped.append(m) # 5) 生成占位符并替换 mapping: dict[str, str] {} counts: dict[str, int] {} # 从后往前替换避免位置偏移 result_chars list(text) for m in sorted(deduped, keylambda x: x.start, reverseTrue): type_key m.type.upper() idx counts.get(type_key, 0) 1 counts[type_key] idx placeholder f[{type_key}_{idx:02d}] m.placeholder placeholder mapping[placeholder] m.original result_chars[m.start:m.end] list(placeholder) result .join(result_chars) logger.info( 脱敏完成: 发现 %d 处敏感信息 (%d 种类型), len(deduped), len(counts), ) return result, mapping # ─── 高熵检测 ─── def _detect_high_entropy(self, text: str) - list[SensitiveMatch]: 检测高熵字符串疑似随机生成的凭证。 results: list[SensitiveMatch] [] # 找出所有连续的非空白词元至少 10 个字符 for m in re.finditer(r\S{10,}, text): token m.group() entropy self._shannon_entropy(token) if entropy self._entropy_threshold: # 额外检查不匹配已知的普通词语 if not self._looks_like_normal_text(token): results.append(SensitiveMatch( originaltoken, placeholder, typehigh_entropy, startm.start(), endm.end(), )) return results staticmethod def _shannon_entropy(s: str) - float: 计算字符串的香农熵。 if not s: return 0.0 counter Counter(s) length len(s) return -sum( (count / length) * math.log2(count / length) for count in counter.values() ) staticmethod def _looks_like_normal_text(token: str) - bool: 判断是否像正常文本排除已知的模式。 # 如果包含常见英文单词模式元音辅音交替很可能是正常文本 word_pattern re.compile(r[aeiou][bcdfghjklmnpqrstvwxyz], re.IGNORECASE) vowel_ratio sum(1 for c in token.lower() if c in aeiou) / max(len(token), 1) # 正常英文单词元音比例通常在 0.2-0.5 if 0.25 vowel_ratio 0.55 and word_pattern.search(token): return True # URL 不是高熵凭证 if token.startswith((http://, https://)): return True return False # ─── 关键词检测 ─── def _detect_keywords(self, text: str) - list[SensitiveMatch]: 检测自定义敏感关键词。 if not self._custom_keywords: return [] results: list[SensitiveMatch] [] for kw in self._custom_keywords: start 0 while True: idx text.find(kw, start) if idx -1: break results.append(SensitiveMatch( originalkw, placeholder, typecustom_keyword, startidx, endidx len(kw), )) start idx 1 return results # ─── Agent 集成中间件 ─── class SanitizerMiddleware: 在 Agent 工具调用前插入脱敏处理。 def __init__(self, sanitizer: PromptSanitizer): self._sanitizer sanitizer self._last_mapping: dict[str, str] {} async def wrap_prompt(self, raw_prompt: str) - str: 在构造 Prompt 时调用返回脱敏后的文本。 clean, mapping await self._sanitizer.sanitize(raw_prompt) self._last_mapping mapping return clean async def unwrap_response(self, raw_response: str) - str: 在返回给用户前调用还原占位符。 return await self._sanitizer.desanitize( raw_response, self._last_mapping ) async def main(): sanitizer PromptSanitizer( entropy_threshold4.5, custom_keywords{ProjectPhoenix, 竞品分析_V2}, ) middleware SanitizerMiddleware(sanitizer) # 模拟用户输入 user_input ( 我的手机号是 13812345678请帮我把这个 API Key: sk-proj-abc123def456ghi789jkl 相关的项目部署到 AWS 上。 另外可以参考一下 ProjectPhoenix 的架构。 ) clean await middleware.wrap_prompt(user_input) print(f脱敏后: {clean}) # 模拟 LLM 返回 llm_response ( 已根据您的 [PHONE_01] 设置了通知方式。 使用 [OPENAI_KEY_01] 完成了部署。 参考了 [CUSTOM_KEYWORD_01] 的架构设计。 API Key: [OPENAI_KEY_01] ) final await middleware.unwrap_response(llm_response) print(f还原后: {final}) if __name__ __main__: asyncio.run(main())设计的几个关键考量从后往前替换如果从前往后替换每次替换后字符位置会偏移后续的start/end就对不上了。从后往前替换不需要重新计算位置。去重叠手机号可能同时匹配phone和bankcard如果手机号恰好 16 位较长的匹配优先级更高。高熵检测的假阳性过滤_looks_like_normal_text通过元音比例和辅音模式过滤掉正常的英文文本避免误杀。超时兜底sanitize外层包了asyncio.wait_for。脱敏不是核心业务路径超时后降级为不脱敏直接发送好过阻塞整个请求。中间件模式SanitizerMiddleware封装了脱敏→LLM→还原的完整流程对 Agent 的其他代码透明。四、边界分析与架构权衡4.1 脱敏的过度与不足正则匹配天然偏向宁可误杀不可漏过——银行卡号\b\d{16,19}\b会匹配任何 16-19 位纯数字包括一些正常的订单号或流水号。如果你的业务中订单号是敏感信息那误杀反而是好事。但如果需要保留某些长数字就需要在正则前加一层白名单_WHITELIST_PATTERNS [ r\b(?:ORDER|TXN|REF)\d\b, # 订单号、交易号等非敏感长数字 ]在白名单中的匹配不进行替换。4.2 LLM 对占位符的理解一个容易忽略的问题是LLM 看到[PHONE_01]后可能会猜测这是什么然后在回答中尝试还原。比如它会写您的电话号码 [PHONE_01] 对应的运营商是……——这种情况下LLM 实际上猜对了这个占位符的含义但它的猜测过程本身可能泄露了上下文信息。缓解方案使用更语义中性的占位符如[S_01]、[P_03]而不是[PHONE_01]。4.3 性能与延迟脱敏处理的延迟取决于文本长度和正则复杂度。对于典型 2000 字的 Prompt规则引擎处理时间在 1-5ms。高熵检测需要对每个连续词元做计算在 5000 字文本中约 3-10ms。总体延迟可控制在 15ms 以内。如果文本特别长如检索到 10 万字的上下文建议先做文本截断或只在可能包含敏感信息的段落上运行脱敏如代码块、用户输入部分。4.4 日志与监控脱敏后的文本仍然可能泄露模式信息如用户问了关于 [PHONE_01] 的问题。如果日志也被发送到第三方如日志服务需要考虑日志层面的脱敏。建议在日志输出前再做一层脱敏——但这会引入额外复杂度。务实做法是脱敏后的 Prompt 直接入日志只对高风险的日志接收方做额外处理。五、总结Prompt 脱敏不是可选的nice to have而是一个大模型应用的基础安全设施。你无法控制用户往对话框里贴什么但你可以控制什么内容被发送到第三方 LLM API。实现上建议从三层渐进式构建第一层必须正则匹配 PII 和常见凭证格式覆盖 90% 的场景。第二层推荐高熵检测补充规则引擎的盲区拦截非标准格式的凭证。第三层按需自定义关键词和 NER 模型覆盖业务特定的敏感信息。最关键的一点还原必须在服务端。永远不要在前端做脱敏——你是在防用户把敏感信息发给 LLM而不是帮用户保护他自己的隐私虽然客观上也有这个效果。安全领域有一条铁律不要信任用户的任何输入。在大模型时代这条铁律要加上后半句也不要让 LLM 看到用户的任何裸奔输入。下一篇预告RAG 服务的内存池设计复用 embedding 模型推理结果来减少重复计算。

相关新闻

Ptex核心原理深度解析:为什么它能彻底改变3D纹理工作流

Ptex核心原理深度解析:为什么它能彻底改变3D纹理工作流

Ptex核心原理深度解析:为什么它能彻底改变3D纹理工作流 【免费下载链接】ptex Per-Face Texture Mapping for Production Rendering https://wdas.github.io/ptex 项目地址: https://gitcode.com/gh_mirrors/pt/ptex Ptex作为Per-Face Texture Mapping&#…

2026/7/21 19:30:29 阅读更多 →
Redis 集群数据迁移:不停服的前提下把向量索引迁移到新集群的方案

Redis 集群数据迁移:不停服的前提下把向量索引迁移到新集群的方案

Redis 集群数据迁移:不停服的前提下把向量索引迁移到新集群的方案 一、深度引言与场景痛点 大家好,我是赵咕咕。 有一段时间我对"停服迁移"这件事产生了 PTSD。每次产品经理小心翼翼地问"这个数据迁移要停多久",我就知…

2026/7/23 14:35:05 阅读更多 →
告别复杂配置:OpCore-Simplify让黑苹果EFI创建变得简单快捷

告别复杂配置:OpCore-Simplify让黑苹果EFI创建变得简单快捷

告别复杂配置:OpCore-Simplify让黑苹果EFI创建变得简单快捷 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 还在为黑苹果的复杂配置而头疼…

2026/7/21 11:11:28 阅读更多 →

最新新闻

ATS: Adaptive Token Sampling for Efficient Vision Transformers 解读

ATS: Adaptive Token Sampling for Efficient Vision Transformers 解读

一、论文基本信息论文题目:Adaptive Token Sampling for Efficient Vision Transformers方法简称:ATS作者:Mohsen Fayyaz、Soroush Abbasi Koohpayegani、Farnoush Rezaei Jafari、Sunando Sengupta、Hamid Reza Vaezi Joze、Eric Sommerlade…

2026/7/23 22:25:23 阅读更多 →
商标品牌赋能寒地通信企业——注册品牌 MUKONI 与LONPTT 的品牌建设之路

商标品牌赋能寒地通信企业——注册品牌 MUKONI 与LONPTT 的品牌建设之路

在市场竞争日益激烈的今天,品牌已成为企业核心竞争力的重要组成部分,而注册商标则是品牌保护和发展的基础。对于寒地通信企业而言,在技术创新和合规经营的基础上,加强品牌建设,打造具有影响力的自主品牌,是…

2026/7/23 22:25:23 阅读更多 →
UVM验证平台的死亡陷阱:从入门到放弃的那些坑

UVM验证平台的死亡陷阱:从入门到放弃的那些坑

一个需要重写的验证平台2026年4月,某芯片公司的GPU项目流片失败。原因令人咬牙切齿:验证平台测到了bug,但误报为通过。追查发现,UVM scoreboard的比较逻辑存在race condition。当DUT和参考模型同时更新时,比较发生在正…

2026/7/23 22:25:23 阅读更多 →
口碑好的含金物料出售企业排名

口碑好的含金物料出售企业排名

在当今的工业和投资领域,含金物料的交易市场愈发活跃。无论是从电子废弃物中回收的含金边角料,还是工业生产过程中产生的含金废渣、废液等,这些含金物料都蕴含着巨大的价值。然而,对于想要出售含金物料的人来说,选择一…

2026/7/23 22:25:23 阅读更多 →
Mamba架构在YOLOv8中的创新应用与优化实践

Mamba架构在YOLOv8中的创新应用与优化实践

1. 从Transformer到Mamba:YOLOv8架构演进的核心驱动力在计算机视觉领域,目标检测模型的架构演进始终围绕着两个核心矛盾展开:全局感受野与计算效率的平衡,以及长程依赖与局部特征捕获的权衡。Transformer架构通过自注意力机制实现…

2026/7/23 22:25:22 阅读更多 →
新手写小说少走 90% 的弯路:AI 工具正确用法,从选题到投稿全流程指南

新手写小说少走 90% 的弯路:AI 工具正确用法,从选题到投稿全流程指南

很多人心里都藏着一个写小说的念头,但真正能写完、能签约的人少之又少。不是没有才华,而是新手太容易踩进各种误区:题材选不对,大纲写不好,开头留不住人,写着写着就跑偏,最后折腾半天没成果&…

2026/7/23 22:24:22 阅读更多 →

日新闻

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:25 阅读更多 →
AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析

更多请点击: https://codechina.net 第一章:AI写作开头钩子设计:为什么你的AI文案完读率不足18%?——基于2,346篇A/B测试报告的归因分析 在对2,346篇跨行业AI生成文案的A/B测试数据进行聚类分析后,我们发现&#xff1…

2026/7/23 0:01:26 阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:01:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 19:43:43 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻