Agent 通过员工账号调用订单接口,只写了一条审批备注。功能测试看似通过,但同一个账号还能导出客户数据和改变审批状态。权限问题不在这次调用做错了,而在运行环境允许它做更多事。
本文用 Python 策略函数、SQLite 事件表和短期能力票据构造最小权限示例。订单流程是虚构工程场景,不包含真实客户、事故或效果统计。示例用于说明状态和测试边界,不替代生产身份系统。
技术路线
#mermaid-svg-NynxDMnWWGd87pZg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NynxDMnWWGd87pZg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NynxDMnWWGd87pZg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NynxDMnWWGd87pZg .error-icon{fill:#552222;}#mermaid-svg-NynxDMnWWGd87pZg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NynxDMnWWGd87pZg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NynxDMnWWGd87pZg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NynxDMnWWGd87pZg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NynxDMnWWGd87pZg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NynxDMnWWGd87pZg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NynxDMnWWGd87pZg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NynxDMnWWGd87pZg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NynxDMnWWGd87pZg .marker.cross{stroke:#333333;}#mermaid-svg-NynxDMnWWGd87pZg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NynxDMnWWGd87pZg p{margin:0;}#mermaid-svg-NynxDMnWWGd87pZg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NynxDMnWWGd87pZg .cluster-label text{fill:#333;}#mermaid-svg-NynxDMnWWGd87pZg .cluster-label span{color:#333;}#mermaid-svg-NynxDMnWWGd87pZg .cluster-label span p{background-color:transparent;}#mermaid-svg-NynxDMnWWGd87pZg .label text,#mermaid-svg-NynxDMnWWGd87pZg span{fill:#333;color:#333;}#mermaid-svg-NynxDMnWWGd87pZg .node rect,#mermaid-svg-NynxDMnWWGd87pZg .node circle,#mermaid-svg-NynxDMnWWGd87pZg .node ellipse,#mermaid-svg-NynxDMnWWGd87pZg .node polygon,#mermaid-svg-NynxDMnWWGd87pZg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NynxDMnWWGd87pZg .rough-node .label text,#mermaid-svg-NynxDMnWWGd87pZg .node .label text,#mermaid-svg-NynxDMnWWGd87pZg .image-shape .label,#mermaid-svg-NynxDMnWWGd87pZg .icon-shape .label{text-anchor:middle;}#mermaid-svg-NynxDMnWWGd87pZg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NynxDMnWWGd87pZg .rough-node .label,#mermaid-svg-NynxDMnWWGd87pZg .node .label,#mermaid-svg-NynxDMnWWGd87pZg .image-shape .label,#mermaid-svg-NynxDMnWWGd87pZg .icon-shape .label{text-align:center;}#mermaid-svg-NynxDMnWWGd87pZg .node.clickable{cursor:pointer;}#mermaid-svg-NynxDMnWWGd87pZg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NynxDMnWWGd87pZg .arrowheadPath{fill:#333333;}#mermaid-svg-NynxDMnWWGd87pZg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NynxDMnWWGd87pZg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NynxDMnWWGd87pZg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NynxDMnWWGd87pZg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NynxDMnWWGd87pZg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NynxDMnWWGd87pZg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NynxDMnWWGd87pZg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NynxDMnWWGd87pZg .cluster text{fill:#333;}#mermaid-svg-NynxDMnWWGd87pZg .cluster span{color:#333;}#mermaid-svg-NynxDMnWWGd87pZg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NynxDMnWWGd87pZg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NynxDMnWWGd87pZg rect.text{fill:none;stroke-width:0;}#mermaid-svg-NynxDMnWWGd87pZg .icon-shape,#mermaid-svg-NynxDMnWWGd87pZg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NynxDMnWWGd87pZg .icon-shape p,#mermaid-svg-NynxDMnWWGd87pZg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NynxDMnWWGd87pZg .icon-shape .label rect,#mermaid-svg-NynxDMnWWGd87pZg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NynxDMnWWGd87pZg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NynxDMnWWGd87pZg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NynxDMnWWGd87pZg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} deny
human_required
allow
验证Agent工作负载身份
读取可信资源属性和版本
策略引擎检查动作字段和条件
保存拒绝原因
创建绑定资源版本的审批
审批通过且未过期
签发短期能力票据
动作网关校验票据与业务版本
执行远端接口
回执核验并追加审计事件
模型生成候选动作,不负责证明自己有权限。策略引擎只处理结构化字段,审批系统产生人的授权事件,动作网关执行前再校验。把这几个责任分开,能区分模型建议错误、策略配置错误和业务接口结果错误。
天远大数据在该权限策略表结构示例中,把动作、资源字段、策略版本和决策原因作为可查询列,便于回归测试具体边界,而不是依赖自然语言日志解释角色权限。

