kisenon
Agent-Safe Change Control

掩码分叉

给智能体(或人)生产环境的形态而不含其任何 PII——脱敏策略、内置函数库,以及脱敏分支和沙箱如何在脱敏提交前保持封闭。

MASK 是面向敏感数据的 Agent-Safe Change Control 轨道:它交给智能体一个具有生产环境 形态、 却不含其任何 PII 的 fork。它以两种方式工作——脱敏 分支 (面向人:开发、CI、外包),以及脱敏 沙箱 (面向智能体)。两者都在下面涵盖。

数据脱敏分支 创建的那一刻就匿名化敏感列。 你在创建分支的调用上附加一个 脱敏策略,新分支中匹配的列会在 分支可被访问之前就被不可逆地重写——哈希、置空、截断或替换。 父分支永远不会被修改。

当你需要把生产形态的数据交给开发、CI 或 外包人员,却又不想把其中的 PII 一并交出去时,你会用到它:真实的表结构、 真实的行数、伪造的邮箱。

How it works

一个脱敏策略是一组项目范围的、具名的规则。每条规则 按模式匹配列并指定一个内置脱敏函数:

字段含义
schema_pattern模式(schema)名称匹配模式(%* = 任意,_ = 一个字符)。默认为 *
table_pattern表名匹配模式。
column_pattern列名匹配模式。
masking_fn下列内置函数之一。
fn_args函数参数(只有 mask_constant 接受一个:value)。

在创建分支时,当提供了 masking_policy_id

  1. 分支照常从其父分支派生(写时复制——瞬时完成)。
  2. 分支进入 masking 状态。不会预配任何端点, 代理也拒绝对它的连接:不存在任何窗口 能读取到脱敏前的数据。
  3. 一个脱敏工作器以最小权限角色连接到该分支的计算, 发现其模式,将你的规则 与之匹配,并在单个事务中执行每一次重写。
  4. 分支落入 ready 且其端点启动——此时只 提供脱敏后的数据。一旦出现任何失败,分支落入 failed 并保持封闭;删除它并重试。

规则输入始终被当作数据处理,绝不当作 SQL:标识符会被加 引号,参数值在执行时作为参数绑定,因此一个恶意的 列名或常量无法突破重写。

Built-in functions

函数效果
mask_emailmd5(value)@masked.invalid
mask_nameName_ + 一个 8 字符的哈希前缀
mask_nullNULL(列必须可为空)
mask_constant你提供的一个固定值(fn_args.value
mask_ssn_partial***-**-1234 —— 保留最后 4 位
mask_credit_card****-****-****-1234 —— 保留最后 4 位
mask_ip0.0.0.0
mask_date_year保留年份,截断到 1 月 1 日
mask_hashmd5(value)
mask_shuffle基于哈希的打乱(完整的字符乱序在计划中)
mask_phone一个合成的 +1-555-XXXX 号码
mask_uuid一个全新的随机 UUID

该目录也由 API 提供:

curl -s https://api.kisenon.com/v1/masking-functions \
  -H "Authorization: Bearer $KISENON_API_KEY"

Managing policies

在控制台中,打开 Project settings → Data masking 来创建一个 策略:为其命名,添加规则(模式列 + 一个函数下拉框),然后 保存。同样的入口也存在于 API 上:

curl -s -X POST \
  https://api.kisenon.com/v1/projects/$PROJECT_ID/masking-policies \
  -H "Authorization: Bearer $KISENON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "dev-safe",
    "rules": [
      {"table_pattern": "users", "column_pattern": "email", "masking_fn": "mask_email"},
      {"table_pattern": "users", "column_pattern": "phone", "masking_fn": "mask_null"},
      {"table_pattern": "%", "column_pattern": "%ssn%", "masking_fn": "mask_ssn_partial"}
    ]
  }'

然后通过把策略加入一个普通的创建分支调用来创建一个 脱敏分支:

curl -s -X POST \
  https://api.kisenon.com/v1/projects/$PROJECT_ID/branches \
  -H "Authorization: Bearer $KISENON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "masked-dev", "masking_policy_id": "'$POLICY_ID'"}'

重写运行期间该分支报告 state: "masking";在控制台中(实时, 通过项目 事件流)或通过轮询 GET /v1/branches/{id} 观察它翻转为 ready

Masked sandboxes (for agents)

同样的策略脱敏一个智能体的 沙箱 fork,因此 一个智能体在生产环境的形态上工作,却永远看不到它的 PII。在创建时 附加一个策略:

keon sandbox create --project <id> --masking-policy <policy-id>

脱敏在沙箱激活 之前 运行。沙箱报告 masked: true 及其 masking_policy_id,并且在它仍处于 creating 时, 一个 masking 进度对象(statephaserules_totalrules_completedrows_affected_so_farerror)显示重写 推进中。

设定一个每项目的下限,使每个沙箱默认都被脱敏:

keon projects update <id> --sandbox-masking-policy <policy-id>   # or: none

一个每请求的 --masking-policy 覆盖项目默认值,但它 绝不能选择退出 一个强制项——这是对一个智能体 说服自己脱离脱敏的一道防御。设定该强制项仅限 owner/admin; 智能体能力密钥会被拒绝(403)。

构造上失败即关闭。 一个脱敏 fork 的端点 诞生 于一个 脱敏状态——代理拒绝连接(SQLSTATE 57P05),直到 重写提交。如果脱敏步骤失败或未接线,创建会失败 即关闭:绝不会为一个未脱敏的 fork 签发任何 URL。脱敏工作器 以最小权限角色 kisenon_masker 连接(绝非超级用户), 不持有任何集群 RBAC,且其每次运行的密码绝不被持久化或记录。

两个值得知道的注意事项:

  • 针对脱敏值的 DML 在重放时不会在父分支上匹配。 一条 像 UPDATE … WHERE email = 'a1b2@masked.invalid' 的语句针对一个 在 main 上并不存在的脱敏值。模式通道的工作是 与值无关的,会干净地提升;依赖值的 DML 不是一个脱敏 fork 该做的活。
  • 外键仍被强制。 脱敏禁用 用户 触发器 (ALTER TABLE … DISABLE TRIGGER USER),而非引用完整性触发器。 因此脱敏一个处于外键关系中的列可能命中一个 RI 错误并 让 fork 失败即关闭,而非悄悄破坏该引用。
  • v1 仅脱敏 main 数据库。

Good to know

  • 脱敏是一次性的,发生在创建时。 编辑一个策略永远不会触及那些 已经用它创建的分支——它们保持自己诞生时的数据形态。 重新创建分支以应用新规则。
  • 使用中的策略无法删除。 删除一个有活动 分支用它创建过的策略会返回 409 policy_in_use;先删除那些 分支。
  • 未匹配的规则会被跳过,而非致命。 如果你的模式发生了漂移而 某个模式什么也匹配不到,分支仍会完成——如果你预期被脱敏的 某列仍保留着真实的数据形态,请检查规则模式。
  • 类型不匹配会使分支失败。 一条匹配到其函数无法重写的列的 规则(比如对一个 integermask_email)会中止整个脱敏—— 分支落入 failed,绝不会以脱敏到一半的状态暴露。