Prompt Guard 中的个人隐私信息检测、策略化处理、输入/输出扫描链路
PII(Personally Identifiable Information)指能够单独或与其他信息结合后识别特定个人身份的数据。常见 PII 包括身份证号、手机号、银行卡号、邮箱地址、姓名、住址、IP 地址等。
结合当前项目实现,要把通用概念和已内置能力分开讲:当前内置手机号、身份证号、银行卡号、邮箱地址;IPv4 地址可配置开启;自定义规则可通过代码扩展;中文姓名和住址暂不内置,建议作为自定义规则或后续 NER 增强项。
| PII 类型 | 模式特征 | 示例 | 实现要点 |
|---|---|---|---|
| 手机号 | 11 位,1[3-9] 开头 | 13812345678 | 使用数字边界避免匹配更长数字串 |
| 身份证号 | 18 位身份证格式 | 11010519900307001X | 校验出生日期格式,并按 GB 11643 校验位过滤 |
| 银行卡号 | 16-19 位数字 | 6222021234567890123 | 使用 Luhn 校验降低订单号、流水号误报 |
| 邮箱地址 | 标准 email 格式 | user@example.com | 匹配常见邮箱用户名和域名结构 |
| IP 地址 | IPv4 地址 | 192.168.1.10 | 默认关闭,通过 detect-ip 开启 |
| 自定义 | 业务自定义正则 | 会员号、工号等 | 通过 PIIDetectors.fromPattern(...) 扩展 |
项目没有单独的 PiiPatterns 静态表,也没有内置 PiiType.NAME。PII 检测器由 PromptGuardAutoConfiguration 根据配置组装:
@Bean
@ConditionalOnProperty(
prefix = "hub.prompt-guard.pii",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public PIIScanner piiScanner(PromptGuardProperties properties) {
PromptGuardProperties.Pii piiConfig = properties.getPii();
List<PIIDetector> detectors = new ArrayList<>();
if (piiConfig.isDetectPhone()) {
detectors.add(PIIDetectors.phone());
}
if (piiConfig.isDetectIdCard()) {
detectors.add(PIIDetectors.idCard());
}
if (piiConfig.isDetectBankCard()) {
detectors.add(PIIDetectors.bankCard());
}
if (piiConfig.isDetectEmail()) {
detectors.add(PIIDetectors.email());
}
if (piiConfig.isDetectIp()) {
detectors.add(PIIDetectors.ipAddress());
}
return new PIIScanner(detectors, properties);
}
| 策略 | 效果示例 | 适用场景 |
|---|---|---|
| BLOCK | 拦截请求或响应 | 高敏感场景,检测到 PII 就拒绝继续处理 |
| REDACT | [手机号] / [身份证号] | 保留语义位置,便于 LLM 理解“这里有一个手机号” |
| MASK | 138****5678 | 保留少量前后缀,方便用户核对 |
| HASH | [hash:3f2a...] | 需要稳定指纹但不暴露原值的审计或去重场景 |
检测到 PII
│
├── block-on-detection = true
│ └── InputScanner 返回 failed,调用方直接拒绝请求
│
└── block-on-detection = false
├── PIIScanner 生成 redactedText
├── 调用方可使用 redactedText 替换原文后继续
└── OutputScanner 命中后交给 OutputSanitizer 做最终清洗
InputScanResult.safeWithPii(...) 已携带脱敏后的 redactedText,但 Web/IM 调用链主要消费的是 isPassed()。因此不要把现状讲成“所有输入都会自动替换后再进入 LLM”。如果要严格做到“PII 不入模”,应开启 block-on-detection,或在调用链中显式使用 getPiiResult().getRedactedText() 替换原消息。
当前 PII 能力不是独立的 PiiDetectionHook,而是接入 Prompt Guard 的 InputScanner 和 OutputScanner。
PII 扫描链路:
User Input
│
▼
┌────────────────────────────┐
│ InputScanner │
│ 1. 限流检查 │
│ 2. 输入注入/黑名单/长度检查 │
│ 3. PIIScanner │
│ ├── 手机号检测 │
│ ├── 身份证检测 │
│ ├── 银行卡检测 │
│ ├── 邮箱检测 │
│ └── IP 检测(可选) │
└──────────────┬─────────────┘
▼
Agent / LLM / Tool
│
▼
┌────────────────────────────┐
│ OutputScanner │
│ 1. 金丝雀令牌泄露检测 │
│ 2. API Key / Bearer 检测 │
│ 3. PIIScanner │
│ 4. OutputSanitizer │
└──────────────┬─────────────┘
▼
User
InputScanner: 1. 注入限流 2. 输入检查: 长度 / 关键词黑名单 / 正则攻击模式 3. PIIScanner 4. 按 block-on-detection 决定阻断或返回 PII 结果 OutputScanner: 1. 金丝雀令牌泄露检测 2. 系统上下文 / API Key / Bearer Token 敏感模式检测 3. PIIScanner 4. OutputSanitizer 清洗最终文本
# application.yml — 当前项目实际配置前缀
hub:
prompt-guard:
pii:
enabled: true
strategy: MASK # BLOCK / REDACT / MASK / HASH
detect-phone: true
detect-id-card: true
detect-bank-card: true
detect-email: true
detect-ip: false
block-on-detection: false
建议面试时补一句:detect-ip 默认关闭,是为了避免内网地址、示例地址、日志片段造成误报;中文姓名和住址不内置,是因为它们需要上下文和实体识别能力,纯正则误杀率很高。
block-on-detection 或使用 BLOCK 语义直接拒绝。需要保留语义位置时用 REDACT,例如 [手机号]。需要用户核对时用 MASK,例如 138****5678。需要审计去重但不能暴露原文时用 HASH。