表结构:当前授权与追加事件分离
sql
CREATE TABLE capability (
capability_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
subject TEXT NOT NULL,
action TEXT NOT NULL,
resource_id TEXT NOT NULL,
fields_json TEXT NOT NULL,
policy_version INTEGER NOT NULL,
resource_version INTEGER NOT NULL,
approval_id TEXT,
expires_at INTEGER NOT NULL,
revoked_at INTEGER,
nonce TEXT NOT NULL UNIQUE
);
CREATE TABLE authorization_event (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
event_type TEXT NOT NULL,
decision TEXT NOT NULL,
reason TEXT NOT NULL,
policy_version INTEGER NOT NULL,
resource_version INTEGER NOT NULL,
capability_id TEXT,
created_at INTEGER NOT NULL,
UNIQUE(task_id, event_type, resource_version, reason)
);
capability 保存当前可验证票据元数据,authorization_event 追加记录允许、拒绝、审批、签发、撤销和执行结果。不要保存长期密钥,也不要把完整订单原文复制进事件表;使用脱敏摘要或受控证据引用。
nonce 用于发现重复签发,但本地唯一约束不能证明外部签发服务没有产生第二张票据。跨组件请求仍需稳定幂等键和查询接口。
策略函数和票据校验
python
from dataclasses import dataclass
import time
@dataclass(frozen=True)
class Request:
subject: str
action: str
resource_id: str
owner_team: str
subject_team: str
fields: frozenset[str]
risk: str
approved: bool
def decide(req: Request):
if req.action != "order.write_review_note":
return "deny", "ACTION_NOT_ALLOWED"
if req.owner_team != req.subject_team:
return "deny", "RESOURCE_OUT_OF_SCOPE"
if not req.fields <= {"review_note"}:
return "deny", "FIELD_OUT_OF_SCOPE"
if req.risk != "low" and not req.approved:
return "human_required", "APPROVAL_REQUIRED"
return "allow", "POLICY_MATCHED"
def verify_capability(ticket, now=None):
now = int(time.time()) if now is None else now
if ticket["revoked_at"] is not None:
return False, "REVOKED"
if now >= ticket["expires_at"]:
return False, "EXPIRED"
if ticket["action"] != "order.write_review_note":
return False, "ACTION_MISMATCH"
if set(ticket["fields"]) - {"review_note"}:
return False, "FIELD_MISMATCH"
return True, "VALID"
生产环境还需校验签名、issuer、audience、资源版本、策略版本和审批事件。资源归属必须从可信业务系统读取,不能相信调用方传来的 owner_team。票据有效也不等于请求一定符合当前业务状态。
SQLite事务与远端边界
策略决策和事件插入可以在同一本地事务中提交。票据签发或业务接口是远端副作用,不能和 SQLite 形成原子提交。不要在持有写事务时等待远端返回,否则会放大锁竞争。
可以先登记待签发意图,提交后由工作进程调用凭证服务。远端返回后,再记录 capability 和签发事件。工作进程在远端成功、本地提交前崩溃时,恢复程序必须按 nonce 查询远端结果,不能直接再签发。
两个工作进程同时处理一张订单时,使用 resource_version 条件更新和稳定动作键。旧版本审批不能用于新版本订单,避免人工修改后自动流程继续执行旧决定。
失败恢复与撤权

策略服务不可用时,写操作默认拒绝或转人工。若低风险读取允许短时缓存,需要指定可缓存的 deny/allow 类型、策略版本和最大陈旧时间。绝不能把一次缓存 allow 当作长期授权。
撤权要穿过凭证、网关缓存、任务队列和工作进程租约。可以选择短期票据减少吊销状态,也可以在网关维护在线吊销检查。前者存在有效期窗口,后者增加可用性依赖,应按动作风险组合使用。
撤权后已经执行的远端写入不会自动回滚。结果未知时先查询业务回执;需要补偿时,补偿动作拥有独立权限、动作键和核验条件,不能直接删除审计历史。
人工接管包包含 task_id、resource_version、候选动作、策略原因、审批状态、票据标识、已有回执和禁止重复事项。人工处理后追加事件,不直接覆盖旧记录。
测试矩阵与边界
单元测试覆盖未知动作、对象越界、字段越界、高风险无审批、审批通过、票据过期和已撤销。表结构测试覆盖重复 nonce、事件插入失败后的事务回滚和敏感字段不落库。
并发测试用两个独立连接尝试处理同一 resource_version,预期只有一个获得执行权。崩溃测试在远端签发后、本地记录前杀进程,恢复时应查询并补记同一 capability,而不是产生新票据。
撤权测试分别发生在任务排队、票据签发、动作网关校验之后。检查缓存刷新、排队消息、工作进程租约和业务接口回执。乱序事件不能让已撤销票据恢复有效;旧策略版本事件不能覆盖新状态。
跨版本升级保留旧策略产生的未完成任务,未知动作或字段默认进入隔离队列。备份恢复应同时检查 capability 与授权事件的一致性。SQLite 适合单机或低写入场景,是否用于生产需根据并发、故障域与运维能力选择。
示例未实现完整的公钥验证、密钥轮换、分布式租约和第三方接口幂等。验收时不要追求口头上的百分之百安全,应该检查允许能解释、拒绝能复现、撤权能生效、未知结果能接管、故障后不会静默扩大权限。