27 - PII检测与数据安全

个人隐私信息检测、脱敏策略、双重扫描架构与面试高频考点

一、什么是 PII 检测?

PII 的定义

PII(Personally Identifiable Information)指能够单独或与其他信息结合后识别特定个人身份的数据。常见 PII 包括:身份证号、手机号、银行卡号、邮箱地址、姓名、住址等。

为什么在 AI 系统中尤为重要?

核心原则:宁可误拦不可漏放。源头拦截的成本远低于事后追溯清除。

二、检测规则体系

中文 PII 模式匹配规则

PII 类型模式特征示例正则表达式说明
身份证号18 位数字 + 校验位310101199001011234\d{17}[\dXx]末位支持 X/x
手机号11 位,1 开头138123456781[3-9]\d{9}覆盖所有运营商号段
银行卡号16-19 位数字6222021234567890123[3-6]\d{15,18}Luhn 校验辅助判定
邮箱地址xxx@xxx.xxxuser@example.com[\w.+-]+@[\w-]+\.[\w.]+标准 email 格式
中文姓名2-4 个汉字张三上下文依赖需结合称呼词 / 姓氏词典

正则模式注册代码

public class PiiPatterns {
    private static final Map<PiiType, Pattern> PATTERNS = Map.of(
        PiiType.ID_CARD,    Pattern.compile("(?<!\\d)(\\d{17}[\\dXx])(?!\\d)"),
        PiiType.PHONE,      Pattern.compile("(?<!\\d)(1[3-9]\\d{9})(?!\\d)"),
        PiiType.BANK_CARD,  Pattern.compile("(?<!\\d)([3-6]\\d{15,18})(?!\\d)"),
        PiiType.EMAIL,      Pattern.compile("([\\w.+-]+@[\\w-]+\\.[\\w.]+)"),
        PiiType.NAME,       Pattern.compile("(?<=姓名[::]\\s{0,2})([\\u4e00-\\u9fa5]{2,4})")
    );

    // 预编译 Pattern 复用,避免重复编译开销
    public static Pattern getPattern(PiiType type) {
        return PATTERNS.get(type);
    }
}

三、脱敏策略

三种脱敏策略

策略效果示例适用场景
掩码 MASK130****1234 / 3201**********1234保留部分特征,便于用户核对
替换 REPLACE[PHONE] / [ID_CARD]完全消除,适合发送给 LLM
删除 REMOVE直接移除整段文本高敏感场景(如军事、医疗)

策略选择逻辑

  脱敏策略选择决策树:

  检测到 PII
       │
       ├── 目标: 发送给 LLM?
       │     │
       │     ├── YES → 使用 REPLACE 策略
       │     │         "[PHONE]" 保留语义位置
       │     │         LLM 仍能理解 "请给[PHONE]发消息"
       │     │
       │     └── NO (返回给用户)
       │           │
       │           ├── 用户需要核对? → MASK 策略
       │           │   "130****1234" 保留前后缀
       │           │
       │           └── 高敏感场景? → REMOVE 策略
       │               直接移除,不留痕迹
       │
  策略代码:
  ┌──────────────────────────────────────────────┐
  │ public String sanitize(String text,           │
  │     PiiType type, MaskStrategy strategy) {    │
  │                                              │
  │   return switch (strategy) {                  │
  │     case MASK    → applyMask(text, type);     │
  │     case REPLACE → "[" + type.name() + "]";   │
  │     case REMOVE  → "";                        │
  │   };                                          │
  │ }                                             │
  └──────────────────────────────────────────────┘

四、双重扫描架构

Input Scan + Output Scan

