OWASP 2026 LLM Top 10 新增 Agent Control Standard:从提示词防护转向运行时权限审计
凌晨两点十七分值班电话把你从床上叫起来。一个本来只负责查日志的代理刚刚调用了数据清理工具把一批带着测试标签的客户记录全部删掉。团队翻系统提示词时没有发现明显问题里面写着未经确认不得执行删除操作人工也审过那段规则。真正出错的地方在工具层代理从工单备注里读到一段像指令的文字换了一个目标拿着合法身份走完了认证流程。天亮后再看审计记录事情比误删本身更难受。代理在批量操作前查过表结构也试过单条更新3次调用都带着合法令牌没有一条被拦截。权限系统只回答这个用户能不能删数据没有回答这个代理为什么在此时调用这个工具、参数是否越界、是否真的获得了审批。模型输出被审得很严执行动作却没有同等的检查。这正是今天讨论 Agent Control Standard 的原因。本文先把 OWASP 2026 LLM Top 10 与 ACS 的关系讲清楚再把运行时控制拆成工具权限、参数约束、审计记录和回滚策略。文中的 Python 程序使用标准库可以直接运行它不是完整的生产网关却足以把“先判断再执行”的边界搭出来。为什么 Agent Control Standard 会在 2026 年出现代理开始替人操作之后安全问题的重心就变了。聊天机器人答错一句话通常是内容质量问题能读数据库、改工单、发消息的代理答错一次可能直接改变业务状态。系统需要知道的不只是模型说了什么还包括它接触了什么、调用了哪个工具、使用了什么参数以及谁能在动作发生前按下暂停键。OWASP GenAI Security Project 的官方页面把 ACS 标为 2026 年 9 月 1 日的资源。项目在 2026 年 9 月 2 日的公告中说明Agent Control Standard 已捐赠给该项目用来补上现有风险清单与实际运行控制之间的缺口。这个时间点很关键说明它不是某个框架的私有插件而是被放进了开放安全项目的讨论范围。ACS 页面给出的核心词很克制代理应该可检查、可追踪、可插桩。企业需要看见代理是什么、能访问什么、做过什么以及为什么这样做还需要在运行时控制它的行为。标准的工程价值不在于再写一份“模型要听话”的提示词而在于定义平台怎样暴露中间件钩子策略怎样通过这些钩子执行。这也解释了它与普通日志方案的差别。日志是在动作完成后留下记录ACS关心的是动作发生前能不能进入一个决策点。决策点可以读取身份、会话、工具、参数和环境状态返回允许、拒绝或要求改变后再试。模型可以继续负责规划真正的权限边界留在模型之外。过去常见的做法是把限制写进系统提示词例如“不要删除数据”“不能访问其他租户”。这些话对正常对话有帮助却不是可靠的授权机制。外部网页、邮件、工单和检索结果都可能进入上下文模型也可能误判用户意图。只要执行端拿到过大的权限提示词被绕过时就没有第二道门。ACS的出现还有一个现实背景代理框架和工具协议正在变多。MCP让应用以统一方式连接外部工具和数据源开发者因此少写了许多适配代码但连接越方便权限分散的问题越容易被忽略。代理可能同时连着代码仓库、工单系统、网盘和数据库任何一个上下文污染都可能把影响面推向其他系统。所以ACS不是要替代MCP也不是重新发明身份认证。它更像一层放在代理平台与工具执行器之间的控制面。MCP继续负责工具描述、调用和结果返回ACS负责回答这次调用能不能发生以及这次调用需要留下什么证据。两者分工清楚工程团队才不必把业务权限塞进模型提示词里。把标准落成代码时可以先记住一个判断身份认证解决“你是谁”运行时授权解决“你现在能做什么”。同一个用户在人工页面上可以删除记录不代表他授权的代理在夜间批量删除时也应获得同样能力。代理身份、用户意图、会话状态和工具风险应该一起进入判决。OWASP 2026 LLM Top 10 的变化过度代理权限升到前列OWASP GenAI Security Project 的资源页将 OWASP GenAI LLM Top 10 2026 标为 2026 年 8 月 3 日发布。官方公告称新版由数百名 AI 安全专家参与吸收了数千起真实 AI 安全事件并增加了与 NIST、MITRE ATLAS、CWE 及代理应用风险清单的映射。它不只是把旧条目换个名字而是把风险排序和真实运行经验放到了一张表里。官方公告还给出两个很直观的信号新版发布后的前48小时下载量超过10,000项目成员数超过30,000。下载量本身不是安全结论却说明企业正在寻找可以交给开发、架构和安全团队共同使用的语言。风险清单要真正有用必须能落到权限、配置、测试和应急动作上。过度代理权限在新版中排到第3位。公告引用的项目支持者解释说这是社区判断与真实事件记录都指向同一风险的地方。代理拿到真实工具和真实权限之后失败不再停留在回答质量而会表现为数据被改、消息被发、凭据被使用或流程被推进。过度代理权限并不等于“所有代理都不该有权限”。它指的是代理拥有的能力、权限范围或自主程度超过任务需要。查订单状态的代理不需要删除订单整理日志的代理不需要执行任意脚本只处理本租户工单的代理不应带着跨租户令牌。能力多一点故障半径就可能大一圈。这个风险很容易被“用户本来就有权限”掩盖。用户授权代理查资料并不自动授权代理向外部系统发信用户允许修改一张表的状态也不代表可以修改任意字段。代理执行时多了一层推理和自动化授权就应该比人工按钮更窄而不是把人工账号原样复制给它。攻击者也不一定需要直接攻破代理服务器。恶意文本可以藏在网页、邮件、工单描述或检索结果中模型把它当成下一步任务后真正决定损害大小的是工具权限。模型被诱导只是起点能否访问生产数据、能否绕过审批、能否把结果传给外部服务才决定事故会不会扩大。可以用一张简单的对照表来理解这类边界从安全层面只看模型输出同时看运行时动作授权对象用户账号用户、代理、会话和工具组合检查时机生成之后执行工具之前失败处理记录错误拒绝、暂存审批或缩小参数审计内容文本和请求时间身份、工具、参数、策略版本和判决因此Top 10 的变化不应被读成“模型又多了一个漏洞”。它更像是在提醒工程团队代理应用已经接近普通分布式系统的权限问题只是动作的发起者换成了模型。安全设计要从“模型是否听话”移动到“即使模型被误导系统还能限制什么”。从提示词到运行时ACS 怎样管住工具调用提示词仍然有用但它适合表达意图不适合承担不可绕过的权限。你可以告诉代理“删除前要确认”却不能只凭这句话决定数据库连接是否允许删除。不可逆操作必须由执行端检查检查结果不能由同一个可能被诱导的模型自己宣布有效。把一次调用拆开看运行时控制至少有4个输入代理身份、用户身份、当前会话和工具请求。工具请求还要继续拆成工具名、参数、目标资源和操作类型。少一个维度策略就容易变成“这个代理能不能用数据库”这种过于粗的判断。ACS页面使用可检查、可追踪、可插桩这3个词。落地时可以把它们翻译成3件具体工作。可插桩意味着框架在工具执行前提供钩子可追踪意味着每次判决都生成结构化事件可检查意味着平台能回答当前代理拥有哪些工具和权限。它们互相配合才不会出现只留日志却无法阻止动作的情况。插桩的重点是位置而不是代码量。钩子要放在代理准备提交工具调用、写入长期记忆、执行代码或启动子代理的位置。放在调用之后只能做监控放在调用之前才能做授权。对同一个工具人工调用、计划任务和代理调用也应走同一类检查避免存在一条没有控制的旁路。追踪记录不能只写“调用成功”。至少要包含用户和代理身份、会话标识、工具名称、经过脱敏的参数摘要、策略版本、判决、原因、时间和执行结果。若参数里有密码、身份证号或整段业务数据应保存哈希、字段名或长度等必要信息不要把敏感结果复制进另一套日志系统。可检查还包括权限清单。平台应能在某个时间点回答一个代理能调用哪些工具、每个工具允许哪些参数、哪些操作需要审批、当前策略版本是什么。这个清单最好可以导出给安全审查使用否则权限变更只存在于运行中的服务内事故调查时很难还原当时的边界。判决不必只有允许和拒绝。低风险查询可以直接允许明显危险的工具可以拒绝带有审批要求但尚未获批的更新可以暂存。也可以把参数从“全量更新”缩小到“指定记录的指定字段”但修改参数必须产生新的审计事件不能让调用方以为原请求原样执行了。放到MCP链路中执行顺序应该是代理产生工具请求控制钩子读取请求并送到策略服务策略服务返回判决只有允许或经过明确修改的请求才进入MCP客户端。工具结果回来后再由追踪层记录结果状态。任何直接绕过钩子调用MCP服务器的路径都应被视为权限设计缺口。运行时控制也不能假装模型永远正确。策略服务不可用时查询类动作可以按业务风险决定是否暂时拒绝高风险写操作应采用故障关闭策略版本读不到时不要悄悄回退到一份未知的宽权限配置。恢复之后要把失败和拒绝事件补齐避免监控只看到成功调用。给 MCP 服务做一套最小权限与审计闭环最小权限的起点是把“工具”当成权限单元而不是把整个服务器当成一个权限单元。列目录、查记录、改状态、发通知和删除数据应是不同工具。工具越大参数越自由策略越难审查工具越小授权和回滚越容易。默认只读是很实用的第一步。读取订单状态、查询日志和获取知识库内容通常不会直接改变业务状态可以先在只读范围内验证代理是否真的带来收益。写入、发送、删除、授权和执行脚本要单独列出不能因为它们与查询放在同一个MCP服务器里就自动共享权限。权限还要细到参数。允许调用“更新记录”并不代表允许更新任意表、任意字段和任意数量的记录。策略可以限制目标表、字段白名单、单次条数、条件表达式和时间窗口。一个调用若带来策略未声明的额外参数应直接拒绝而不是忽略未知字段后继续执行。租户边界不能靠代理自觉维护。每次MCP请求都应该从可信身份上下文中得到租户标识再由服务端把租户条件加入查询或更新逻辑。代理提交的租户字段只能作为待校验输入不能直接决定最终查询范围。否则一次提示词注入就可能把“我的数据”改成“所有数据”。高风险操作需要一个独立的审批状态。审批单号、审批人、作用范围和有效期都应由服务端检查不能只要求模型在参数里写一个approval字段。审批通过后仍要重新校验工具、资源和参数防止审批的是单条更新执行时却被拼成批量删除。审计闭环的核心不是日志越多越好而是记录能否帮助下一次判决。每次拒绝都要有稳定的原因码例如工具未注册、参数越界、租户不匹配、审批缺失或策略版本过期。原因码可以用于告警和统计给人的解释则可以更短不必把内部策略细节全部返回给代理。调用结果也要进入闭环。成功、失败、超时、被取消和被策略拒绝应当能够区分。只记录成功会让团队误以为系统运行平稳实际上失败的工具调用可能正在反复触发模型重试。重试次数、单会话调用量和高风险操作密度都适合设置上限。一套能用的闭环可以画成这样的顺序身份确认工具登记参数检查风险判决执行记录结果回写策略复盘。任何一步没有数据后面的判断都会变成猜测。上线初期不必一次覆盖全部工具但要让进入生产的那几个工具完整走完这条链。一段可运行的 Python 工具调用拦截器下面这段程序用 Python 标准库提供一个很小的Guardian服务。它登记了查询和更新两类工具拒绝高危工具校验表名、记录编号、数量和审批字段并把判决记录在内存列表里。启动参数带上--demo时会执行允许、拒绝和待审批3个样例不带参数时启动HTTP接口便于代理或MCP适配层调用。import json import re import sys from datetime import datetime, timezone from http.server import BaseHTTPRequestHandler, HTTPServer DENIED_TOOLS {delete_all_records, drop_table, grant_admin} AUDIT_LOG [] POLICIES { query_records: { allowed_params: {table, limit}, tables: {orders, customers, logs}, max_limit: 100, approval: False, }, update_record: { allowed_params: {table, record_id, updates, approval_id}, tables: {orders}, max_limit: 1, approval: True, }, } def write_audit(request, decision, reason): record { agent_id: request.get(agent_id, ), session_id: request.get(session_id, ), tool_name: request.get(tool_name, ), decision: decision, reason: reason, time: datetime.now(timezone.utc).isoformat(), } AUDIT_LOG.append(record) return record def decide(request): tool_name request.get(tool_name, ) params request.get(tool_params, {}) if tool_name in DENIED_TOOLS: return write_audit(request, deny, tool_is_denied) policy POLICIES.get(tool_name) if not policy: return write_audit(request, deny, tool_is_not_registered) if not isinstance(params, dict): return write_audit(request, deny, params_must_be_object) unknown set(params) - policy[allowed_params] if unknown: return write_audit(request, deny, unknown_parameter) if params.get(table) not in policy[tables]: return write_audit(request, deny, table_is_not_allowed) limit params.get(limit, 1) if not isinstance(limit, int) or limit 1 or limit policy[max_limit]: return write_audit(request, deny, limit_is_out_of_range) if tool_name update_record: record_id params.get(record_id, ) if not re.fullmatch(rord_[A-Za-z0-9], record_id): return write_audit(request, deny, record_id_is_invalid) if not params.get(approval_id): return write_audit(request, modify, approval_is_required) return write_audit(request, allow, policy_check_passed) class GuardianHandler(BaseHTTPRequestHandler): def do_POST(self): if self.path ! /guardian/decide: self.send_error(404) return length int(self.headers.get(Content-Length, 0)) try: payload json.loads(self.rfile.read(length)) result decide(payload) body json.dumps(result, ensure_asciiFalse).encode(utf-8) self.send_response(200) self.send_header(Content-Type, application/json; charsetutf-8) self.send_header(Content-Length, str(len(body))) self.end_headers() self.wfile.write(body) except (ValueError, TypeError, json.JSONDecodeError): self.send_error(400, invalid_json) def log_message(self, fmt, *args): return def run_demo(): samples [ {agent_id: reader, session_id: s1, tool_name: query_records, tool_params: {table: orders, limit: 10}}, {agent_id: reader, session_id: s2, tool_name: drop_table, tool_params: {table: orders, limit: 1}}, {agent_id: writer, session_id: s3, tool_name: update_record, tool_params: {table: orders, record_id: ord_7, updates: {status: paid}}}, ] for sample in samples: print(json.dumps(decide(sample), ensure_asciiFalse)) if __name__ __main__: if len(sys.argv) 1 and sys.argv[1] --demo: run_demo() else: print(Guardian listening on port 8080) HTTPServer((, 8080), GuardianHandler).serve_forever()这段代码的关键不在于HTTP服务有多复杂而在于判决位置固定在工具执行之前。query_records只能访问登记过的表查询数量有上限update_record即使参数格式正确没有审批号也只返回modify删除整表这类工具则直接拒绝。调用方拿到allow之后才应该继续进入真正的MCP执行器。运行--demo可以看到3种结果查询请求被允许危险工具被拒绝更新请求进入待审批。生产系统不能把审批状态只放在内存里也不能把完整参数原样写入日志。它还需要把审计存储、策略版本、租户校验和真正的审批系统接起来但这些扩展不会改变拦截器的基本顺序。企业落地时如何把代理权限拆成可回滚的策略代码能拦住一次调用策略版本才能控制一整套系统。不要把所有权限硬编码在代理提示词里也不要让每次策略变化都依赖重新发布代理。工具清单、参数范围、审批要求和拒绝规则应当独立配置并且每个版本都有唯一标识。权限拆分可以从代理角色开始。日志分析代理只绑定日志查询客服代理只绑定订单查询和低风险工单更新发布代理才接触部署工具。每个代理使用独立身份令牌只覆盖它的工具集合和租户范围。共享服务账号越方便事故发生后的追责和隔离越困难。策略配置要能做影子评估。新版本先记录“如果启用会怎样”暂时不改变真实判决再与旧版本的允许和拒绝结果对照。重点看两类差异正常请求是否突然被大量拒绝高风险请求是否仍然被放行。没有这一步策略上线就像把生产流量直接交给未经检查的代码。回滚也要有明确触发条件。高风险放行次数突然增加、拒绝率在正常时段异常上升、某个代理调用工具的范围扩张、审计服务连续缺失事件都可以触发回滚或暂停写操作。回滚动作应由策略版本完成而不是临时登录服务器修改一行配置临时修改很难留下可复核证据。权限最好按能力拆成独立开关例如读订单、改状态、发通知和删除数据分别管理。这样某个更新规则需要回撤时只关闭更新能力不必让整个代理失去查询功能。原子权限也方便做灰度将一项能力先交给少量代理和少量租户验证。策略审计要和业务指标放在一起看。单看拦截数量没有意义拦截后是否出现重试风暴、人工工单是否增加、被拒绝的请求是否集中在一个新版本都能帮助判断策略过紧还是工具描述有问题。安全控制不是越严越好而是要在风险可接受的范围内让边界稳定。事故复盘时团队应能从一个判决记录还原完整链路谁发起、哪个代理代办、哪个会话触发、调用了什么工具、用了哪一版策略、返回了什么判决、执行器是否真的运行。若只能看到模型的一段文字就无法区分模型判断错误、权限配置过宽还是工具实现绕过了控制。代理能不能上线不取决于它会多少工具而取决于每一次动作能否被解释、被限制、被撤回。提示词可以表达意图MCP可以连接工具ACS提供把意图和动作隔开的运行时控制。真正值得投入的工作是让这道边界在模型被误导、策略变更和服务故障时仍然存在。