当 Agent 不再只是回答问题,而是代表用户读取数据、调用 API、修改资源时,传统的“登录一次、授权一段时间”就不够用了。The Agent Access Model 提出了一种面向任务范围的安全架构:由严格的身份代理负责确认“谁在执行”,由持续中介负责确认“此刻是否允许”,再通过有状态的信任记录判断“这个任务是否仍然值得信任”。
这套思路的重点不是给 Agent 一个更强的长期 Token,而是把访问权限绑定到具体任务、具体资源和当前上下文。
三个关键边界
1. 身份代理:Agent 不直接继承用户身份
Agent 通常需要代表用户完成工作,但不应直接持有用户的密码、长期访问令牌或完整会话。更稳妥的做法是引入身份代理层:
- 用户登录和多因素认证由身份系统完成;
- Agent 通过任务请求申请一个短期、范围明确的执行身份;
- 身份代理验证用户、Agent、任务和目标资源之间的关系;
- 下游服务只接受代理签发的、面向任务的凭证。
例如,一个“整理本周销售报告”的任务可以只获得读取销售数据库和写入指定报告目录的权限,而不能访问全部客户数据,也不能删除历史报告。
2. 持续中介:每次操作都重新判断
一次授权不应该自动等同于整个任务生命周期内的无限授权。持续中介层可以在 Agent 每次调用工具、API 或数据服务时检查:
- 当前任务是否仍然有效;
- 请求的资源是否属于任务范围;
- 操作类型是否被允许;
- 用户会话、设备和风险状态是否发生变化;
- Agent 是否超出调用次数、时间或数据量限制。
这意味着权限检查应该放在工具调用路径上,而不是只放在 Agent 初始化阶段。即使 Agent 的推理过程被提示注入影响,后端仍然可以拒绝越界操作。
3. 有状态信任:信任会随任务变化
Agent 的信任不是一个永久布尔值。它应该是一条随着事件变化的状态记录。成功完成低风险读取操作,可能提高任务的可信度;出现异常参数、访问不相关资源或连续失败,则应降低信任度,触发重新认证、人工审批或任务终止。
可以把任务状态设计成 active、step_up_required、suspended 和 revoked 等明确状态。状态变化必须可审计,并且由服务端保存,不能只依赖 Agent 自己传回的字段。
一个可落地的任务凭证模型
下面的 YAML 是一个简化的实践示例,展示如何把身份、任务范围和限制放在同一个可验证对象中。字段和签名格式需要根据实际身份平台调整,这里不代表某个特定协议的标准格式。
agent_id: report-agent
subject: user-123
job_id: job-2025-03-08-001
issued_at: 2025-03-08T09:00:00Z
expires_at: 2025-03-08T09:15:00Z
allowed_actions:
- read:sales.weekly
- write:reports/job-2025-03-08-001.md
limits:
max_tool_calls: 20
max_rows: 5000
require_approval_for:
- delete:*
trust_state: active
issuer: access-broker.internal
signature: replace-with-jws-or-mtls-proof
实践时,下游服务不能只相信客户端提交的 YAML 或 JWT 声明。它应当验证签名、发行者、有效期、任务状态和资源范围,并从服务端查询最新的撤销状态。
把持续中介放进工具调用路径
下面是一个可以运行和改造的 Python 最小示例。它用内存对象模拟身份代理和访问中介,重点演示每次工具调用都检查任务状态、动作范围和调用次数。生产环境应将状态放到数据库或专用授权服务中,并使用真实的签名验证和审计日志。
from dataclasses import dataclass, field
from datetime import datetime, timedelta, timezone
@dataclass
class TaskGrant:
job_id: str
subject: str
expires_at: datetime
allowed_actions: set[str]
max_tool_calls: int
tool_calls: int = 0
state: str = 'active'
events: list[str] = field(default_factory=list)
def record(self, message: str) -> None:
self.events.append(f'{datetime.now(timezone.utc).isoformat()} {message}')
class AccessBroker:
def issue(self, subject: str) -> TaskGrant:
grant = TaskGrant(
job_id='job-001',
subject=subject,
expires_at=datetime.now(timezone.utc) + timedelta(minutes=15),
allowed_actions={'read:sales.weekly', 'write:reports/job-001.md'},
max_tool_calls=3,
)
grant.record('grant_issued')
return grant
class ContinuousMediator:
def authorize(self, grant: TaskGrant, action: str) -> None:
now = datetime.now(timezone.utc)
if grant.state != 'active':
raise PermissionError(f'task is {grant.state}')
if now >= grant.expires_at:
grant.state = 'revoked'
grant.record('grant_expired')
raise PermissionError('grant expired')
if grant.tool_calls >= grant.max_tool_calls:
grant.state = 'suspended'
grant.record('call_limit_reached')
raise PermissionError('tool call limit reached')
if action not in grant.allowed_actions:
grant.record(f'denied action={action}')
raise PermissionError(f'action not allowed: {action}')
grant.tool_calls += 1
grant.record(f'allowed action={action}')
def call_tool(grant: TaskGrant, action: str) -> str:
mediator = ContinuousMediator()
mediator.authorize(grant, action)
return f'executed {action} for {grant.job_id}'
if __name__ == '__main__':
grant = AccessBroker().issue('user-123')
print(call_tool(grant, 'read:sales.weekly'))
print(call_tool(grant, 'write:reports/job-001.md'))
try:
call_tool(grant, 'delete:reports/job-001.md')
except PermissionError as exc:
print(f'denied: {exc}')
print('\\n'.join(grant.events))
运行方式:
python agent_access_demo.py
这个示例还有几个需要补齐的生产能力:使用不可伪造的任务凭证、在每次请求中绑定受众和资源、把撤销状态集中存储、记录完整的输入输出摘要,并对高风险写操作加入人工审批或二次认证。
采用时的检查清单
- 每个 Agent 任务是否都有唯一的
job_id和明确过期时间? - Agent 是否只拿到任务所需的短期凭证,而不是用户长期 Token?
- 工具和下游 API 是否在每次调用时执行授权检查?
- 资源范围是否能精确到项目、表、文件或具体记录?
- 任务状态是否由服务端维护,并支持暂停、撤销和人工接管?
- 是否能从审计记录还原“谁发起了任务、Agent 做了什么、为何被允许”?
- 当风险上升时,系统是否会收紧权限,而不是继续沿用原授权?
任务型 Agent 的安全边界最终应落在服务端,而不是提示词、Agent 框架或模型的自我约束上。身份代理解决身份来源,持续中介解决即时授权,有状态信任解决任务过程中的变化。三者结合,才能让 Agent 在足够有用的同时保持可暂停、可撤销、可审计。