19.13-AI输出安全过滤
要点
- AI 输出安全过滤:在 LLM 的输出返回给用户之前,检查是否包含不安全的内容
- 跟脱敏不同——脱敏是隐藏特定格式的数据(手机号、身份证),过滤是判断内容是否「安全」
- 需要过滤的内容类型:有害内容(暴力、仇恨言论)、系统 prompt 泄露、内部信息泄露、有害指令代码
- Cloudflare AI Gateway 内置了输出过滤功能,可以零代码开启
- 自实现的过滤方案:关键词过滤、分类模型过滤、规则引擎过滤——多层叠加
- 过滤策略需要按场景调整——儿童应用过滤严格,开发者工具过滤宽松
- 过度过滤会影响用户体验——安全与可用性需要平衡
1. 为什么需要 AI 输出过滤
LLM 的输出不完全可控——即使 system prompt 里写了安全规则,LLM 仍然可能生成不安全的内容:
- 有害内容:暴力、仇恨言论、色情内容(模型训练数据里有)
- System prompt 泄露:被 Prompt Injection 诱导输出 system prompt
- 内部信息泄露:数据库地址、API Key、内部架构信息
- 有害代码:如果 AI 能生成代码,可能包含安全漏洞或恶意代码
- 隐私泄露:训练数据中的个人信息
1.1 过滤 vs 脱敏
| 维度 | 脱敏(19.12) | 输出过滤 |
|---|---|---|
| 目标 | 隐藏特定格式的数据 | 判断内容是否安全 |
| 方法 | 正则匹配 + 替换 | 规则引擎 + 分类模型 |
| 可逆性 | 部分可逆(脱敏规则已知可推断) | 不可逆(内容被替换为通用回复) |
| 示例 | 手机号 138****5678 | 有害内容 → 「抱歉,我无法回答这个问题」 |
两者经常叠加使用——先过滤安全,再脱敏格式。
2. Cloudflare AI Gateway 的输出过滤
AI Gateway 内置了安全过滤功能:
// 使用 AI Gateway 的安全过滤
const gateway = c.env.AI.gateway('my-gateway')
const result = await gateway.run(
'@cf/meta/llama-3.1-8b-instruct',
{ messages },
{
gateway: {
// 安全过滤
safety: {
enabled: true,
// 过滤阈值(0-1,越高越严格)
threshold: 0.8,
},
},
}
)AI Gateway 的安全过滤基于 Llama Guard 等安全分类模型,能识别多种有害内容类型。
优势:
- 零代码实现
- 持续更新(Cloudflare 维护分类模型)
- 统一的监控面板(可以看到被过滤的请求)
劣势:
- 过滤策略不够灵活(不能按业务场景精细调整)
- 依赖第三方服务
- 可能过度过滤(误判正常内容为有害)
3. 自实现输出过滤
3.1 关键词过滤
最基础的过滤——维护一个敏感词库:
// src/lib/content-filter.ts
// 敏感词库
const BLOCKED_PATTERNS = [
// 系统 prompt 提取尝试
/system\s*prompt/i,
/ignore\s*(previous|all)\s*instructions/i,
/忽略(之前|所有|上面)(的)?指令/,
/你现在(是|扮演)/,
/you\s*are\s*now/i,
// 有害内容关键词(实际项目需要更完整的列表)
/制造炸弹/,
/how\s*to\s*make\s*a\s*bomb/i,
// 内部信息
/database\s*url/i,
/connection\s*string/i,
/api[_\s]*key\s*[:=]/i,
]
function checkKeywordFilter(text: string): { blocked: boolean; reason?: string } {
for (const pattern of BLOCKED_PATTERNS) {
if (pattern.test(text)) {
return { blocked: true, reason: `Matched pattern: ${pattern}` }
}
}
return { blocked: false }
}局限性:关键词过滤容易被绕过(同义词、拆字、编码),且维护成本高。只能作为第一层快速过滤。
3.2 分类模型过滤
用一个小模型判断输出是否安全:
async function classifyOutput(text: string, env: Env): Promise<{
safe: boolean
categories: Record<string, number>
}> {
const result = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages: [
{
role: 'system',
content: `你是一个内容安全分类器。分析以下文本,输出 JSON:
{
"safe": true/false,
"categories": {
"violence": 0-1,
"hate": 0-1,
"sexual": 0-1,
"self_harm": 0-1,
"system_prompt_leak": 0-1,
"internal_info_leak": 0-1
}
}
只输出 JSON,不要解释。`
},
{ role: 'user', content: text }
],
})
return JSON.parse((result as { response: string }).response)
}优势:比关键词过滤更准确,能理解上下文。
劣势:增加延迟(额外的 LLM 调用)、增加成本、分类模型本身也可能被绕过。
3.3 多层过滤流水线
// 多层过滤
async function filterOutput(
output: string,
env: Env
): Promise<{ safe: boolean; filteredOutput: string }> {
// 第 1 层:关键词过滤(快速、低成本)
const keywordCheck = checkKeywordFilter(output)
if (keywordCheck.blocked) {
return {
safe: false,
filteredOutput: '抱歉,我无法回答这个问题。',
}
}
// 第 2 层:格式过滤(脱敏手机号、身份证等)
let filtered = filterSensitiveFormats(output)
// 第 3 层:分类模型过滤(精确、高成本)
const classification = await classifyOutput(output, env)
if (!classification.safe) {
return {
safe: false,
filteredOutput: '抱歉,我无法回答这个问题。',
}
}
// 第 4 层:System prompt 泄露检测
if (detectSystemPromptLeak(output, env.SYSTEM_PROMPT)) {
return {
safe: false,
filteredOutput: '抱歉,我无法回答这个问题。',
}
}
return { safe: true, filteredOutput: filtered }
}4. System Prompt 泄露检测
System prompt 是敏感信息。如果被 Prompt Injection 诱导输出,需要检测和拦截:
function detectSystemPromptLeak(output: string, systemPrompt: string): boolean {
// 方法 1:检查输出是否包含 system prompt 的独特短语
const promptPhrases = systemPrompt
.split(/[。.!!??\n]/)
.filter(p => p.length > 10) // 只检查长度足够的短语
let matchCount = 0
for (const phrase of promptPhrases) {
if (output.includes(phrase.trim())) {
matchCount++
}
}
// 超过 30% 的短语被匹配 → 疑似泄露
return matchCount / promptPhrases.length > 0.3
// 方法 2:检查常见的泄露模式
const leakPatterns = [
/my\s*(system|instructions?)\s*(is|are|:)/i,
/i\s*was\s*(instructed|told)\s*to/i,
/my\s*(rules?|guidelines?|constraints?)/i,
]
return leakPatterns.some(p => p.test(output))
}5. 内部信息泄露检测
AI 可能「无意中」输出内部信息——数据库地址、API Key、内部架构信息:
function detectInternalInfoLeak(output: string): string {
let filtered = output
// 过滤数据库连接字符串
filtered = filtered.replace(
/(?:postgres|mysql|mongodb|redis):\/\/[^\s]+/g,
'[DATABASE_URL]'
)
// 过滤 API Key 格式
filtered = filtered.replace(
/(?:sk|pk|ak)_[a-zA-Z0-9]{20,}/g,
'[API_KEY]'
)
// 过滤内部域名
const internalDomains = ['internal.example.com', 'db.example.internal']
for (const domain of internalDomains) {
filtered = filtered.replace(new RegExp(domain, 'g'), '[INTERNAL]')
}
// 过滤 IP 地址(可能是内部 IP)
filtered = filtered.replace(
/\b(?:10|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d{1,3}\.\d{1,3}\b/g,
'[INTERNAL_IP]'
)
return filtered
}6. 有害代码检测
如果 AI 能生成代码(比如编程助手),需要检测生成的代码是否包含安全漏洞:
function detectDangerousCode(code: string): {
safe: boolean
issues: string[]
} {
const issues: string[] = []
// 检测 eval()
if (/\beval\s*\(/.test(code)) {
issues.push('eval() is dangerous - can execute arbitrary code')
}
// 检测 SQL 拼接
if (/['"`]\s*\+\s*\w+\s*\+\s*['"`]/.test(code)) {
issues.push('Possible SQL injection - string concatenation in query')
}
// 检测 exec() / system()
if (/\b(?:exec|system|shell_exec|passthru)\s*\(/.test(code)) {
issues.push('Command execution function detected')
}
// 检测硬编码密码
if (/(?:password|secret|key)\s*[:=]\s*['"][^'"]{8,}['"]/.test(code)) {
issues.push('Hardcoded credentials detected')
}
// 检测不安全的随机数
if (/\bMath\.random\s*\(/.test(code)) {
issues.push('Math.random() is not cryptographically secure')
}
return {
safe: issues.length === 0,
issues,
}
}7. 按场景调整过滤策略
不同应用场景需要不同的过滤严格程度:
7.1 过滤级别
type FilterLevel = 'strict' | 'moderate' | 'relaxed'
const FILTER_CONFIG: Record<FilterLevel, {
keywordBlockThreshold: number
classificationThreshold: number
categories: Record<string, number>
}> = {
strict: {
keywordBlockThreshold: 0.5,
classificationThreshold: 0.6,
categories: {
violence: 0.3,
hate: 0.3,
sexual: 0.3,
self_harm: 0.3,
},
},
moderate: {
keywordBlockThreshold: 0.7,
classificationThreshold: 0.8,
categories: {
violence: 0.5,
hate: 0.5,
sexual: 0.5,
self_harm: 0.4,
},
},
relaxed: {
keywordBlockThreshold: 0.9,
classificationThreshold: 0.95,
categories: {
violence: 0.8,
hate: 0.7,
sexual: 0.7,
self_harm: 0.6,
},
},
}7.2 按应用选择
// 儿童教育应用:严格过滤
const appFilterLevel: FilterLevel = 'strict'
// 普通对话助手:中等过滤
const appFilterLevel: FilterLevel = 'moderate'
// 开发者工具:宽松过滤
const appFilterLevel: FilterLevel = 'relaxed'8. 在 Hono 路由中集成
// AI 聊天接口
app.post('/api/ai/chat', authMiddleware, async (c) => {
const { messages, model } = await c.req.json()
// 1. 调用 LLM
const result = await callLLM(messages, model, c.env)
const rawOutput = result.content
// 2. 输出过滤
const filterResult = await filterOutput(rawOutput, c.env)
if (!filterResult.safe) {
// 记录被过滤的事件(用于审计和改进)
await logFilteredOutput(c, rawOutput, filterResult)
return c.json({
content: filterResult.filteredOutput,
filtered: true,
})
}
// 3. 返回过滤后的输出
return c.json({
content: filterResult.filteredOutput,
filtered: false,
})
})
// 记录被过滤的输出
async function logFilteredOutput(c: Context, output: string, result: any) {
const userId = c.get('user')?.id
await c.env.DB.prepare(
'INSERT INTO filtered_outputs (user_id, output, reason, created_at) VALUES (?, ?, ?, ?)'
).bind(
userId,
output.slice(0, 1000), // 只记录前 1000 字符
JSON.stringify(result),
new Date().toISOString()
).run()
}9. 过度过滤的问题
过滤太严格会误判正常内容为有害——用户正常提问被拒绝回答,体验很差。
用户:「帮我写一个关于战争的小说」
AI 输出:(关于战争的正常文学创作)
严格过滤 → 检测到「暴力」类别 0.4 → 拒绝回答解决方案:
- 渐进过滤:先宽松,根据反馈逐步收紧
- 分类别阈值:暴力、色情等高风险类别阈值低,其他类别阈值高
- 白名单:特定场景(教育、医疗)的内容放宽过滤
- 用户反馈:允许用户报告「过度过滤」,持续优化
10. AI 输出安全检查清单
| 检查项 | 说明 |
|---|---|
| 关键词过滤 | 第一层快速过滤 |
| 分类模型过滤 | 判断暴力、仇恨、色情等有害内容 |
| System prompt 泄露检测 | 防止 Prompt Injection 提取 system prompt |
| 内部信息泄露检测 | 数据库 URL、API Key、内部 IP |
| 有害代码检测 | eval、SQL 拼接、硬编码密码 |
| 按场景调整过滤级别 | strict / moderate / relaxed |
| 被过滤事件记录 | 用于审计和改进 |
| 过度过滤监控 | 误判率监控,平衡安全与体验 |
11. 小结
AI 输出安全过滤是在 LLM 输出返回给用户之前检查是否包含不安全的内容。跟脱敏不同——脱敏隐藏特定格式的数据,过滤判断内容是否「安全」。
过滤方案:关键词过滤(快速但易绕过)→ 分类模型过滤(精确但有延迟和成本)→ 多层叠加。Cloudflare AI Gateway 内置了安全过滤功能,可以零代码开启。
System prompt 泄露检测和内部信息泄露检测是特殊的安全过滤场景——防止 AI 被诱导输出敏感信息。
过滤策略需要按场景调整(strict/moderate/relaxed),避免过度过滤影响用户体验。被过滤的事件需要记录,用于审计和持续改进。
下一篇讲审计日志——安全事件的记录和追溯能力。