密钥脱敏:你的 API Key 可能藏在摘要里

发布时间:2026/7/12 12:10:27
密钥脱敏:你的 API Key 可能藏在摘要里 密钥脱敏你的 API Key 可能藏在摘要里专栏信息《从零到一构建跨平台 AI 助手WeClaw 实战指南》专栏本文是模块八第 9 篇讲解输出端敏感信息保护的设计与实现。作者与项目作者简介翁勇刚 WENG YONGGANG新概念龙虾-WeClaw 开发团队负责人一群专注于跨平台 AI 应用的实践者理念“再复杂的技术就能用代码讲清楚”项目地址https://github.com/wyg5208/weclaw.git官网地址https://weclaw.link作者 CSDNhttps://blog.csdn.net/yweng18摘要本文结构概览本文从对话中的 API Key 被摘要原样保留的安全风险出发分析输入端脱敏 vs 输出端脱敏的权衡详解供应商前缀匹配的正则设计重点讲解代码块保护的实现与 Python re 模块的几个常见坑。背景AI 助手经常帮助用户配置 API、调试代码。用户在对话中粘贴的 API Key 会被 LLM 摘要原样保留长期留存在上下文中存在泄漏风险。核心问题如何在不破坏代码对话上下文的前提下自动识别并脱敏敏感信息解决方案输出端脱敏 供应商前缀匹配 代码块保护 公共模块抽取关键成果覆盖 8 种常见密钥格式的自动脱敏代码块内容不受影响避免示例代码被误脱敏抽取为公共模块多个子系统复用适合读者关注 AI 应用安全性和数据保护的开发者阅读时长约 10 分钟关键词密钥脱敏、正则表达式、API Key、输出端安全、代码块保护一、风险场景API Key 在对话中的生命周期1.1 一次普通的对话用户: 帮我配置一下 OpenAI 的 API我的 Key 是 sk-proj-abc123def456...xyz AI: 好的让我帮你配置... [30 轮对话后触发了上下文压缩] 摘要: ... 用户配置了 OpenAI APIKey: sk-proj-abc123def456...xyz ...问题API Key 被原样保留在摘要中即使原始对话消息被压缩删除摘要仍然包含完整的密钥。1.2 更隐蔽的场景用户: 看下这个报错日志里有我的 AWS KeyAKIA1234567890ABCDEF AI: 这个错误是因为... [压缩后] 摘要: ... 用户遇到 AWS 配置问题Key: AKIA1234567890ABCDEF ...密钥不仅存在于直接对话中还可能出现在日志、错误信息、配置文件中。二、为什么只在输出端脱敏2.1 两种脱敏策略策略时机优势劣势输入端脱敏用户消息进入系统时源头防护破坏代码对话上下文输出端脱敏生成摘要/日志/审计记录时不影响对话原始消息中仍有明文2.2 输入端脱敏的问题# 输入端脱敏的后果user_message我的 API Key 是 sk-proj-abc123帮我配置一下redacted我的 API Key 是 ***帮我配置一下# AI 收到的是脱敏后的消息# AI: 您的 Key 看起来不完整请提供完整的 API Key ← 困惑AI 模型需要看到完整的 Key 才能帮助用户配置。输入端脱敏会让模型不知道用户在说什么。2.3 输出端脱敏的优势# 输出端脱敏对话正常进行摘要/日志中脱敏original_messages[...]# 保留明文对话正常# 摘要生成时脱敏summarygenerate_summary(original_messages)redacted_summaryredact_sensitive_info(summary)# redacted_summary: ... 用户配置了 OpenAI APIKey: *** ...三、供应商前缀匹配3.1 常见密钥格式[图片: 正则匹配覆盖范围表 | 生成方式: Markdown 表格]供应商前缀示例正则OpenAIsk-proj-sk-proj-abc...xyzsk-proj-[A-Za-z0-9_-]{20,}OpenAI (legacy)sk-sk-abc...xyzsk-[A-Za-z0-9]{20,}AWSAKIAAKIA1234567890ABCDEFAKIA[0-9A-Z]{16}GitHubghp_ghp_abc...xyzghp_[A-Za-z0-9]{36}Bearer TokenBearerBearer eyJhbG...Bearer [A-Za-z0-9._-]{20,}通用 API Keyapi_keyapi_keyabc123...api_key[]?[A-Za-z0-9_-]{16,}AzureAccountKeyAccountKeyabc...AccountKey[A-Za-z0-9/]{20,}通义千问sk-sk-abc...(与 OpenAI 共享)3.2 正则设计原则前缀锚定以供应商特有前缀开头减少误匹配长度约束密钥通常 20 字符避免匹配短字符串字符集精确不同供应商的字符集不同如 AWS 只有大写字母数字3.3 核心实现importre# 密钥模式定义SECRET_PATTERNS[# OpenAI (new format: sk-proj-xxx)re.compile(rsk-proj-[A-Za-z0-9_-]{20,}),# OpenAI (legacy: sk-xxx)re.compile(rsk-[A-Za-z0-9]{20,}),# AWS Access Keyre.compile(rAKIA[0-9A-Z]{16}),# GitHub Personal Access Tokenre.compile(rghp_[A-Za-z0-9]{36}),# Bearer Token (in headers)re.compile(rBearer\s[A-Za-z0-9._\-]{20,}),# Generic api_key assignmentre.compile(rapi_key\s*[:]\s*[\]?[A-Za-z0-9_\-]{16,}),]REDACTED_MARKER***defredact_sensitive_info(text:str)-str:对文本中的敏感信息进行脱敏resulttextforpatterninSECRET_PATTERNS:resultpattern.sub(REDACTED_MARKER,result)returnresult四、代码块保护4.1 问题示例代码被误脱敏# 用户分享的代码示例code_example # 配置 OpenAI 客户端 from openai import OpenAI client OpenAI(api_keysk-proj-example-key-for-demo) # ← 被误脱敏 # 脱敏后redacted # 配置 OpenAI 客户端 from openai import OpenAI client OpenAI(api_key***) # ← 代码结构被破坏了 用户在代码示例中使用 API Key 作为演示脱敏后代码的可读性被破坏。4.2 解决方案先提取代码块再脱敏非代码部分defredact_with_code_protection(text:str)-str:带代码块保护的脱敏 三反引号包裹的代码块内容不做脱敏处理 # Step 1: 提取代码块用占位符替换code_blocks[]defsave_code_block(match):code_blocks.append(match.group(0))returnf__CODE_BLOCK_{len(code_blocks)-1}__# 匹配 代码块text_with_placeholdersre.sub(r[\s\S]*?,save_code_block,text)# Step 2: 对非代码部分做脱敏redacted_textredact_sensitive_info(text_with_placeholders)# Step 3: 还原代码块fori,blockinenumerate(code_blocks):redacted_textredacted_text.replace(f__CODE_BLOCK_{i}__,block)returnredacted_text4.3 效果对比输入: 用户的 API Key 是 sk-proj-abc123def456 配置代码如下 python client OpenAI(api_keysk-proj-abc123def456)输出:用户的 API Key 是 ***配置代码如下clientOpenAI(api_keysk-proj-abc123def456)正文中的 Key 被脱敏代码块中的 Key 保留原样。[图片: 脱敏前后对比 | 生成方式: 文生图 PROMPT: Before and after comparison of text redaction with code block protection: Left shows original text with API keys highlighted in red in body text and blue in code blocks, Right shows body text keys replaced by redacted markers in green while code block keys remain unchanged in blue, split-panel clean design] --- ## 五、Python re 的两个坑 ### 5.1 坑 1变宽 lookbehind 不支持 **需求**只匹配代码块外面的密钥不匹配代码块里面的。 **直觉做法** python # 使用 negative lookbehind 排除代码块内的匹配 pattern re.compile(r(?!.*)sk-proj-[A-Za-z0-9_-]{20,}) # ❌ 报错: re.error: look-behind requires fixed-width patternPython 的re模块不支持变宽 lookbehind。(?!.*)中的.*是变宽的。解决使用先替换再还原的方案如上文代码块保护方案而非 lookbehind。5.2 坑 2sk-中的连字符需求匹配sk-proj-xxx格式的密钥。直觉做法# 错误字符类中的 - 被解释为范围patternre.compile(rsk-[A-Za-z0-9-]{20,})# ⚠️ 这个正则能匹配 sk-proj-xxx但也能匹配 sk-------全是连字符# 更隐蔽的问题sk-proj- 作为前缀时patternre.compile(rsk-[proj]-[A-Za-z0-9]{20,})# ❌ [proj] 是字符类匹配 p/r/o/j 中的单个字符# 而不是匹配字符串 proj正确做法# 正确用分组而非字符类patternre.compile(rsk-(?:proj-)?[A-Za-z0-9_-]{20,})# 匹配 sk-xxx 和 sk-proj-xxx 两种格式六、公共模块抽取6.1 从单点到复用脱敏逻辑最初只用于摘要生成后来发现多个子系统都需要子系统用途摘要生成摘要中的密钥脱敏审计日志工具调用参数中的密钥脱敏错误报告异常信息中的密钥脱敏对话导出导出记录中的密钥脱敏6.2 公共模块设计# security/redact.py —— 统一的敏感信息脱敏模块importrefromtypingimportOptional# 常量定义 REDACTED_MARKER***MIN_KEY_LENGTH16# 模式定义 _PATTERNS:list[re.Pattern][re.compile(rsk-(?:proj-)?[A-Za-z0-9_-]{20,}),re.compile(rAKIA[0-9A-Z]{16}),re.compile(rghp_[A-Za-z0-9]{36}),re.compile(rBearer\s[A-Za-z0-9._\-]{20,}),]# 公共 API defredact_text(text:str,protect_code_blocks:boolTrue)-str:对文本进行敏感信息脱敏 Args: text: 待脱敏文本 protect_code_blocks: 是否保护代码块默认 True Returns: 脱敏后的文本 ifprotect_code_blocks:return_redact_with_code_protection(text)return_redact_raw(text)defredact_param_value(value:str)-str:对单个参数值进行脱敏 用于审计日志中的工具参数记录 ifnotvalueorlen(value)MIN_KEY_LENGTH:returnvaluereturn_redact_raw(value)七、总结与展望7.1 核心要点回顾输出端脱敏不影响对话AI 能看到完整信息帮助用户摘要/日志中自动脱敏供应商前缀匹配精准可靠利用每种密钥的特有前缀减少误匹配代码块保护避免误伤先提取代码块再脱敏保留代码示例的完整性Python re 有坑变宽 lookbehind 不支持连字符在字符类中有歧义下期预告《System Prompt 膨胀你的 AI 有多少预算给了自我介绍》实测CORE_SYSTEM_PROMPT 已超过旧上限三级预算控制的设计技能粒度裁剪 vs 字符级截断敬请期待版权声明本文为 CSDN 博主「翁勇刚」的原创文章遵循 CC 4.0 BY-SA 版权协议转载请附上原文出处链接及本声明。