Agent写库前先签名:零信任写操作怎么做
很多 Agent 系统现在已经能:
改数据库
改CRM
发退款
写工单
更新配置
但真正落到生产以后,一个很难回答的问题是:
数据库里这一行,
到底是谁改的?
如果所有 Agent 都通过同一个连接池、同一个 Service Account 写入,日志里最多只能看到:
application=agent-platform
看不到:
哪个Agent
哪个Run
代表谁
为什么写
写入时参数是什么
Google 最近公开的 ADK 零信任示例里,把“每次状态变更都由具体 Agent 签名”作为第一层硬边界:生产环境可以给 Agent 分配独立 Workload Identity,并通过 Cloud KMS / HSM 保存非对称私钥;Agent 对写入 Payload 做确定性序列化和摘要,再请求 KMS 签名,数据库入口验证通过后才允许提交。
真正值得借鉴的不是某个 Cloud 产品,而是:
状态变更要从“有权限就能写”升级成“身份、内容和授权都能被证明”。
第一层:先定义“被签名的是什么”
危险做法:
只签 amount=149
这不够。
真正应该把完整业务动作纳入 Signature:
{
"agent_id": "refund-agent",
"run_id": "run-918",
"subject_id": "user-82",
"purpose": "customer-refund",
"order_id": "order-17",
"amount": 149.00,
"currency": "USD",
"idempotency_key": "refund-order-17-v1",
"expires_at": "2026-08-27T03:10:00Z"
}
如果只签金额,不签订单号:
签名可能被重放到另一个订单
如果不签 expires_at:
旧签名可能长期有效
如果不签 idempotency_key:
重复执行更难识别
Payload必须Canonicalize
JSON:
{"a":1,"b":2}
和:
{"b":2,"a":1}
逻辑相同,但字节不同。
如果签名前没有统一序列化,验证端很容易失败。
可以定义:
public interface CanonicalPayloadEncoder {
byte[] encode(Object payload);
}
规则至少包括:
字段排序
数字格式统一
时间统一UTC
Unicode Normalize
禁止浮点隐式精度变化
金额最好不要直接签:
149.0
149.00
而是转成:
minor units = 14900
我会签Minor Units
public record RefundAction(
String agentId,
String runId,
String orderId,
long amountMinor,
String currency,
String idempotencyKey,
Instant expiresAt) {
}
这样:
149.00 USD
稳定表示成:
14900
不会出现浮点序列化问题。
第二层:Agent不能持有私钥
最差实现:
AGENT_PRIVATE_KEY
放在:
.env
Kubernetes Secret
Container File
模型如果获得文件读取或环境变量访问能力,私钥就可能泄漏。
更好的:
Agent只有Sign权限
没有Export权限
私钥永远留在:
KMS / HSM
Runtime 只提交摘要。
签名请求也需要授权
不能让任何 Workload 都能调用:
refund-agent-key
KMS Policy:
只有 refund-agent Workload Identity
允许 sign
也就是说:
Agent Identity
→ Dedicated Signing Key
而不是所有 Agent 共用一把 Key。
为什么每个Agent独立Key有价值
如果:
research-agent
拿不到:
refund-agent-key
即使它设法调用数据库接口,也无法生成合法退款签名。
这形成:
Capability Separation
比 Prompt 里写:
“研究Agent禁止退款”
强很多。
第三层:验证必须发生在真正的写边界
签名检查放在 Agent Tool 内部还不够。
例如:
Agent
→ refund_tool
→ verify
→ DB
如果另一个服务可以直接连 DB:
绕过
所以真正验证位置应该尽量靠近:
State Mutation Boundary
例如:
Database Ingress Service
Write API
Stored Procedure
Event Consumer
一个Write Gateway
Agent
↓
Signed Action
↓
Write Gateway
├─ Signature Verify
├─ Expiry
├─ Idempotency
├─ Policy
├─ Resource Ownership
└─ Audit
↓
Database
数据库不直接暴露给 Agent。
第四层:签名不能替代业务Policy
合法签名只能证明:
这个Agent确实发了这个请求
不能证明:
这个请求业务上合理
例如 Refund Agent 合法签了:
$10,000
仍然应该被 Policy 拒绝。
if (action.amountMinor()
> order.refundableAmountMinor()) {
return Decision.DENY;
}
所以签名是:
Identity + Integrity
不是:
Business Authorization
第五层:签名必须绑定Authorization Decision
可以在 Action 里继续加入:
grant_id
approval_id
policy_version
例如:
{
"grant_id": "grant-77",
"approval_id": "approval-19",
"policy_version": "refund-policy-v12"
}
Gateway 验证:
签名有效
+
Grant有效
+
Approval有效
+
Policy仍允许
才提交。
第六层:签名要防Replay
攻击者拿到一份完整合法请求:
重复发10次
签名每次都有效。
所以一定需要:
idempotency_key
Gateway 保存:
create table write_idempotency (
idempotency_key varchar(256) primary key,
action_hash varchar(64) not null,
status varchar(32) not null,
result_ref varchar(512),
created_at timestamptz not null
);
第一次:
执行
后面同 Key:
返回原结果
如果 Key 相同但 Action Hash 不同:
SECURITY ERROR
第七层:Expiry要短
一个签名动作最好只活:
30秒
2分钟
5分钟
而不是一天。
验证:
if (Instant.now()
.isAfter(action.expiresAt())) {
throw new ExpiredActionException();
}
长期审批可以存在更久。
真正执行签名应该短。
第八层:数据库行要保存Signature Evidence
不要验证完就丢。
例如:
create table refund_ledger (
refund_id varchar(128) primary key,
order_id varchar(128) not null,
amount_minor bigint not null,
agent_id varchar(128) not null,
run_id varchar(128) not null,
action_hash varchar(64) not null,
signature_ref varchar(512) not null,
key_version varchar(64) not null,
created_at timestamptz not null
);
以后可以独立做:
Integrity Audit
后台Audit怎么工作
每天抽样或全量:
读取Ledger
↓
重新Canonicalize
↓
计算Hash
↓
验证历史Signature
如果某行被 SQL 直接改过:
Hash对不上
立即报警。
这就是签名真正的价值之一:
数据库本身被修改
也能发现
Key Rotation必须保存Key Version
如果 Key 每90天轮换:
旧记录
仍然需要验证。
所以签名记录不能只存:
key_id
要存:
key_version
历史 Public Key 继续保留用于 Verify。
Key Rotation流程
Create new key version
↓
New writes use v2
↓
Old v1 disable signing
↓
Keep v1 public verify
↓
After retention ends archive
不要直接删除旧 Key。
Agent-to-Agent写操作更要签名
Supervisor:
让 refund-agent 执行退款
最终签名者应该是:
refund-agent
同时保留:
parent_run
delegation_id
这样能还原:
谁提出
谁授权
谁真正写
不要让 Supervisor 用自己的 Key 替所有 Sub-Agent 签。
一个Delegated Action
public record DelegatedWriteAction(
String agentId,
String parentAgentId,
String delegationId,
String runId,
String capability,
String resourceId,
String actionHash) {
}
Write Signature最适合哪些动作
非常适合:
退款
预算修改
权限修改
数据库关键状态
部署状态
合同审批状态
不一定需要给:
每条Debug日志
都签。
优先覆盖:
高价值、不可逆、合规要求高
的写操作。
最少测试这10种情况
1. Payload字段顺序变化
2. 金额被篡改
3. Order ID被篡改
4. 签名过期
5. 相同Idempotency重复提交
6. 同Key不同Payload
7. Agent使用错误Key
8. Grant撤销后执行
9. Key Rotation后验证旧记录
10. 直接SQL修改Ledger后Audit报警
签名解决不了什么
它解决不了:
模型为什么做出错误决定
也解决不了:
业务Policy写错
它能提供的是:
确定是谁
确定签了什么
确定内容没被改
确定历史可验证
这已经是生产 Agent 非常重要的一层。
Agent 有写权限以后,真正成熟的系统不能只依赖:
数据库账号有权限
而应该继续建立:
Workload Identity
+
Action Signature
+
Policy
+
Approval
+
Idempotency
+
Immutable Audit
这样即使模型被诱导、服务被误配或数据库记录被直接修改,系统仍然有硬边界和证据。
Prompt 可以告诉 Agent:
“不要乱写。”
签名和 Gateway 则能证明:
这次到底是谁写的、写了什么、是否经过授权。
这两个层次完全不是一回事。
更多企业级 AI 应用、Agent、RAG 与模型工程化内容,我会继续整理在 智元界:
https://www.zyentor.com/