> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/ai_security_guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/ai_security_guide/di-san-bu-fen-fang-yu-pian/09_io_protection/9.3_sensitive_data.md).

# 9.3 敏感信息保护

防止隐私和机密信息通过 LLM 泄露是重要的安全要求。

## 9.3.1 敏感信息类型

需要保护的敏感信息包括：

| 类型          | 示例         | 风险    |
| ----------- | ---------- | ----- |
| 个人身份信息（PII） | 姓名、身份证、电话  | 隐私泄露  |
| 认证凭据        | 密码、API Key | 账户安全  |
| 业务机密        | 内部文档、策略    | 商业损失  |
| 系统信息        | 配置、架构      | 被攻击利用 |

## 9.3.2 输入侧脱敏

在输入进入 LLM 前进行脱敏：

```python
import re
import secrets

class InputSanitizer:
    def __init__(self):
        self.patterns = {
            'email': r'\b[\w.-]+@[\w.-]+\.\w+\b',
            'phone': r'\b\d{11}\b|\b\d{3}-\d{4}-\d{4}\b',
            'id_card': r'\b(?:\d{18}|\d{17}[0-9Xx])\b',
            'credit_card': r'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b',
        }

    def sanitize(self, text: str) -> tuple[str, dict]:
        mappings = {}
        sanitized = text

        for pii_type, pattern in self.patterns.items():
            matches = re.findall(pattern, text)
            for i, match in enumerate(matches):
                token = secrets.token_urlsafe(12)
                placeholder = f"[PII:{pii_type.upper()}:{i}:{token}]"
                mappings[placeholder] = {
                    "value": match,
                    "type": pii_type,
                    "origin": "input_sanitizer",
                }
                sanitized = sanitized.replace(match, placeholder)

        return sanitized, mappings

    def restore(
        self,
        text: str,
        mappings: dict,
        allowed_placeholders: set[str],
    ) -> str:
        """只在确定性允许的输出字段中还原占位符。"""
        restored = text
        for placeholder in allowed_placeholders:
            if placeholder not in mappings:
                raise ValueError(f"Unexpected placeholder: {placeholder}")
            restored = restored.replace(
                placeholder,
                mappings[placeholder]["value"],
            )

        # 还原后仍需独立 DLP/凭据扫描；模型输出中的伪造占位符不应被还原。
        if re.search(r"\[PII:[A-Z_]+:\d+:[A-Za-z0-9_-]+\]", restored):
            raise ValueError("Unapproved PII placeholder in model output")
        return restored
```

## 9.3.3 输出侧过滤

检测并过滤输出中的敏感信息：

```mermaid
flowchart TB
    A["模型输出"] --> B["PII 检测"]
    B --> C["凭据检测"]
    C --> D["机密检测"]
    D --> E{"发现敏感信息?"}
    E --> |是| F["脱敏/过滤"]
    E --> |否| G["原样输出"]
    F --> G
```

图 9-5：输出侧过滤流程图

**检测实现**

```python
class OutputPIIFilter:
    def filter(self, output: str) -> str:
        filtered = output

        # 正则匹配

        for pii_type, pattern in self.patterns.items():
            filtered = re.sub(pattern, f"[{pii_type}已隐藏]", filtered)

        # NER 识别

        entities = self.ner_model.extract(output)
        for entity in entities:
            if entity.type in self.sensitive_types:
                filtered = filtered.replace(entity.text, "[已隐藏]")

        return filtered
```

## 9.3.4 系统提示保护

防止系统提示泄露：

```python
SYSTEM_PROMPT_FRAGMENTS = [
    "你的系统提示",
    "你的指令",
    "你被配置为",
    # ... 更多关键片段

]

def protect_system_prompt(output: str, system_prompt: str) -> str:
    # 检查是否泄露了系统提示片段

    for fragment in SYSTEM_PROMPT_FRAGMENTS:
        if fragment in output:
            return "[抱歉，我无法透露我的系统配置]"

    # 检查相似度

    if calculate_similarity(output, system_prompt) > THRESHOLD:
        return "[抱歉，我无法透露我的系统配置]"

    return output
```

## 9.3.5 数据分级保护

根据敏感级别实施不同保护策略：

| 级别 | 数据类型 | 保护措施    |
| -- | ---- | ------- |
| 公开 | 公司简介 | 无特殊限制   |
| 内部 | 内部政策 | 身份验证    |
| 机密 | 薪资数据 | 加密 + 审计 |
| 绝密 | 核心技术 | 禁止访问    |

## 9.3.6 数据泄露检测

监控潜在的数据泄露行为：

```python
class LeakageDetector:
    def check_for_leakage(self, output: str, context: dict) -> LeakageResult:
        risks = []

        # 检查是否包含用户 A 的信息在用户 B 的会话中

        if self.cross_user_leak(output, context):
            risks.append(Risk("cross_user_leak", HIGH))

        # 检查是否包含训练数据记忆

        if self.training_data_leak(output):
            risks.append(Risk("training_data_leak", MEDIUM))

        # 检查是否泄露系统信息

        if self.system_info_leak(output, context):
            risks.append(Risk("system_info_leak", HIGH))

        return LeakageResult(risks)

    def cross_user_leak(self, output: str, context: dict) -> bool:
        """检测输出中是否包含非当前用户的信息"""
        current_user = context.get("user_id")
        retrieved_docs = context.get("retrieved_documents", [])

        # 1. 来源校验：检查检索文档是否全部属于当前用户
        for doc in retrieved_docs:
            if doc.get("owner_id") != current_user:
                if self._content_appears_in_output(doc["content"], output):
                    return True  # 其他用户的文档内容出现在输出中

        # 2. 实体交叉检测：提取输出中的命名实体，与当前用户的已知实体集合对比
        output_entities = self.ner_extractor.extract(output)
        user_entities = self.get_user_entity_set(current_user)
        foreign_entities = output_entities - user_entities
        if self._entities_match_other_users(foreign_entities):
            return True

        # 3. 元数据标签检查：验证输出引用的数据源标签
        return False
```

