Presidio是一个开源工具集,用于在文本中查找并遮盖或替换个人身份信息(PII)。它独立于LLM“此句子是安全的”判断,通过代码为电子邮件、电话号码、账户标识符等具有特定格式的敏感信息构建一道防线。
如果说前一篇的Nova Lite和NeMo Self-check是分类请求的含义和意图,那么Presidio则是查找数据中的个人信息段落。它们并非竞争产品,而是减少不同类型失败的层次。本次实践中,我们将仅使用合成数据,通过Analyzer定位,Anonymizer进行遮蔽,然后连接到NeMo Retrieval Rail。
虽然名称中仍保留Microsoft,但目前的官方文档指出Presidio正在向社区拥有的项目过渡。新的容器镜像将从Data Privacy Stack组织的GitHub Container Registry发布。本文基于Microsoft发起的Presidio的当前官方文档和分发路径。

Presidio的功能与非功能
Presidio的核心分为两个阶段。AnalyzerEngine返回文本中个人信息候选的类型、起始/结束位置和置信度分数,AnonymizerEngine则使用Replace、Redact、Mask、Hash、Encrypt等操作符转换这些位置。
원문
↓
Analyzer: 개인정보 후보와 위치 탐지
↓
정책: 종류·점수·처리 방식 결정
↓
Anonymizer: 치환·삭제·마스킹·암호화
↓
정제된 문장
Presidio并非协调整个LLM管道的编排器。它不负责用户认证、租户权限、文档访问控制和工具授权。应用程序或NeMo的Custom Action调用Presidio,最终访问权限必须由可信的服务层强制执行。
| — | — | — |
| 组件 | 职责 | 结果 |
| Presidio Analyzer | 使用正则表达式、命名实体识别、规则、校验和与上下文检测PII候选 | 实体类型、位置、分数 |
| Presidio Anonymizer | 对检测到的位置应用操作符 | 去标识化文本 |
| 应用程序·NeMo | 决定调用时机和失败策略 | 允许、阻止、限制模式 |
| 权限层 | 验证用户·租户·文档权限 | 访问允许范围 |
在LLM安全管道中如何放置
用户输入在发送到LLM之前需要进行检查。RAG(Retrieval-Augmented Generation)是一种检索与问题相关的文档片段并添加到Prompt中的方式,此时应检查检索后返回的原始Chunk,而非嵌入向量。仅检查向量无法确认实际进入模型的字符串。
Browser
↓
Application: 인증·Tenant 확정
↓
NeMo Input Rail: 입력 Safety 분류
↓
권한 있는 Retrieval API: Tenant Filter 검색
↓
NeMo Retrieval Rail → Presidio: 원문 Chunk 검사·익명화
↓
Prompt 조립 → LLM → Output Rail
↓
Application: 최종 권한·감사 기록
Presidio也可以放置在输出端。然而,即使输入和检索文档已经过精炼,模型仍可能从对话上下文或工具结果中重新生成信息,因此敏感服务应在Output Rail中再次检查。在任何环节,是将检测错误更改为ALLOW、阻止,还是转换为受限响应,都必须根据业务影响明确规定。
实践1:用Python创建最小的检测·匿名化
创建Python虚拟环境并安装官方包。官方安装文档中支持的版本和NLP模型要求请在运行时再次确认。
mkdir presidio-privacy-lab
cd presidio-privacy-lab
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install presidio-analyzer presidio-anonymizer
python -m spacy download en_core_web_lg
不要复制实际的客户信息,请使用预留的.test域名和虚拟标识符。
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
from presidio_anonymizer.entities import OperatorConfig
text = "문의 주소는 student@example.test 입니다."
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
findings = analyzer.analyze(
text=text,
entities=["EMAIL_ADDRESS"],
language="en",
score_threshold=0.5,
)
result = anonymizer.anonymize(
text=text,
analyzer_results=findings,
operators={
"EMAIL_ADDRESS": OperatorConfig(
"replace", {"new_value": "<EMAIL>"}
)
},
)
print([(x.entity_type, x.start, x.end, x.score) for x in findings])
print(result.text)
预期结果是句子中包含
实践2:将组织标识符添加为Custom Recognizer
仅凭默认的Recognizer无法识别组织特有的员工编号、咨询编号或资产编号。例如,如果实践用的员工编号格式是EMP-年份-四位数字,则需要添加Pattern Recognizer。
from presidio_analyzer import AnalyzerEngine, Pattern, PatternRecognizer
employee_pattern = Pattern(
name="synthetic_employee_id",
regex=r"bEMP-20d{2}-d{4}b",
score=0.85,
)
employee_recognizer = PatternRecognizer(
supported_entity="EMPLOYEE_ID",
patterns=[employee_pattern],
context=["사번", "employee", "담당자"],
)
analyzer = AnalyzerEngine()
analyzer.registry.add_recognizer(employee_recognizer)
sample = "담당자 사번은 EMP-2026-0001 입니다."
findings = analyzer.analyze(
text=sample,
entities=["EMPLOYEE_ID"],
language="en",
)
print(findings)
正则表达式擅长捕捉格式,但不能完全理解上下文。过于宽泛的模式可能会将正常字符串误识别为个人信息,而过于狭窄则可能遗漏变体。对于韩语姓名或地址等语言和上下文依赖性强的实体,需要结合支持的NLP模型、Custom Recognizer和评估数据集进行设计。
实践3:按策略分离操作符
并非所有PII都应以相同方式处理。在LLM Prompt中,如果不需要恢复原文,Replace或Redact更为简单;在分析过程中,如果需要保持同一用户的连续性,可以考虑Hash或Pseudonymization。
| — | — | — |
| 目的 | 操作符候选 | 注意事项 |
| 不向LLM发送原文 | replace, redact, mask | 确认不删除必要的上下文 |
| 保持相同值的连接性 | hash | 固定Salt在单独的Secret Store中管理 |
| 需要批准的恢复 | encrypt | 不将Key放入Prompt·Log·配置文件 |
| 保留部分实体 | keep | 需要明确的业务依据和访问控制 |
根据官方文档,最近的Hash Operator默认对每个实体使用随机Salt。如果相同的输入需要相同的哈希值,则必须提供一致的Salt,但如果Salt泄露,再识别的风险会增加。Salt或Encrypt Key应由KMS·Key Vault·Secret Manager等外部秘密管理层处理。
实践4:从NeMo Retrieval Rail调用
以下函数是搜索API完成权限检查并返回原始Chunk后,通过Presidio进行精炼的最小示例。Presidio不直接查询Vector DB或判断租户权限。
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
def sanitize_retrieved_chunks(chunks: list[str]) -> list[str]:
sanitized = []
for chunk in chunks:
findings = analyzer.analyze(
text=chunk,
entities=["EMAIL_ADDRESS", "PHONE_NUMBER"],
language="en",
score_threshold=0.6,
)
clean = anonymizer.anonymize(
text=chunk,
analyzer_results=findings,
)
sanitized.append(clean.text)
return sanitized
在NeMo中,可以将此函数注册为Custom Action,并从Retrieval Rail调用。或者,也可以在应用程序拥有的Retrieval API内部执行相同的检查,然后将精炼后的Chunk传递给NeMo。两种方式均可行,选择取决于运营主体、延迟和策略跟踪位置。
重要的顺序是:租户过滤器搜索 → 返回有权限的原文 → PII检查 → Prompt组装。先调用Presidio并不会获得读取错误租户文档的权限。
通过容器分离调用
官方安装文档提供了将Analyzer和Anonymizer分别作为HTTP服务运行的容器镜像。截至2026年9月,新版本在ghcr.io/data-privacy-stack下发布,生产环境中应固定经过验证的Release Tag,而非latest。
podman pull ghcr.io/data-privacy-stack/presidio-analyzer:latest
podman pull ghcr.io/data-privacy-stack/presidio-anonymizer:latest
podman run --rm -p 5002:3000
ghcr.io/data-privacy-stack/presidio-analyzer:latest
podman run --rm -p 5001:3000
ghcr.io/data-privacy-stack/presidio-anonymizer:latest
在实践中,为了说明结构,我们展示了latest,但对于可复现的环境,应固定Digest或明确的版本。服务之间的原文传输段也应应用TLS、服务认证和网络策略,并且不应将请求体保留在访问日志中。
测试不应只看检测率
Presidio是一个自动判断工具,因此可能同时出现误报(False Positive)和漏报(False Negative)。官方文档也警告说,它不保证一定能找到所有敏感信息,需要根据实际用途进行额外的系统和评估。
| — | — |
| 测试用例 | 预期结果 |
| 合成电子邮件·电话号码 | 检测到并替换为指定实体 |
| 组织特有虚拟员工编号 | 通过Custom Recognizer检测 |
| 格式相似的正常订单号 | 不过度检测 |
| 韩文·英文·空格变体 | 确认各语言的目标检测率 |
| Analyzer超时·错误 | 在敏感路径中采用Fail-closed或限制模式 |
| 已遮蔽的字符串 | 不因重复转换而破坏上下文 |
测试日志应记录case_id、实体类型、数量、分数区间、策略版本和处理结果,而不是原文。将检测到的字符串本身复制到Log·Trace·Metric Label会使个人信息防线成为新的泄露途径。
常见被忽视的局限性
第一,遮蔽不等于删除。如果原始数据已首先存储在应用程序日志、跟踪、队列或缓存中,即使遮蔽发送给LLM的句子,其他副本也已存在。Presidio应尽可能靠近数据收集点部署。
第二,即使删除单个字段,通过组合上下文仍可能重新识别个人。结合稀有职位、日期、地区和事件描述的再识别风险,并非简单的正则表达式就能解决。
第三,匿名化不能替代授权。首先,应确认用户是否有权读取原始文档,然后只处理最少必要的信息。
第四,性能和策略应同时衡量。每次请求都用多个Recognizer检查长RAG Chunk会增加延迟和CPU使用率。应结合摄取阶段的预处理和检索阶段的再检查,并为策略变更时现有文档重新评估设置路径。
实践完成检查清单
- 区分了Presidio Analyzer和Anonymizer的职责。
- 使用了.test地址和虚拟标识符代替实际个人信息。
- 分别测试了默认Recognizer和组织专用的Custom Recognizer。
- 按实体分离了Replace·Mask·Hash·Encrypt策略。
- 在RAG中,检查的是检索后的原始Chunk,而非向量。
- 在Presidio前端保留了租户过滤器和文档权限检查。
- 设定了失败策略,以防止错误时静默绕过(Allow)。
- 未将原文、Salt、Encrypt Key留在Prompt和Telemetry中。
- 创建了误报(False Positive)和漏报(False Negative)两种测试用例。
- 将阻止·遮蔽结果保存为下一个Promptfoo回归测试用例。
总结
Presidio将“LLM会自动隐藏个人信息”的期望转化为可执行的检查和转换步骤。Analyzer查找个人信息候选,Anonymizer根据组织策略对这些段落进行去标识化。NeMo或应用程序在输入、搜索和输出边界调用此功能。
然而,Presidio不替代认证·授权、租户隔离和整体编排。最安全的部署是:只搜索有权限的数据,检查原始Chunk,只将精炼后的数据传递给LLM,并在输出端再次确认。下一篇将探讨如何串联多个防线,并在控制平面管理策略、版本和观测。
官方资料
※ 支持的语言、NLP模型、包API和容器注册表可能因版本而异。在应用于生产之前,请根据当前的官方文档和固定的版本,首先在合成数据集中进行验证。
发表回复