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),避免过度过滤影响用户体验。被过滤的事件需要记录,用于审计和持续改进。

下一篇讲审计日志——安全事件的记录和追溯能力。