PiiDetectionHook 同时注册两个 Hook 类型,在输入和输出两端各设一道扫描关卡:

  PII 双重扫描架构:

  User Input                                                    User
     │                                                           ▲
     ▼                                                           │
  ┌─────────────────────┐                              ┌─────────────────────┐
  │ ON_USER_MESSAGE     │                              │ ON_OUTPUT           │
  │ (Input Scan)        │                              │ (Output Scan)       │
  │ ┌─────────────────┐ │                              │ ┌─────────────────┐ │
  │ │ PII Scan        │ │                              │ │ PII Scan        │ │
  │ │ ├── 身份证检测  │ │                              │ │ ├── 身份证检测  │ │
  │ │ ├── 手机号检测  │ │                              │ │ ├── 手机号检测  │ │
  │ │ ├── 银行卡检测  │ │                              │ │ ├── 银行卡检测  │ │
  │ │ ├── 邮箱检测    │ │                              │ │ ├── 邮箱检测    │ │
  │ │ └── 姓名检测    │ │                              │ │ └── 姓名检测    │ │
  │ └────────┬────────┘ │                              │ └────────┬────────┘ │
  │          ▼          │                              │          ▼          │
  │   REPLACE 策略      │                              │   MASK 策略         │
  │   "[PHONE]"         │                              │   "130****1234"     │
  └──────────┬──────────┘                              └──────────┬──────────┘
             ▼                                                    │
        Clean Input ──────▶ LLM ──────▶ Raw Output ──────────────┘

为什么需要双向扫描?

五、与提示词注入防护的协作

PII 检测 vs 提示词注入防护:

PII 检测聚焦隐私保护——检测个人信息模式(手机号、身份证),防止数据泄露。保护的是用户

提示词注入防护聚焦系统安全——检测攻击指令模式(ignore previous instructions),防止系统被操控。保护的是系统

协作流程

  两道防线的协作关系:

  用户输入
     │
     ▼
  ┌───────────────────────────────┐
  │ ① 提示词注入防护 (order=5)   │  ← 先检查是否为攻击
  │    检测: "忽略之前的指令..."    │
  │    结果: BLOCK / ALLOW        │
  └──────────────┬────────────────┘
                 ▼ (ALLOW)
  ┌───────────────────────────────┐
  │ ② PII 检测 (order=15)        │  ← 再检查是否含隐私信息
  │    检测: 手机号、身份证...     │
  │    结果: 脱敏后的消息          │
  └──────────────┬────────────────┘
                 ▼
            Clean Input → Agent

  两者互补而非互斥:
  ├── 注入防护: 防攻击 (保护系统)
  └── PII 检测: 防泄露 (保护用户)

六、配置示例

# application.yml — PII 检测与脱敏配置
agent:
  security:
    pii-detection:
      enabled: true
      # 扫描方向:INPUT(仅输入)、OUTPUT(仅输出)、BOTH(双向)
      scan-direction: BOTH

      # 检测规则
      rules:
        id-card:
          enabled: true
          pattern: "(?<!\\d)(\\d{17}[\\dXx])(?!\\d)"
          mask-strategy: REPLACE     # 发送给 LLM 时完全替换
          output-strategy: MASK      # 返回用户时掩码
        phone:
          enabled: true
          pattern: "(?<!\\d)(1[3-9]\\d{9})(?!\\d)"
          mask-strategy: REPLACE
          output-strategy: MASK
        bank-card:
          enabled: true
          pattern: "(?<!\\d)([3-6]\\d{15,18})(?!\\d)"
          mask-strategy: REPLACE
          output-strategy: MASK
          luhn-check: true           # 启用 Luhn 校验减少误报
        email:
          enabled: true
          pattern: "([\\w.+-]+@[\\w-]+\\.[\\w.]+)"
          mask-strategy: REPLACE
          output-strategy: MASK
        name:
          enabled: false             # 中文姓名默认关闭(误报率高)
          confidence-threshold: 0.9  # 高置信度才脱敏

      # 白名单:跳过检测的上下文
      exclude-contexts:
        - code-block                 # 代码块中的数字不检测
        - example-marker             # 示例标记内的内容不检测

      # 性能优化
      pre-filter:
        enabled: true                # 启用快速预过滤
        # 文本中没有数字和@符号时跳过正则匹配

七、面试高频问题

