“不信任”的最高准则:Agent 的零信任权限模型 (Zero Trust)
What(本文讲什么)
对 agent 系统来说,零信任不是网络口号,而是“工具执行链的权限模型”:默认不信任任何 tool call,把每一次副作用当成一次独立授权决策,并在执行点强制落地(PEP)。
这篇文章把零信任落到 agent runtime 的可实现结构:
- PDP/PEP:策略决策点与策略执行点分别在哪里。
- ABAC:授权不仅看身份,还看任务上下文与资源目标。
- 失败与降级:策略查询超时怎么办、审批链失败怎么办(超时、降级)。
- 审计证据链:每一次允许/拒绝都必须可追溯(审计、观测)。
NIST SP 800-207 给出了零信任架构的逻辑组件(PDP/PEP 等)与基本原则,是这篇文章的标准锚点。
参考:
- https://csrc.nist.gov/pubs/sp/800/207/final
- https://nvlpubs.nist.gov/nistpubs/specialpublications/NIST.SP.800-207.pdf
Problem(要解决的工程问题)
当 agent 接入的工具越来越多时,传统“发一个 API key”会出现结构性风险:
- 权限过大:一个 key 通吃所有工具,一次 prompt injection 可能造成大规模越权(权限)。
- 权限无上下文:修 CSS 的任务也能删除数据库,因为授权只看身份不看意图。
- 重试放大副作用:超时后重试,重复提交不可逆动作(幂等、重试)。
- 不可追溯:出事后无法回答“谁允许了这次调用、为何允许、影响了哪些资源”(审计、观测)。
所以零信任的目标不是“让系统更难用”,而是把副作用通道收口成可治理协议。
Principle(PDP/PEP:策略必须在执行点生效)
零信任最重要的边界是把“决策”与“执行”分离:
- PDP(Policy Decision Point):根据输入属性计算 Permit/Deny。
- PEP(Policy Enforcement Point):在真实工具执行前强制执行 PDP 的结果,能够阻断、限流、终止会话。
对 agent 来说,PEP 的位置非常具体:就在 tool registry / tool executor 前一层。模型输出永远不能直接触发副作用。
ABAC:授权要绑定任务与资源
ABAC(属性访问控制)的核心是:授权由多类属性共同决定,而不是只看“你是谁”。对 agent 最重要的属性通常包括:
task_id/task_type(当前任务是什么)。tool_name/tool_risk_level(调用的工具是什么、风险级别)。resource_targets(目标资源集合:路径、域名、表名、项目 id)。actor(agent id / 用户 id / 环境)。history(最近是否触发过拒绝、是否出现重试风暴)。
Usage(怎么做:把零信任接入工具执行链)
1) 先把工具分级(risk tiers)
最小分级建议:
read_only:只读查询,不产生副作用。write_low:可写但可回滚(例如生成 patch,但不直接提交)。write_high:不可逆或高风险(删除、支付、发布、权限变更)。
高风险工具默认必须 HITL(人工批准),并且要展示可视化 diff/预览(审计)。
2) policy-as-code:用 OPA 类决策引擎表达策略
策略不应散落在代码里。OPA 提供 policy-as-code 的决策引擎入口,你可以把它当 PDP:输入属性,输出决策。
参考: https://www.openpolicyagent.org/docs
3) 一个可落地的策略文件(示例)
这份策略示例强调三件事:
- 按 tool 分级授权。
- 按 resource_targets 做白名单/黑名单。
- 明确超时/重试/幂等/审计字段要求。
# zero_trust_policy.yaml
rules:
- tool: "file_read"
risk: "read_only"
allow_paths: ["content/**", "src/**"]
deny_paths: [".env", "**/secrets/**", "~/.ssh/**"]
- tool: "apply_patch"
risk: "write_low"
allow_paths: ["content/**", "src/**"]
require_wal: true
require_idempotency_key: true
timeout_ms: 10000
max_retries: 1
- tool: "shell_exec"
risk: "write_high"
require_approval: true
allowed_commands: ["npm test", "npm run build", "git status"]
forbidden_commands: ["rm", "chmod", "curl", "sh"]
timeout_ms: 600000
max_retries: 0
4) PEP 实现骨架:在执行前强制阻断
class PolicyDecision:
def __init__(self, *, allowed: bool, reason: str):
self.allowed = allowed
self.reason = reason
class PolicyEngine:
"""PDP:输入属性,输出允许/拒绝。"""
async def decide(self, *, tool_name: str, attrs: dict) -> PolicyDecision:
raise NotImplementedError
class ToolEnforcer:
"""
PEP:工具执行前的强制点。
"""
def __init__(self, *, policy: PolicyEngine, auditor):
self._policy = policy
self._auditor = auditor
async def call(self, *, tool_name: str, args: dict, attrs: dict):
decision = await self._policy.decide(tool_name=tool_name, attrs=attrs)
await self._auditor.record_policy_decision(tool_name, args, attrs, decision)
if not decision.allowed:
raise PermissionError(decision.reason)
return await self._execute(tool_name, args)
注意:record_policy_decision 是审计证据链的一部分,必须写入不可篡改审计日志(审计)。
5) 超时、重试、幂等:零信任也必须覆盖可靠性
很多人把零信任写成“允许/拒绝”,但对 agent 来说还不够。你必须把可靠性纳入策略:
- 超时:策略查询超时怎么办?工具执行超时怎么办?(超时)
- 重试:拒绝是否可重试?允许是否可重试?(重试)
- 幂等:允许后的副作用是否允许重放?必须靠幂等 key + WAL(幂等、审计)
- 降级:策略系统不可用时是否默认拒绝?是否允许只读降级?(降级)
HITL 与 kill switch(人类是最后一道执行门禁)
对高风险工具,必须支持:
- 可视化预览:命令预览、diff 预览、资源目标预览(审计)。
- 一键终止:停止当前任务并清理子进程,避免动作继续扩散(资源释放)。
Pitfall(常见坑与防错)
- 只做认证不做授权:登录不等于允许操作(权限)。
- 策略散落在代码里:无法复审与回滚(审计、回滚)。
- PEP 不在执行点:只在 prompt 里约束没有意义(隔离)。
- 策略查询无超时:策略系统慢会拖死整个执行链(超时、降级)。
- 没有审计证据:无法回答“为什么允许/拒绝”(审计、观测)。
Debug(排查零信任系统)
排查顺序建议:
- policy input:attrs 是否齐全(task_id/resource_targets 等)?
- policy decision:拒绝原因是否可解释?是否被审计记录?
- PEP 生效:是否真的阻断了工具执行?
- 超时/降级:策略系统不可用时的默认行为是否符合预期?
审计日志的最小 schema(建议硬固定)
零信任的价值之一是“可追责”。因此每次决策都必须产生审计记录,至少包含:
task_id/trace_idtool_nameresource_targetsdecision(permit/deny)reason_code(例如 policy_rule_id)idempotency_key(对写操作)approved_by(若触发 HITL)latency_ms(PDP 决策耗时,便于定位超时)
没有这些字段,你无法回答“为什么允许/拒绝”,零信任就会退化成黑盒拦截器(审计、观测)。
策略查询超时怎么办(必须写清楚的降级策略)
PDP 本身也是一个依赖。它会慢、会超时、会不可用。你必须提前决定:
- 默认拒绝(推荐):PDP 不可用时直接拒绝高风险 tool(权限)。
- 只读降级:允许 read_only tools 继续运行,但禁止 write_high(降级)。
- 熔断:PDP 连续超时 N 次,暂停所有副作用工具并要求人工介入(超时、降级)。
如果你不写清楚这一点,系统会在故障时“随机放行或随机拒绝”,事故半径不可控。
策略缓存(谨慎使用)
很多人第一反应是缓存策略决策来提速,但对 agent 来说缓存有风险:上下文属性变了,旧决策可能不再成立。
缓存策略如果要做,至少要满足:
- key 必须包含 task_id/tool_name/resource_targets/风险等级。
- TTL 必须很短,并且只缓存 permit,不缓存 deny(避免拒绝锁死)。
- 一旦发生敏感事件(例如权限拒绝/异常工具调用),立即清空相关缓存(审计)。
事故复盘模板(把权限事故变成可迭代输入)
当出现越权或误放行时,复盘至少要回答:
- 这次调用的属性输入是什么?(attrs)
- PDP 为什么 permit?命中哪条规则?
- PEP 是否真的执行了 permit 的约束(超时/幂等/路径白名单)?
- 是否有重试导致副作用放大(重试、幂等)?
- 如何修改 policy-as-code 防止同类事故再次发生(回滚、审计)?
Source(资料来源)
- NIST SP 800-207: https://csrc.nist.gov/pubs/sp/800/207/final
- NIST SP 800-207 PDF: https://nvlpubs.nist.gov/nistpubs/specialpublications/NIST.SP.800-207.pdf
- OPA docs: https://www.openpolicyagent.org/docs
- BeyondCorp(实践框架入口): https://beyondcorp.com/