跳到主要内容

PII 检测与脱敏

PII 安全护栏会检测请求和响应文本中的敏感数据。它可以将每个命中内容替换为脱敏占位符并继续处理,也可以阻断请求或响应。

PII 检测完全在网关内执行,不会调用外部审核服务。命中的原始值不会写入网关日志、用量记录或错误响应。

本指南将创建一个 PII 安全护栏,用于脱敏内置检测器命中的内容;随后添加自定义模式,并验证脱敏与阻断行为。

准备工作

请先准备以下内容:

  • 阅读安全护栏行为,了解钩子点和执行模式。
  • 以下配置路径之一:
    • AISIX Cloud,其中包含环境、已接入的网关和具有写权限范围的 Admin Token。对于 On-Premises,请按照 AISIX Cloud 快速入门操作。如需申请混合云访问权限,请联系 API7
    • 加载声明式 resources.yaml 文件的开源 AISIX 网关。
  • 可以发送 Chat Completions 请求的模型别名和调用方 API Key。
  • curl。AISIX Cloud 路径还会使用 jq

内置检测器

detectors 中列出检测器的 type 即可启用。其中 china_id_cardbank_card 这两个检测器会在匹配前验证校验和,避免随机数字串被误判并脱敏。

type匹配内容
email邮箱地址
china_mobile中国大陆手机号码
china_id_card中国大陆居民身份证号码(ISO 7064 校验和)
bank_card银行卡号(Luhn 校验和)
us_ssn美国社会安全号码
ip_addressIPv4 地址
api_keyAPI Key 和 Token(OpenAI、AWS、GitHub、Slack、Google 签名)
jwtJSON Web Token
private_keyPEM 私钥块

命中动作

每次命中都会解析为以下动作之一:

  • mask:将命中的文本片段替换为 [EMAIL_REDACTED] 等脱敏占位符。输入脱敏后继续调用上游模型,输出脱敏后继续返回调用方。
  • block:以 422 Unprocessable Entity 拒绝请求或响应。

default_action 会设置所有检测器和自定义模式的默认动作。也可以在单个检测器或自定义模式上设置 action 覆盖默认动作。

创建 PII 安全护栏

以下示例会在 AISIX 将请求发送到上游模型服务提供方前,对调用方请求中的邮箱地址和 API Key 进行脱敏。请选择一种配置路径,再使用通用验证步骤。

导出两种路径都会使用的网关参数:

# AISIX_PROXY 末尾不包含斜杠或端点路径。
# 本地快速入门使用 http://127.0.0.1:3000。
export AISIX_PROXY="YOUR_AISIX_GATEWAY_URL"
export AISIX_API_KEY="YOUR_CALLER_API_KEY"
export AISIX_MODEL="gpt-4o-mini"

AISIX Cloud

导出控制平面连接参数:

# AISIX_CP 包含 /api,末尾不包含斜杠。
# 本地 On-Premises 快速入门使用 http://localhost:8080/api。
export AISIX_CP="YOUR_AISIX_CLOUD_ADMIN_API_BASE_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"

创建一个输入安全护栏,对 emailapi_key 检测器执行脱敏,并获取其 ID:

GUARDRAIL_ID=$(curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/guardrails" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "pii-redaction-policy",
"enabled": false,
"hook_point": "input",
"kind": "pii",
"config": {
"default_action": "mask",
"detectors": [
{ "type": "email" },
{ "type": "api_key" }
]
}
}' | jq -r '.guardrail.id')

❶ 先以禁用状态创建,再在下面附加范围、最后启用,可以让这三步的顺序与控制台一致。安全护栏在附加之前不会检查任何流量。

input 会在 AISIX 将调用方请求发送到上游前进行脱敏。使用 output 可以脱敏服务提供方响应,使用 both 可以同时覆盖两侧。更多信息请参见检查位置

default_action 设置列出检测器的动作。mask 会将每个命中替换为脱敏占位符。只有当某个检测器需要不同行为时,才设置检测器级别的 action

将其附加到整个环境(scope_type: "env" 会绑定环境中的每个请求并省略 scope_id;如需缩小范围,请使用 modelapi_keyteam 并提供 scope_id):

curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/guardrails/$GUARDRAIL_ID/attachments" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "scope_type": "env" }'

挂载存在后启用安全护栏:

curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID/guardrails/$GUARDRAIL_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'

启用后的配置会自动投射到已接入的网关。

开源 AISIX 网关

