智能体面试准备(八十):智能体凭证治理与最小权限访问工程——密钥托管、临时凭证与全链路审计
引言
本文是系列第 80 篇。前面讲过工具沙箱、人在回路、多租户权限,这一篇聚焦一个更细但后果极重的问题:Agent 调用外部系统时,凭证(Credential)怎么管。
这件事一旦做错,后果是灾难性的:把数据库密码写进 prompt、把长期有效的 API Key 硬编码在镜像里、Agent 被提示词注入后拿着高权限 token 删库。而面试官考察"Agent 安全"时,凭证治理是绕不开的一环——因为它最能区分"知道要加锁"和"真的做过权限体系"。
本篇讲清四件事:凭证不能出现在模型可见的任何地方、要用短期凭证、要按任务最小授权、要能追溯每一次使用。
凭证治理总览 ================================================== 用户 / 上游系统 │ 1. 身份认证(OIDC/OAuth) ▼ ┌─────────────────────┐ │ 凭证颁发服务 STS │ │ 签发短期、受限凭证 │ └──────────┬──────────┘ │ 2. 短期凭证(分钟级、范围受限) ▼ ┌─────────────────────┐ │ Agent 运行时 │ │ ┌───────────────┐ │ │ │ 凭证代理 Proxy │ │ <- 模型永远拿不到明文 │ └───────┬───────┘ │ └──────────┼──────────┘ │ 3. 代理注入到真实调用 ▼ 外部系统(DB / SaaS / 内部 API) │ ▼ 4. 审计与回溯一、铁律:凭证绝不进模型上下文
先看反面清单,这些都是真实事故:
| 反模式 | 后果 |
|---|---|
| API Key 写进 system prompt | 模型可能原样输出(注入/追问即泄露) |
| 密钥硬编码在镜像/环境变量 | 镜像泄漏即全量泄露,轮换要重新发版 |
| 用同一个高权限账号跑所有任务 | 一次越权影响全部数据 |
| 凭证长期有效(不过期) | 泄露后窗口无限大 |
| 把数据库 DSN(含密码)传给 LLM 让它"自己连" | 完全失控 |
正确做法是凭证代理(Credential Proxy):模型只发出"我要调用 X 工具"的意图,真实凭证由旁路代理注入,模型全程接触不到明文。
# 反例:凭证进入 promptbad_prompt=f"用这个 key 调接口:api_key={SECRET}"# 严禁# 正例:Agent 只声明意图,凭证由代理注入definvoke_tool(tool_name,args,ctx):cred=sts.issue(# 向凭证服务申请短期、限范围凭证tool=tool_name,tenant=ctx.tenant,ttl_seconds=300,scope=f"{tool_name}:read",# 最小权限reason=ctx.task_id,)headers={"Authorization":f"Bearer{cred.token}"}returnhttp_call(TOOLS[tool_name].endpoint,args,headers=headers)二、短期凭证与最小权限
凭证设计三原则:短期、最小、可撤销。
| 原则 | 做法 | 参数示例 |
|---|---|---|
| 短期 | TTL 分钟级,用完即废 | ttl=300s |
| 最小权限 | 按工具+操作+资源范围授权 | crm:order:read而非crm:* |
| 可撤销 | 支持按 task_id 即时吊销 | revoke(task_id) |
| 绑定上下文 | 凭证绑定租户/会话/任务 | 换任务即失效 |
classScopedCredential:def__init__(self,token,scope,expires_at,task_id):self.token,self.scope=token,scopeself.expires_at,self.task_id=expires_at,task_iddefvalid(self,required_scope,task_id):return(time.time()<self.expires_atandtask_id==self.task_idandscope_allows(self.scope,required_scope))defscope_allows(granted,required):"""支持前缀通配的最小权限判定"""g,r=granted.split(":"),required.split(":")returnlen(g)==len(r)andall(a==bora=="*"fora,binzip(g,r))三、按任务授权与人工升级
不同风险等级的任务,凭证获取方式不同:
| 风险等级 | 示例 | 授权方式 |
|---|---|---|
| 只读低风险 | 查询天气、检索文档 | 自动颁发,无需审批 |
| 有副作用 | 发消息、写工单 | 自动颁发 + 事后通知 |
| 高风险 | 转账、删数据、发外部邮件 | 人工审批后颁发,或人在回路确认 |
| 越权请求 | 超出该 Agent 权限范围 | 直接拒绝并告警 |
实现上给每个工具标注风险等级(注册在工具注册中心,见上篇 B79),凭证服务根据等级决定"自动发 / 需审批 / 拒绝"。风险分级 + 自动颁发 + 高风险人工升级是标准三段式。
RISK={"read_only":"auto","side_effect":"auto+notify","high_risk":"require_approval"}defissue_for(tool,ctx):level=TOOLS[tool].riskpolicy=RISK[level]ifpolicy=="require_approval":ifnotapprovals.granted(ctx.task_id,tool):raisePermissionError(f"{tool}需人工审批")returnsts.issue(tool=tool,tenant=ctx.tenant,ttl_seconds=300iflevel!="high_risk"else600,scope=TOOLS[tool].required_scope,reason=ctx.task_id)四、防注入:凭证在攻防中的位置
提示词注入的真实危害不是"模型说错话",而是攻击者借模型之手调用高权限工具。凭证治理正是对抗链的关键一环:
防线叠加(缺一不可) 1. 凭证不进上下文 -> 注入者无法直接套出密钥 2. 最小权限 scope -> 即使被诱导调用,影响范围受限 3. 高风险需审批 -> 关键动作有人工闸门 4. 短期 TTL -> 泄露窗口极小 5. 全链路审计 -> 事后可追溯、可告警注意:不要把"模型判断安全"当作唯一闸门。模型的判断可被诱导,真正的闸门必须是策略引擎(代码级、确定性),模型只能提供建议。
五、审计与轮换
| 能力 | 说明 |
|---|---|
| 审计日志 | 每次凭证颁发与使用记录:谁、何时、工具、参数摘要、结果 |
| 异常检测 | 频率异常、非常规时段、跨租户访问、权限提升尝试 |
| 密钥轮换 | 定期自动轮换;泄露时一键吊销 |
| 最小可见 | 日志中脱敏,绝不记录明文凭证 |
defaudit_log(tool,args,ctx,result):log.info({"ts":time.time(),"tenant":ctx.tenant,"task_id":ctx.task_id,"tool":tool,"args_digest":hashlib.sha256(json.dumps(args,sort_keys=True).encode()).hexdigest()[:16],"status":"ok"ifresult.okelse"fail",# 注意:不记录明文参数与凭证})六、落地检查清单
- 凭证是否 100% 不进入模型上下文(prompt / 工具返回值 / 日志)?
- 是否全部使用短期凭证(TTL ≤ 分钟级)?
- 是否按"工具+操作+资源"三维最小授权?
- 高风险工具是否有人工审批闸门,且闸门在策略层而非模型层?
- 是否能按 task_id 即时吊销?
- 是否所有调用都有审计,且日志脱敏?
- 密钥是否支持自动轮换与一键吊销?
面试速答
问:Agent 调用外部 API 的密钥怎么管?
答:凭证绝不进模型上下文。用凭证代理:模型只声明调用意图,真实凭证由旁路服务按"短期 TTL + 最小 scope + 绑定 task_id"签发并注入,模型全程接触不到明文。
问:短期凭证的具体参数怎么设?
答:TTL 分钟级(如 300s),scope 按工具+操作+资源三维最小授权(如 crm:order:read 而非 crm:*),绑定 task_id 与租户,支持按 task_id 即时吊销。
问:提示词注入怎么防?
答:五层叠加——凭证不进上下文、最小权限 scope、高风险人工审批、短期 TTL、全链路审计。关键是安全闸门必须在策略层(代码确定性),不能依赖模型判断。
问:高风险操作怎么控制?
答:工具在注册中心标注风险等级,凭证服务按等级决定自动颁发/需审批/拒绝。高风险(转账、删数据、外发邮件)走人工审批或人在回路确认,审批通过才发凭证。
高频追问清单
- 凭证代理挂了或被绕过,怎么保证 Agent 拿不到明文?
- 多租户场景下凭证怎么隔离?跨租户访问怎么检测?
- 短期凭证 TTL 设太短导致频繁申请,性能和可用性怎么权衡?
- 工具返回值里意外带了密钥(如接口返回 token),怎么清洗?
- Agent 需要代表用户操作(on-behalf-of),OAuth 委托授权怎么设计?
- 高风险审批流超时未响应,任务应该阻塞、降级还是中止?
- 凭证审计日志本身怎么防篡改?
- 密钥自动轮换时,在途请求怎么处理才不中断?
- 如何检测"凭证被异常使用"(频率、地理、时段异常)?
- 本地/私有化部署没有云厂商 STS,怎么自建短期凭证服务?