**跨用户泄露检测的局限与建议**

完美的跨用户泄露检测在工程上极其困难，主要原因包括：

* **实体匹配局限**：不同用户可能拥有名称相同的实体（如“张三”这样的常见名字），导致单纯的实体匹配产生误报，难以准确判断信息所有权。
* **语义混淆风险**：输出中的内容可能是模型生成的通用信息，并非来自特定用户的数据，但与某用户的数据在语义上接近，导致检测困难。
* **检索链路复杂性**：在实际 RAG 系统中，用户在一次会话中可能查询多个数据源，跨用户检索的边界判定本身就存在模糊地带。

**因此，我们推荐采用“预防优于检测”的策略**：

1. **在检索阶段就严格过滤**：通过强制的访问控制和租户隔离，确保检索前端已经过滤掉非当前用户的文档，使得检测层只需应对已被过滤后的安全输入。
2. **实施多租户数据隔离**：使用独立的向量索引或显式的租户标签过滤，而不是依赖输出层的事后检测。
3. **审计与监控补偿**：如果仍存在跨用户泄露的残余风险，应通过强化审计日志与异常监控来及时发现，而非期待输出过滤器能完全阻断。

## 9.3.7 审计与合规

满足隐私保护法规要求：

**GDPR 合规要点**

| 要求     | 实现        |
| ------ | --------- |
| 最小化处理  | 只处理必要数据   |
| 目的限制   | 明确数据用途    |
| 数据主体权利 | 支持访问、删除请求 |
| 传输保护   | 加密传输      |

**审计记录**

```python
class PrivacyAuditLog:
    def log_data_access(self, user_id: str, data_type: str, purpose: str):
        record = {
            "timestamp": datetime.now(),
            "user_id": user_id,
            "data_type": data_type,
            "purpose": purpose,
            "legal_basis": self.get_legal_basis(purpose),
        }
        self.store(record)

    def log_data_deletion(self, request_id: str, user_id: str, status: str):
        record = {
            "timestamp": datetime.now(),
            "request_id": request_id,
            "user_id": user_id,
            "action": "deletion",
            "status": status,
        }
        self.store(record)
```

## 9.3.8 敏感信息防护的可测指标

如果没有量化指标，输入输出边界的防护很容易沦为“心理安慰”。在生产环境中实施数据保护体系，必须建立以下核心度量指标，并将其纳入日常监控大盘：

| 指标维度                    | 业务价值与衡量标准                                                           | 统计方式示例                                                                                                                 |
| ----------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **泄漏率 (Leakage Rate)**  | 衡量脱敏引擎的假阴性（漏报）。高敏数据（如密钥、身份证）要求 **0 容忍**。                            | 利用外部/独立 DLP 旁路探针检测 LLM 回复客户端的最终 Payload，记录被独立探针抓到的未拦截敏感实例数。                                                            |
| **命中规则分布**              | 识别哪些维度的数据最常面临威胁，指导分类器规则库的动态增补。                                      | 按实体类型（如 `API_KEY: 40%`, `ID_CARD: 35%`）聚合网关拦截日志，绘制周环比风险饼图。                                                             |
| **脱敏一致性 (Consistency)** | 衡量输入侧基于不可预测占位符（如 `[PII:EMAIL:0:<nonce>]`）的脱敏机制，在允许还原的确定性字段中是否被正确保留。 | 计算 `通过解析器验证并完成逆向替换的占位符数量 / 允许还原的占位符总数`；低于阈值时应 fail closed，进入修复/重试流程，并通过独立 DLP 检查最终 payload。Prompt 只能作为提示，不应作为一致性的唯一控制。 |
| **下游执行防护率**             | 防止脱敏后的文本或大模型裸输出在下游业务链条中“把数据变成了指令”（即二次注入引发 RCE 或 SQLi）。              | 监控通过了敏感扫描的 LLM 输出流向，确保抵达 Shell/DB 前 100% 经过显式的参数化转移或严格的静态分析门禁。                                                         |

将上述指标作为自动化红队回归测试的一部分，并在 CI/CD 中设定熔断阈值，是验证隐私防护有效性的关键。

## 9.3.9 开源工具推荐

以下开源工具可帮助快速落地敏感信息保护能力：

| 工具                   | 核心能力                                                                                                          | 适用场景                                                |
| -------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Microsoft Presidio   | 成熟的 PII 识别与数据脱敏引擎，通过规则引擎和 NER 模型自动识别文本中的姓名、电话、邮箱、信用卡号等敏感实体，并替换为占位符（如 `[PERSON]`、`[CREDIT_CARD]`），支持脱敏后的安全反演恢复 | 数据发送给大模型前的自动脱敏处理，以及模型返回后的敏感信息过滤，是落实“机密不入上下文”原则的首选工具 |
| Stanza NER（Stanford） | 多语言 NLP 工具包，其中当前官方 NER 模型覆盖 23 种语言，可提取人名、地名、组织名等实体                                                            | 多语言场景下的 PII 实体抽取，与正则规则互补，提升部分非英文环境下的识别准确率           |

敏感信息保护需要在系统设计的各个层面落实，是合规和信任的基础。
