jleonceo/pii-output-gate

GitHub: jleonceo/pii-output-gate

面向 AI 智能体输出的零依赖 PII 拦截关卡,通过校验码验证和拒绝列表匹配,在确定性层面阻止经过验证的个人身份数据外泄。

Stars: 0 | Forks: 0

# pii-output-gate **AI 智能体的输出关卡:任何携带个人数据的内容都无法离开。** 无依赖(仅使用标准 Python),无数据库,无 AI,无网络。 [西班牙语](#español) · [英语](#english) ## 西班牙语 ### 解决什么问题 使用真实数据的 AI 智能体迟早会发布一些东西:一份报告、一个 commit、一封电子邮件、一个文件。 这个项目源于一次真实的泄露事件。一个智能体在将名字替换为 `EMP001`、`EMP002` 后发布了工资单,并认为它们已经清理干净了。但事实并非如此。假名化并不等于匿名化:将名字替换为代码并没有删除数据,只是将其伪装起来,而工资单的其余部分依然存在,足以识破这种伪装。智能体做了看似合理的事,却弄错了,而且下游没有任何东西来阻止它。 这就是这个项目的意义所在:在输出关卡上加一把锁,不依赖模型本身的“良好表现”。无论 prompt 写得多好,如果输出内容中包含有效的 DNI(西班牙身份证),这扇门就不会打开。 ### 示例 ``` Texto limpio -> DEJA SALIR DNI valido (letra correcta) -> BLOQUEA clases: nif Nombre de la deny-list, tal cual -> BLOQUEA clases: nombre_denylist El mismo nombre partido por un salto de linea -> BLOQUEA clases: nombre_denylist El mismo nombre con acento cambiado -> BLOQUEA clases: nombre_denylist IBAN de ejemplo del estandar, con guiones -> BLOQUEA clases: iban ``` 中间的那三个案例正是它存在的理由。一个简单的搜索器会抓住第一个案例,而漏掉另外两个:对它来说,分成两行的 `Fulano Menganez` 已经不再是 `Fulano Menganez` 了。 还要注意拦截信息:它说明了数据的类别,而不是数据本身。**PII 检测器的日志绝不能泄露 PII。** ### 工作原理 三项检查,无一靠猜: 1. **NIF 和 NIE**:仅匹配模式是不够的。还会验证**校验字母**(模 23 算法)。一个带错误字母的 `12345678A` 不是 DNI,它只是一个数字,不会被拦截。 2. **IBAN**:同样的标准,会验证标准的**模 97 算法**。不会拦截任何仅仅以 ES 开头的内容。 3. **姓名**:与你提供的拒绝列表进行比对。它永远不会被纳入版本控制:而是作为参数传入。 在进行任何比对之前,文本会经过彻底的规范化处理:移除不可见字符(整个 Unicode `Cf` 类别,而不仅仅是其中几个),并将与拉丁字母外观相同的字母表进行映射。这部分不是表面功夫,它是抵御红队测试的核心。 所有逻辑都位于 `portero_pii.py` 中:规范化、NIF/NIE 模 23 验证、IBAN 模 97 验证和拒绝列表。 ### 红队测试 全绿的测试套件证明不了什么:它只能证明你想到的那些案例通过了。教训来自于同一系统中的另一个守卫,它有 15 个全绿案例,却在一个隐藏在 SQL 注释中的 `DROP` 语句面前**拦截失败**。 因此,这个关卡并不是因为有测试就被接受的。它被接受是因为它能在蓄意的绕过尝试中幸存下来:分两行的姓名、替换的重音符号、看起来一模一样的不同字母表、旧版编码、带连字符的 IBAN、带点的 IBAN、带换行符的 IBAN。 然而,它还是漏掉了这个: ``` EMP001 12345678Z 1850,00 -> no bloqueaba ``` 一个前面带数字的有效 DNI 会被泄露。在 10 句包含 DNI 的自然语句中,有 5 句会泄露:只需在前面加上一个金额、一个年份或一个参考代码。原因是候选对象在扫描时没有重叠,因此前面的数字将 DNI 吞没在了一次失败的尝试中,而扫描继续进行,再也没有重新查看它。 在发布当天,一次代码审计发现了这个问题,当时测试库全绿,且有 11 项绕过测试。这 11 项测试中没有一项在 DNI 前面加上数字。而那一行正是引发本项目的经过假名化处理的工资单:关卡恰恰在它旨在监视的数据类型上发生了泄露。 这个问题已经修复,现在的陷阱存在于测试库(`r12`)中,并带有针对误报的防护(`r13`)。 ``` run_tests_pii.py 9 casos de aceptación VERDE redteam_pii.py 13 intentos de evasión VERDE regresion_pii.py 22 casos de regresión VERDE ``` 这是该系统中第二次出现全绿测试库拦截失败的情况。所以上面那句话不是装腔作势:这是刚刚再次发生的事情。 ### 诚实的边界 运行 demo,你会看到最后一个案例: ``` FRONTERA DECLARADA: un nombre que NO esta en la deny-list -> DEJA SALIR ``` `Ramiro Villalobos` 是一个名字,它通过了。**这不是一个 bug;这是设计如此。** 这一层只捕捉它可以*验证*的内容:你给它的列表,它能计算的校验码。它无法验证它不知道的姓名。如果检测器拦截了所有*看起来*像名字的内容,就不可能有零误报,而一个存在误报的关卡最终会被禁用,这是所有结果中最糟糕的。 捕捉未知的名字是另一个问题,需要语言模型。这一层不尝试这样做。 当针对一个带有**预注册**裁决的工资单语料库对其进行评估时,结果为**红色**。并且它被如实报告为红色。三次泄露中有两次正是由于这个边界在按照预期方式工作。 ### 异常不从文本中请求 第一个版本允许在文本中使用内联标记 `# pii:allow` 来授权异常。已将其从契约中移除。 原因:从你正在检查的内容本身读取的权限是一个注入面。如果文本能告诉守卫放行,守卫就会照办。进入这扇门的是数据(DATA),绝不是指令。异常在带外进行授权,作为参数传入。 ### 试一试 ``` git clone https://github.com/jleonceo/pii-output-gate cd pii-output-gate python demo.py # la historia en 30 segundos python run_tests_pii.py # 9 casos de aceptación python redteam_pii.py # 13 intentos de evasión python regresion_pii.py # 22 casos de regresión ``` 没有 `pip install`。不需要。 ### 现有的工具 在发布这个之前,我去查看了现有的工具。有很多: - **`openai-guardrails`** 提供了一个 PII 检查来拦截输出,其中包含西班牙语实体。这是由 OpenAI 发布的同样的想法。 - **Microsoft Presidio** 使用其校验码检测西班牙语的 NIF、NIE 和护照。 - **`python-stdnum`** 验证 NIF、NIE、CIF、IBAN、CUPS 和地籍参考,没有依赖项,并且涵盖的范围比这个更广。 所以这不是一个产品:对于几乎所有的真实用例,请使用 Presidio。这里剩下的,以及发布它的原因,是这种方法:一扇失败时关闭的门、真正的红队测试,以及被如实报告的红色结果。 ### 数据和隐私 这个 repo 中的所有内容都是虚构的,或者是其标准的公开规范示例:文档中的 IBAN(西班牙语、葡萄牙语和德语)、NIF `12345678Z` 和测试 NIE,以及教科书上的占位符名称(`Fulano Menganez`、`Zutana Perez`、`Ramiro Villalobos`)。完全没有真实的 PII。拒绝列表永远不会被纳入版本控制:它作为参数传入。 ### 相关仓库 这个关卡是关于多智能体系统更广泛工作的一部分。它的兄弟项目: - [verificacion-determinista-ia](https://github.com/jleonceo/verificacion-determinista-ia):在没有 AI 的情况下重新检查数据一致性的护栏。那个向内看;这个向外看。 - [control-interno-fraude-ia](https://github.com/jleonceo/control-interno-fraude-ia):在内部控制框架内,使用算术进行会计欺诈检测。 - [accounting-agent-swarm](https://github.com/jleonceo/accounting-agent-swarm):产生这扇门所监视的输出的智能体集群。 - [gobernanza-skills-analiticas](https://github.com/jleonceo/gobernanza-skills-analiticas):管理所有这些的方法,带有 golden sets 和无回归关卡。 - [agent-memory-governance](https://github.com/jleonceo/agent-memory-governance):防止智能体的记忆变成一个垃圾场。 ## 英语 ### 解决什么问题 使用真实数据的 AI 智能体迟早会发布一些东西:一份报告、一个 commit、一封电子邮件、一个文件。 这个项目源于一次真实的泄露事件。一个智能体在将名字替换为 `EMP001`、`EMP002` 后发布了工资单,并认为它们已经清理干净了。但事实并非如此。假名化并不等于匿名化:将名字替换为代码并没有删除数据。它只是将其伪装起来,而工资单的其余部分依然存在,足以识破这种伪装。智能体做了看似合理的事,却弄错了,而且下游没有任何东西来阻止它。 这就是这个项目的意义所在:在输出关卡上加一把锁,不依赖模型本身的“良好表现”。无论 prompt 写得多好,如果输出内容中包含有效的国民身份证,这扇门就不会打开。 ### 示例 这是 `demo.py` 实际打印的内容。命令行使用西班牙语:`DEJA SALIR` 表示放行,`BLOQUEA` 表示拦截。 ``` Texto limpio -> DEJA SALIR DNI valido (letra correcta) -> BLOQUEA clases: nif Nombre de la deny-list, tal cual -> BLOQUEA clases: nombre_denylist El mismo nombre partido por un salto de linea -> BLOQUEA clases: nombre_denylist El mismo nombre con acento cambiado -> BLOQUEA clases: nombre_denylist IBAN de ejemplo del estandar, con guiones -> BLOQUEA clases: iban ``` 中间的那三个案例正是它存在的理由。一个简单的搜索器会抓住第一个案例,而漏掉另外两个:对它来说,分成两行的 `Fulano Menganez` 已经不再是 `Fulano Menganez` 了。 还要注意拦截信息:它说明了数据的类别,而不是数据本身。**PII 检测器的日志绝不能泄露 PII。** ### 工作原理 三项检查,无一靠猜: 1. **NIF 和 NIE**(西班牙税务和身份号码:本国公民使用 NIF,外籍居民使用 NIE):仅匹配模式是不够的。还会验证**校验字母**(模 23 算法)。一个带错误字母的 `12345678A` 不是身份证;它只是一个数字,不会被拦截。 2. **IBAN**:同样的标准,会验证标准的**模 97 校验和**。不会拦截任何仅仅以 ES 开头的内容。 3. **姓名**:与你提供的拒绝列表进行比对。它永远不会被提交:而是作为参数传入。 在进行任何比对之前,文本会经过彻底的规范化处理:移除不可见字符(整个 Unicode `Cf` 类别,而不仅仅是其中几个),并将与拉丁字母外观相同的字母表进行映射。这部分不是表面功夫。它是抵御红队测试的核心。 ### 红队测试 全绿的测试套件证明不了什么:它只能证明你想到的那些案例通过了。教训来自于同一系统中的另一个守卫,它有 15 个全绿案例,却在一个隐藏在 SQL 注释中的 `DROP` 语句面前**拦截失败**。 因此,这个关卡并不是因为有测试就被接受的。它被接受是因为它能在蓄意的绕过尝试中幸存下来:分两行的姓名、替换的重音符号、看起来一模一样的不同字母表、旧版编码、带连字符的 IBAN、带点的 IBAN、带换行符的 IBAN。 然而,它还是漏掉了这个: ``` EMP001 12345678Z 1850,00 -> did not block ``` 一个前面带数字的有效国民身份证会被放行。在 10 句包含身份证的自然语句中,有 5 句会泄露:只需在前面加上一个金额、一个年份或一个参考代码。原因是候选对象在扫描时没有重叠,因此前面的数字将身份证吞没在了一次失败的尝试中,而扫描继续进行,再也没有重新查看它。 在发布当天,一次代码审计发现了这个问题,当时测试库全绿,且有 11 项绕过测试。这 11 项测试中没有一项在身份证前面加上数字。而那一行正是引发本项目的经过假名化处理的工资单:关卡恰恰在它旨在监视的数据类型上发生了泄露。 这个问题已经修复,现在的陷阱存在于测试库(`r12`)中,并带有针对误报的防护(`r13`)。 ``` run_tests_pii.py 9 acceptance cases GREEN redteam_pii.py 13 evasion attempts GREEN regresion_pii.py 22 regression cases GREEN ``` 这是该系统中第二次出现全绿测试库拦截失败的情况。所以上面那句话不是装腔作势:这是刚刚再次发生的事情。 ### 诚实的边界 运行 demo,看看最后一个案例: ``` FRONTERA DECLARADA: un nombre que NO esta en la deny-list -> DEJA SALIR ``` `Ramiro Villalobos` 是一个名字,它通过了。**这不是一个 bug;这是设计如此。** 这一层只捕捉它可以*验证*的内容:你给它的列表,它能计算的校验码。它无法验证它不知道的姓名。如果检测器拦截了所有*看起来*像名字的内容,就不可能有零误报,而一个存在误报的关卡最终会被禁用,这是所有结果中最糟糕的。 捕捉未知的名字是另一个问题,需要语言模型。这一层不尝试这样做。 当针对一个带有**预注册**裁决的工资单语料库对其进行评估时,结果为**红色**。并且它被如实报告为红色。三次泄露中有两次正是由于这个边界在按照预期方式工作。 ### 异常不从文本中请求 第一个版本允许在文本中使用内联标记 `# pii:allow` 来授权异常。已将其从契约中移除。 原因:从你正在检查的内容本身读取的权限是一个注入面。如果文本能告诉守卫放行,守卫就会照办。进入这扇门的是数据(DATA),绝不是指令。异常在带外进行授权,作为参数传入。 ### 试一试 ``` git clone https://github.com/jleonceo/pii-output-gate cd pii-output-gate python demo.py # the story in 30 seconds python run_tests_pii.py # 9 acceptance cases python redteam_pii.py # 13 evasion attempts python regresion_pii.py # 22 regression cases ``` 没有 `pip install`。你不需要它。 ### 现有的工具 在发布这个之前,我去查看了现有的工具。有很多: - **`openai-guardrails`** 提供了一个 PII 检查来拦截输出,其中包含西班牙语实体。由 OpenAI 发布的同样的想法。 - **Microsoft Presidio** 使用其校验码检测西班牙语的 NIF、NIE 和护照。 - **`python-stdnum`** 验证 NIF、NIE、CIF、IBAN、CUPS 和地籍参考,没有项,并且涵盖的范围比这个更广。 所以这不是一个产品:对于几乎所有的真实用例,请使用 Presidio。这里剩下的,以及发布它的原因,是这种方法:一扇失败时关闭的门、真正的红队测试,以及被如实报告的红色结果。 ### 数据和隐私 这个 repo 中的所有内容都是虚构的,或者是其标准的公开规范示例:文档中的 IBAN(西班牙语、葡萄牙语和德语)、NIF `12345678Z` 和测试 NIE,以及教科书上的占位符名称(`Fulano Menganez`、`Zutana Perez`、`Ramiro Villalobos`)。完全没有真实的 PII。拒绝列表永远不会被提交:它作为参数传入。 ### 相关仓库 这个关卡是关于多智能体系统更广泛工作的一部分。它的兄弟项目: - [verificacion-determinista-ia](https://github.com/jleonceo/verificacion-determinista-ia):在没有 AI 的情况下重新检查数据一致性的护栏。那个向内看;这个向外看。 - [control-interno-fraude-ia](https://github.com/jleonceo/control-interno-fraude-ia):在内部控制框架内,使用算术进行会计欺诈检测。 - [accounting-agent-swarm](https://github.com/jleonceo/accounting-agent-swarm):产生这扇门所监视的输出的智能体集群。 - [gobernanza-skills-analiticas](https://github.com/jleonceo/gobernanza-skills-analiticas):管理所有这些的方法,带有 golden sets 和无回归关卡。 - [agent-memory-governance](https://github.com/jleonceo/agent-memory-governance):防止智能体的记忆变成一个垃圾场。 *构建者 / Built by [Juan Luis León Rodríguez](https://juanluisleon.vercel.app) · 2026 年 7 月 · 许可证 / License: [MIT](LICENSE)*
标签:AI安全网关, PII检测, Python, 文本过滤, 无后门, 网络安全, 逆向工具, 隐私保护