在已定义示例模型和调用方 API Key 的资源文件中添加安全护栏:

resources.yaml(PII 脱敏安全护栏)
guardrails:
- name: pii-redaction-policy
enabled: true
hook_point: input
kind: pii
default_action: mask
detectors:
- type: email
- type: api_key

guardrail_attachments:
- guardrail_id: pii-redaction-policy
scope_type: env
priority: 100

mask 会把每个检测到的文本片段替换为脱敏占位符,并让请求继续。

❷ 每个检测器都在网关内部运行。如果某个检测器需要覆盖默认动作,可以设置 action: block

安全护栏只在挂载指定的范围内生效:请添加 guardrail_attachments 条目引用它,否则它虽然会被加载,但不会检查任何流量。请验证完整文件,然后重新加载网关。可运行的 Docker 工作流参见重新加载资源文件

对于响应侧 PII 脱敏,如果遇到流式流量,AISIX 会保留响应内容,直到可以应用安全护栏。缓冲默认值和溢出行为请参见流式输出

验证脱敏

发送一个提示词中包含邮箱地址的请求。AISIX Cloud 投射是异步的;如果第一个请求尚未体现新规则,请等待网关应用最新修订后重试。收敛检查参见资源投射

curl -sSi -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"model": "${AISIX_MODEL}",
"messages": [
{
"role": "user",
"content": "email me at alice@example.com about the order"
}
]
}
EOF

请求会以 HTTP/1.1 200 OK 成功返回。AISIX 会先改写提示词,再调用上游模型,因此服务提供方收到的是:

email me at [EMAIL_REDACTED] about the order

原始值不会到达上游服务提供方、网关日志或用量记录。用量记录只携带每个检测器的命中次数,包括检测器名称,但不包含命中文本。

阻断敏感数据

当某个检测器应拒绝流量而不是脱敏时,请使用 block。以下示例会添加第二个安全护栏,阻断包含有效中国大陆居民身份证号码的请求。

AISIX Cloud

创建阻断安全护栏,并将其附加到环境:

BLOCK_GUARDRAIL_ID=$(curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/guardrails" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "block-id-card",
"enabled": false,
"hook_point": "input",
"kind": "pii",
"config": {
"default_action": "block",
"detectors": [
{ "type": "china_id_card" }
]
}
}' | jq -r '.guardrail.id')

curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/guardrails/$BLOCK_GUARDRAIL_ID/attachments" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "scope_type": "env" }'

curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID/guardrails/$BLOCK_GUARDRAIL_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'

开源 AISIX 网关

resources.yaml 中把阻断安全护栏添加到脱敏安全护栏旁边:

resources.yaml(PII 阻断安全护栏)
guardrails:
- name: pii-redaction-policy
enabled: true
hook_point: input
kind: pii
default_action: mask
detectors:
- type: email
- type: api_key

- name: block-id-card
enabled: true
hook_point: input
kind: pii
default_action: block
detectors:
- type: china_id_card

guardrail_attachments:
- guardrail_id: pii-redaction-policy
scope_type: env
priority: 100
- guardrail_id: block-id-card
scope_type: env
priority: 100

验证完整文件并重新加载网关。

验证阻断

如果请求内容包含有效身份证号码,会被拒绝:

curl -sSi -X POST "$AISIX_PROXY/v1/chat/completions" \
-H "Authorization: Bearer ${AISIX_API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @- <<EOF
{
"model": "${AISIX_MODEL}",
"messages": [
{
"role": "user",
"content": "my id is 11010519491231002X"
}
]
}
EOF

被阻断的响应会以 HTTP/1.1 422 Unprocessable Entity 开头,并包含如下响应体:

{
"error": {
"message": "request blocked by content policy (guardrail 'block-id-card')",
"type": "content_filter"
}
}

该消息会刻意保持通用,并且不会回显命中的原始值。

添加自定义模式

使用 custom_patterns 可以检测组织内部特有的数据。每个模式都需要 name 和兼容 Rust 的 regex,另可选填每个模式各自的 actionreplacement。AISIX 会在脱敏占位符和命中计数中使用该名称。无效表达式会阻止安全护栏进入活动链路。

以下示例会在保留现有邮箱和 API Key 检测器的同时,为 pii-redaction-policy 添加员工 ID 模式。

AISIX Cloud

使用扩展后的检测器集合替换现有安全护栏配置:

curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID/guardrails/$GUARDRAIL_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"config": {
"default_action": "mask",
"detectors": [
{ "type": "email" },
{ "type": "api_key" }
],
"custom_patterns": [
{
"name": "employee_id",
"regex": "EMP-[0-9]{6}"
}
]
}
}'

安全护栏配置发生更改时,现有挂载会继续保留。

开源 AISIX 网关

把相同模式添加到现有安全护栏条目:

resources.yaml(自定义 PII 模式)
guardrails:
- name: pii-redaction-policy
enabled: true
hook_point: input
kind: pii
default_action: mask
detectors:
- type: email
- type: api_key
custom_patterns:
- name: employee_id
regex: 'EMP-[0-9]{6}'

验证完整文件并重新加载网关。无论采用哪种配置路径,命中该模式时都会脱敏为 [EMPLOYEE_ID_REDACTED]

选择脱敏后的文本

默认情况下,被脱敏的片段会变成由该模式的 name 构成的 [<NAME>_REDACTED]。在模式上设置 replacement 即可自行指定文本;设为空字符串则直接删除该片段。

custom_patterns:
- name: employee_id
regex: 'EMP-[0-9]{6}'
replacement: '******'

结果由两条规则决定,两条都与正则工具的惯常行为不同:

  • replacement 按字面量使用。 $1 等分组引用不会被展开。这与透传路由上的 url_rewrites 正好相反,那里的 $1 会展开。
  • 正则中带捕获组时,只替换第 1 组。 匹配的其余部分原样保留。不带捕获组的正则则替换整个匹配。

第二条规则正是模式表达「替换值、保留键」的方式。下例只脱敏版本号而保留字段名:

custom_patterns:
- name: eda_version
regex: 'version: (\S+)'
replacement: '***'

version: 12.1 变为 version: ***

把周围文本一并写进 replacement 是常见的误用,而且不会被拒绝:

# 错误:正则本身已经保留了 `ACCT-` 和 `-5678`,而且 `$1` 是字面量。
custom_patterns:
- name: account
regex: 'ACCT-([0-9]{4})-([0-9]{4})'
replacement: 'ACCT-$1-****'

ACCT-1234-5678 会变成 ACCT-ACCT-$1-****-5678——它会被接受,而且看上去仍然像是打过码的。正确做法是把上下文留在捕获组之外:

custom_patterns:
- name: account
regex: 'ACCT-([0-9]{4})-([0-9]{4})'
replacement: '****'

ACCT-1234-5678 变为 ACCT-****-5678

replacement 仅对 mask 生效。实际动作为 block 的模式会直接拒绝请求而不是改写内容,因此同时设置两者会报错。

决定 AISIX 无法扫描时的行为

PII 安全护栏在网关内部运行,从不调用外部服务,因此不存在后端故障。但它仍然可能被交到读不懂的请求体面前——不是合法 UTF-8 的请求体。AISIX 会拒绝这类请求,而不是既不脱敏也不扫描地转发它,而决定这一点的设置就是 fail_open

fail_open 默认为 false:安全护栏拿不到的内容会被 422 拒绝,消息中会指名失败标记 unscannable_body。如果可用性比治理更重要,把它设为 true。这次放行仍然会作为一次绕过记录在该请求的用量记录上,可以据此告警。

在 AISIX Cloud 中,该选项位于安全护栏的创建和编辑表单上。在资源文件中,把它写在安全护栏条目上:

resources.yaml(放行安全护栏读不懂的流量)
guardrails:
- name: pii-redaction-policy
kind: pii
fail_open: true
default_action: mask
detectors:
- type: email
- type: api_key

依赖它之前有两个限制值得先了解。这个类型没有对应的 output_fail_open,因此这一个取值同时管它的两个检查侧;它与 on_buffer_exceeded 是两回事,后者决定被保留的响应超出 max_buffer_bytes 时会发生什么。而且 fail_open 管不到已经被保留下来的流式响应:输出侧的 PII 安全护栏会缓冲流式输出以便跨 chunk 边界脱敏,此时最终没有留下任何可扫描内容的流,无论 fail_open 取何值都会被拒绝。

早期版本的网关在 pii 上完全忽略该字段。今天携带 fail_open: true、而设置时该字段还什么都不做的规则,会在网关升级后改变行为。参见安全护栏无法完成检查时

下一步

你已经配置内置 PII 安全护栏,并验证了脱敏和阻断行为。使用下面的指南继续调整或扩展策略: