密钥脱敏:你的 API Key 可能藏在摘要里
专栏信息
《从零到一构建跨平台 AI 助手:WeClaw 实战指南》专栏
本文是模块八第 9 篇,讲解输出端敏感信息保护的设计与实现。
作者与项目
作者简介:翁勇刚 WENG YONGGANG
新概念龙虾-WeClaw 开发团队负责人,一群专注于跨平台 AI 应用的实践者
理念:“再复杂的技术,就能用代码讲清楚”
- 项目地址:https://github.com/wyg5208/weclaw.git
- 官网地址:https://weclaw.link
- 作者 CSDN:https://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 API,Key: sk-proj-abc123def456...xyz ..."问题:API Key 被原样保留在摘要中!即使原始对话消息被压缩删除,摘要仍然包含完整的密钥。
1.2 更隐蔽的场景
用户: 看下这个报错,日志里有我的 AWS Key:AKIA1234567890ABCDEF 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=[...]# 保留明文(对话正常)# 摘要生成时脱敏summary=generate_summary(original_messages)redacted_summary=redact_sensitive_info(summary)# redacted_summary: "... 用户配置了 OpenAI API,Key: *** ..."三、供应商前缀匹配
3.1 常见密钥格式
[图片: 正则匹配覆盖范围表 | 生成方式: Markdown 表格]
| 供应商 | 前缀 | 示例 | 正则 |
|---|---|---|---|
| OpenAI | sk-proj- | sk-proj-abc...xyz | sk-proj-[A-Za-z0-9_-]{20,} |
| OpenAI (legacy) | sk- | sk-abc...xyz | sk-[A-Za-z0-9]{20,} |
| AWS | AKIA | AKIA1234567890ABCDEF | AKIA[0-9A-Z]{16} |
| GitHub | ghp_ | ghp_abc...xyz | ghp_[A-Za-z0-9]{36} |
| Bearer Token | Bearer | Bearer eyJhbG... | Bearer [A-Za-z0-9._-]{20,} |
| 通用 API Key | api_key= | api_key=abc123... | api_key=["']?[A-Za-z0-9_-]{16,} |
| Azure | AccountKey= | AccountKey=abc... | 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(r'sk-proj-[A-Za-z0-9_-]{20,}'),# OpenAI (legacy: sk-xxx)re.compile(r'sk-[A-Za-z0-9]{20,}'),# AWS Access Keyre.compile(r'AKIA[0-9A-Z]{16}'),# GitHub Personal Access Tokenre.compile(r'ghp_[A-Za-z0-9]{36}'),# Bearer Token (in headers)re.compile(r'Bearer\s+[A-Za-z0-9._\-]{20,}'),# Generic api_key assignmentre.compile(r'api_key\s*[=:]\s*["\']?[A-Za-z0-9_\-]{16,}'),]REDACTED_MARKER="***"defredact_sensitive_info(text:str)->str:"""对文本中的敏感信息进行脱敏"""result=textforpatterninSECRET_PATTERNS:result=pattern.sub(REDACTED_MARKER,result)returnresult四、代码块保护
4.1 问题:示例代码被误脱敏
# 用户分享的代码示例code_example=""" # 配置 OpenAI 客户端 from openai import OpenAI client = OpenAI(api_key="sk-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_placeholders=re.sub(r'```[\s\S]*?```',save_code_block,text)# Step 2: 对非代码部分做脱敏redacted_text=redact_sensitive_info(text_with_placeholders)# Step 3: 还原代码块fori,blockinenumerate(code_blocks):redacted_text=redacted_text.replace(f"__CODE_BLOCK_{i}__",block)returnredacted_text4.3 效果对比
输入: 用户的 API Key 是 sk-proj-abc123def456, 配置代码如下: ```python client = OpenAI(api_key="sk-proj-abc123def456")输出:
用户的 API Key 是 ***,
配置代码如下:
client=OpenAI(api_key="sk-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 坑 2:sk-中的连字符
需求:匹配sk-proj-xxx格式的密钥。
直觉做法:
# 错误:字符类中的 - 被解释为范围pattern=re.compile(r'sk-[A-Za-z0-9-]{20,}')# ⚠️ 这个正则能匹配 sk-proj-xxx,但也能匹配 sk-------(全是连字符)# 更隐蔽的问题:sk-proj- 作为前缀时pattern=re.compile(r'sk-[proj]-[A-Za-z0-9]{20,}')# ❌ [proj] 是字符类,匹配 p/r/o/j 中的单个字符# 而不是匹配字符串 "proj"正确做法:
# 正确:用分组而非字符类pattern=re.compile(r'sk-(?:proj-)?[A-Za-z0-9_-]{20,}')# 匹配 sk-xxx 和 sk-proj-xxx 两种格式六、公共模块抽取
6.1 从单点到复用
脱敏逻辑最初只用于摘要生成,后来发现多个子系统都需要:
| 子系统 | 用途 |
|---|---|
| 摘要生成 | 摘要中的密钥脱敏 |
| 审计日志 | 工具调用参数中的密钥脱敏 |
| 错误报告 | 异常信息中的密钥脱敏 |
| 对话导出 | 导出记录中的密钥脱敏 |
6.2 公共模块设计
# security/redact.py —— 统一的敏感信息脱敏模块importrefromtypingimportOptional# === 常量定义 ===REDACTED_MARKER="***"MIN_KEY_LENGTH=16# === 模式定义 ===_PATTERNS:list[re.Pattern]=[re.compile(r'sk-(?:proj-)?[A-Za-z0-9_-]{20,}'),re.compile(r'AKIA[0-9A-Z]{16}'),re.compile(r'ghp_[A-Za-z0-9]{36}'),re.compile(r'Bearer\s+[A-Za-z0-9._\-]{20,}'),]# === 公共 API ===defredact_text(text:str,protect_code_blocks:bool=True)->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 版权协议,转载请附上原文出处链接及本声明。