Q1: PII检测和提示词注入防护有什么区别?
A: PII 防数据泄露(保护用户隐私),提示词注入防攻击(保护系统安全)。检测目标不同:PII 检测个人信息模式(手机号、身份证等数据特征),注入检测攻击指令模式("忽略之前指令"等语义特征)。两者在 Hook 链中顺序执行——先注入防护(order=5,判断是否攻击),再 PII 检测(order=15,脱敏隐私信息),互补协作。
Q2: 正则表达式检测PII的局限性是什么?有什么补充方案?
A: 局限:① 高误报——文本中的普通数字序列(如订单号)可能被误判为手机号;② 无法处理变形——空格分隔(138 1234 5678)、全角字符等绕过正则;③ 上下文无感知——无法区分"示例电话"和"真实电话"。补充方案:NER 命名实体识别(基于 BERT 等模型)、上下文分析(检查周围词汇如"联系方式"、"手机")、机器学习分类器(特征工程 + 二分类)。实际系统中常采用正则快筛 + NER 精确判断的两阶段方案。
Q3: 脱敏后的数据如何确保LLM仍能正确理解用户意图?
A: 使用替换占位符而非完全删除:[PHONE] 保留语义位置,LLM 知道"请给[PHONE]发消息"的意图是发消息。关键信息保留前后缀——130****1234 保留运营商号段,用户可核对。对于需要实际值的场景(如工具调用),系统在执行层还原原始值(脱敏仅作用于 LLM 推理层),确保工具拿到的是真实参数。
Q4: 如果用户主动要求"帮我记住我的手机号是xxx",应该脱敏吗?
A: 分层策略:① 长期记忆存储时加密(AES-256),不影响后续理解;② 传递给 LLM 推理时脱敏(REPLACE 策略),降低泄露风险;③ 用户明确授权 + 功能确实需要时可豁免(如通讯录管理 Agent)。遵循 GDPR 最小必要原则——只在明确需要且用户知情同意时才保留原始值,且需审计日志记录豁免操作。
Q5: 中文姓名检测的难度在哪里?如何降低误报?
A: 难度:2-4 字汉字组合在中文文本中极为普遍("人工智能"、"数据分析"都是 4 字),纯字面匹配误报率极高。降低误报策略:① 结合上下文——前面有称呼词("请联系"、"用户")或动作词("签名"、"收件人")时才判定;② 姓氏词典前置过滤——先匹配首字是否为常见姓氏(赵钱孙李...);③ 高置信度阈值(>0.9),综合多个信号才触发;④ 代码块、列表项等结构化环境中跳过检测。
Q6: 输出扫描(ON_OUTPUT)的必要性是什么?LLM会泄露训练数据中的PII吗?
A: 输出扫描的必要性:① LLM 可能在回复中复述用户此前提供的 PII("您之前提到的手机号 138...");② LLM 幻觉可能生成看似真实的 PII(虚构的身份证号恰好符合校验规则);③ 工具返回结果可能包含第三方 PII(数据库查询返回其他用户的信息)。关于训练数据:主流 LLM 已在训练阶段做了 PII 过滤,但无法 100% 保证,输出扫描是纵深防御的最后一道关卡。
Q7: 在流式输出(SSE)场景下,如何做PII检测?每个chunk都检测吗?
A: 核心挑战:PII 可能被分割在多个 chunk 中(如手机号"138"在 chunk1、"12345678"在 chunk2)。解决方案:① 缓冲区累积——累积到完整 token 边界再检测,避免检测不完整的模式;② 滑动窗口——保留前一个 chunk 的尾部(如最后 20 字符)与当前 chunk 拼接后检测,覆盖跨 chunk 的 PII;③ 最终完整检测兜底——流式传输结束后,对完整响应做一次全量扫描,确保无遗漏。实际延迟增加 <10ms,用户无感知。
Q8: PII检测的性能开销如何?对响应延迟的影响?
A: 正则匹配 O(n) 复杂度,单次检测万字级文本 <5ms,影响极小。性能瓶颈在 NER 模型(如启用):推理延迟 50-200ms。优化措施:① 预编译 Pattern 复用(避免重复 compile);② 快速预过滤——先检查文本是否包含数字或 @ 符号,不包含则直接跳过全部正则;③ 多 Pattern 合并为一个大正则(减少遍历次数);④ NER 模型按需加载(仅当正则匹配到候选项时才调用 NER 验证)。