资讯详情

资讯详情

深入解析 agent-core 的 ToolRejectExample 安全 Rail:BEFORE 与 AFTER 工具拒绝行为的分治与实现原理

人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习【免费下载链接】agent-coreopenJiuwen agent-core可提供AI Agent开发、运行、调优与演进相关的全套SDK能力项目地址https://gitcode.com/openJiuwen/agent-core点击查看免费下载导读本文围绕 openJiuwen agent-core 仓库中examples/security_rail_demo/ToolRejectExample这个安全 Rail 示例展开系统讲解如何基于BaseSecurityRail框架实现零人工干预的密钥泄露自动拦截在BEFORE_TOOL_CALL阶段检测到敏感信息时跳过工具执行、让 Agent 继续尝试其他方案在AFTER_TOOL_CALL阶段检测到敏感信息时强制终止 Agent、立即返回错误。读完本文你将掌握安全 Rail 的两种拒绝语义skip_tool与force_finish及其底层实现链路并能基于该示例快速编写、安装、测试自己的自动拒绝型安全守卫。一、示例定位自动拒绝型安全 RailToolRejectExample是 agent-core 仓库examples/security_rail_demo目录下五个安全 Rail 示例之一另有ApiKeyGuardAlert、ApiKeyGuardInterrupt、ModelCallGuard、SensitiveDataSanitize。它与ApiKeyGuardInterrupt的核心差异在于ApiKeyGuardInterrupt检测到密钥后发起 HITLHuman-in-the-Loop人工审批中断由用户在界面上选择批准/拒绝ToolRejectExample检测到密钥后直接拒绝REJECT不经过任何人工确认实现无人值守的自动阻断。从源码注释看该 Rail 的适用场景非常明确见 rail.py理解拒绝行为reject behavior在不同事件上的差异在无需 HITL 的条件下测试工具安全在不需要用户审批的场景做简单、直接的安全强制。类定义ToolrejectexampleRail继承自BaseSecurityRail声明了priority 88安全 Rail 的推荐优先级区间是 85–95并只在两个事件上生效supported_events { AgentCallbackEvent.BEFORE_TOOL_CALL, AgentCallbackEvent.AFTER_TOOL_CALL, }这两个事件在框架中的定义位于 AgentCallbackEvent 枚举BEFORE_TOOL_CALL在工具执行前触发AFTER_TOOL_CALL在工具执行完成后触发。事件到回调方法的映射由EVENT_METHOD_MAP维护BEFORE_TOOL_CALL → before_tool_call、AFTER_TOOL_CALL → after_tool_call见 EVENT_METHOD_MAPBaseSecurityRail会依据supported_events自动注册对应回调get_callbacks()只注册声明过的事件见 base_security_rail.py。二、核心概念BEFORE 与 AFTER 的拒绝行为对比示例文档用一张表精确刻画了两个事件上拒绝行为的本质区别这也是本 Rail 全篇的灵魂事件检测位置拒绝动作Agent 行为BEFORE_TOOL_CALL敏感信息在tool_args工具参数中skip_tool跳过工具Agent继续运行尝试其他方案AFTER_TOOL_CALL敏感信息在tool_result工具结果中force_finish强制结束Agent终止运行向用户返回错误为什么要有这种区别关键在于敏感信息是否已经产生/泄露BEFORE 拒绝秘密还停留在用户输入或工具参数里工具尚未真正执行不存在泄露风险。此时让 Agent 换一种思路继续既安全又不打断任务是性价比最高的处置方式。AFTER 拒绝秘密已经进入工具结果意味着数据已经泄露例如.env文件内容被读取了出来。继续运行只会扩大暴露面必须立即终止并把错误返回给用户。示例文档给出了两条非常直观的对话流BEFORE 拒绝Agent 继续User: Find file containing sk-proj_secret123 LLM: ToolCall(glob, args*sk-proj_secret123*) Rail: Reject Secret in arguments → skip_tool → ToolMessage Tool execution skipped Agent: Continues... LLM: I couldnt search for that pattern. Let me try a different approach. → Asks user for safe filenameAFTER 拒绝Agent 终止User: Read file .env Tool: Returns API_KEYsk-abc123... Rail: Reject Secret in result → force_finish → Returns error to user Agent: Terminates, shows Blocked by security rail这种参数阶段可重试、结果阶段必终止的设计在示例的 README.md 中有完整说明是编写任何泄露防护型 Rail 时都应当遵循的原则。三、两条完整流程图解示例文档为两种场景绘制了端到端的 ASCII 流程图我们将其完整继承并补充框架内部的关键环节3.1 BEFORE Reject Flow┌─────────────────────────────────────────────────────────────────┐ │ User Request │ │ glob *sk-secret* │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ LLM Generates ToolCall │ │ tool_args *sk-secret* │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ BEFORE_TOOL_CALL Rail │ │ ✓ Secret detected in args │ │ → Reject │ │ → _skip_tool (base behavior) │ │ → ToolMessage Tool execution skipped │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ Agent Continues │ │ LLM sees ToolMessage │ │ → That didnt work, let me ask user for safe filename │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ Conversation Continues │ │ User can provide alternative │ └─────────────────────────────────────────────────────────────────┘3.2 AFTER Reject Flow┌─────────────────────────────────────────────────────────────────┐ │ User Request │ │ Read .env file │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ Tool Executes (BEFORE passed) │ │ read_file(.env) │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ Tool Result │ │ API_KEYsk-abc123... │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ AFTER_TOOL_CALL Rail │ │ ✓ Secret detected in result │ │ → Reject │ │ → force_finish (base behavior) │ │ → Returns error to user │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ Agent Terminates │ │ User sees: Blocked by security rail │ │ Conversation ends │ └─────────────────────────────────────────────────────────────────┘注意两条流程中一个容易被忽略的细节AFTER 场景里工具是真正执行过了的BEFORE 阶段没有命中于是放行执行只是结果返回后被 Rail 拦截。这也解释了为什么 AFTER 必须force_finish—— 泄露已经发生只能止损。四、配置项详解工具白名单与敏感模式示例在 rail.py 中定义了两组配置均以模块级常量形式存在可自由按业务调整。4.1 工具白名单 TOOL_WHITELISTTOOL_WHITELIST [ read_file, # Can read files containing secrets bash, # Can execute commands revealing secrets grep, # Can search for secrets glob, # Can find files by secret patterns write_file, # Can write secrets to files ]白名单的意义在于收敛监控面Rail 只对列出的工具做敏感信息检测其余工具直接放行。从实现上看_check_before与_check_after的第一步都是if tool_name not in TOOL_WHITELIST: return self.allow()见 rail.py。这 5 个工具覆盖了典型的敏感信息通道read_file/grep/glob—— 读取、检索、定位包含密钥的文件bash—— 执行可能回显密钥的命令write_file—— 把密钥写入文件是 AFTER 阶段特别需要盯防的出口。4.2 敏感模式 SENSITIVE_PATTERNSSENSITIVE_PATTERNS [ r(?:api_key|API_KEY|apikey|APIKEY|secret|SECRET|token|TOKEN|credential|CREDENTIAL)\s*[:]\s*[\]?\S[\]?, rsk-[a-zA-Z0-9_-]{20,}, # OpenAI-style keys (supports underscore/hyphen) rBearer\s[a-zA-Z0-9\-_], rAKIA[0-9A-Z]{16}, # AWS keys ]四条正则分别覆盖四类常见泄露模式模式匹配目标关键特征第 1 条键值对形式配置api_key/secret/token/credential等键名大小写不敏感后跟或:及非空白值第 2 条OpenAI 风格密钥sk-前缀 至少 20 位字母/数字/下划线/连字符支持_与-第 3 条Bearer 认证令牌Bearer后跟令牌字符串第 4 条AWS Access KeyAKIA前缀 16 位大写字母/数字在ToolrejectexampleRail.__init__中这些原始字符串被编译为re.compile(p, re.IGNORECASE)的预编译对象见 rail.py后续_contains_secret()逐个pattern.search(content)命中即返回True。所有模式均使用re.IGNORECASE因此对API_KEY、api_key、Api_Key等大小写变体一视同仁。五、源码级原理拒绝决策是如何落地的示例文档在结尾给出了一段Code Reference指向BaseSecurityRail._apply_reject()。让我们沿着真实源码把这条链路走通这有助于理解 Rail 在什么条件下生效、为什么表现如文档所述。5.1 决策类型体系BaseSecurityRail定义了四类安全决策见 base_security_rail.pySecurityAllow—— 放行可携带new_args改写工具参数SecurityReject—— 拒绝携带message/result/tool_messageSecurityInterrupt—— 中断并等待用户输入本示例不使用SecurityAlert—— 放行但向用户发出告警ApiKeyGuardAlert示例使用。子类只需实现run_security_check()返回一个决策基类的_run_and_apply()负责构建SecurityCheckContext并调用apply_security_decision()落地见 base_security_rail.py。5.2_apply_reject()的事件分派文档引用的核心逻辑位于 _apply_reject其分派规则与示例文档完全一致if event AgentCallbackEvent.BEFORE_TOOL_CALL: self._skip_tool( ctx, tool_call, tool_resulterror_msg, tool_messageToolMessage(contenterror_msg, tool_call_idtool_call_id), ) return if event AgentCallbackEvent.AFTER_TOOL_CALL: inputs.tool_result error_msg inputs.tool_msg ToolMessage(contenterror_msg, tool_call_idtool_call_id) result self._build_force_finish_result(decision) ctx.request_force_finish(result) return其中error_msg的取值也体现了两种场景的语义差异BEFORE 场景缺省为Tool execution skipped其余场景缺省为Blocked by security rail见 base_security_rail.py。而_build_force_finish_result()会把拒绝信息包装成标准错误结果{output: ..., result_type: error}见 base_security_rail.py。5.3_skip_toolBEFORE 拒绝的落地机制_skip_tool的实际作用是标记工具跳过并在上下文中注入伪造的工具结果def _skip_tool(self, ctx, tool_call, tool_result, tool_messageNone) - None: ctx.extra[_skip_tool] True ctx.inputs.tool_result tool_result ctx.inputs.tool_msg msg见 base_security_rail.py。随后真正执行工具的 AbilityManager 在 _railed_execute_single_tool_call 中会检查这个标记skip_result ctx.extra.pop(_skip_tool, None) if skip_result: return ctx.inputs.tool_result, ctx.inputs.tool_msg # 不执行工具直接返回注入的结果也就是说被拒绝的glob/read_file调用根本不会触达真实工具LLM 拿到的只是一条ToolMessage内容为 Tool execution skipped 或 Rail 自定义 message于是 Agent 可以基于这条消息调整策略继续运行——这正是 BEFORE 场景Agent continues的机制根源。5.4force_finishAFTER 拒绝的终止链路AFTER 场景调用ctx.request_force_finish(result)这会写入一个ForceFinishRequest定义见 ForceFinishRequest请求方法见 request_force_finish。Agent 主循环在 ReActAgent 的迭代中多次消费该信号。以工具执行完成后为例见 react_agent.py 主循环results await self._execute_tool_call(ctx, ai_message.tool_calls, session, context) finish ctx.consume_force_finish() if finish: await self.context_engine.save_contexts(session) invoke_inputs.result finish.result break # 结束迭代把 {output: ..., result_type: error} 作为最终结果返回模型调用之后同样会消费一次force_finish见 react_agent.py。由于 AFTER 拒绝发生在工具执行完成后的回调链里该信号在紧接着的消费点被命中Agent 循环随即跳出invoke()以错误结果终止——用户看到的就是 Blocked by security rail。5.5 回调数据载体 ToolCallInputsRail 在run_security_check中通过ctx.inputs访问数据其类型为 ToolCallInputs包含tool_call、tool_name、tool_args、tool_result、tool_msg等字段。示例 Rail 对非ToolCallInputs的输入例如模型事件直接返回allow()if not isinstance(inputs, ToolCallInputs): return self.allow()这也是为什么该 Rail 声明只监听两个工具事件是自洽的——它只在工具调用链路中工作。六、与 ApiKeyGuardInterrupt 的取舍示例文档专门用一节对比了它与 ApiKeyGuardInterrupt 的差异这是选择安全策略时最常纠结的问题特性ApiKeyGuardInterruptToolRejectExample交互方式✓ HITL 人工审批✗ 直接拒绝无交互用户选择权可批准/拒绝还可总是允许无选择BEFORE 拒绝用户拒绝 → skip自动拒绝 → skipAFTER 拒绝用户拒绝 → force_finish自动拒绝 → force_finish何时选 Interrupt希望在阻断前给用户一个知情与决策的机会。ApiKeyGuardInterrupt的run_security_check会构造带payload_schema和ui_options的InterruptRequest并通过_handle_interrupt_resume支持auto_confirm记住本工具的审批结果机制实现一次审批、会话内自动放行见 ApiKeyGuardInterrupt/rail.py。何时选 Reject本示例追求零人工干预的自动阻断例如无人值守的批处理、流水线或对审批延时不可接受的场景。代价是误报即误伤——一旦正则误命中工具会被跳过或会话被终止所以敏感模式的严谨性在自动拒绝模式下比在审批模式下更关键。从决策类型角度还可以补充一个中间选项SecurityAlert既不阻断也不审批仅放行并弹出告警见ApiKeyGuardAlert与 SecurityAlert适用于低风险、仅提示的场景。七、安装与目录规范7.1 安装方式示例文档给出的安装方式是把整个文件夹复制到宿主应用的扩展目录cp -r examples/security_rail_demo/ToolRejectExample ~/guardrail/extensions/从仓库中examples/security_rail_demo/README.md的说明可知宿主应用jiuwenclaw重启后会自动通过 RailManager 加载扩展并读取扩展目录下的extensions_config.json决定启用哪些 Rail。示例配置example_config.json的字段结构如下摘自 example_config.json{ ApiKeyGuardReject: { name: ApiKeyGuardReject, class_name: ApikeyguardrejectRail, enabled: false, description: Blocks API key/secret leakage completely (REJECT mode), priority: 89 } }每个扩展以文件夹名为 key配置class_name、enabled、priority等字段一次启用一个扩展以便单独测试。若你的项目里要用到 ToolRejectExample可参照此格式为它增加一个以文件夹名ToolRejectExample为 key 的配置项。7.2 命名规范重要宿主应用的 RailManager 对扩展目录结构有硬性约定详见 security_rail_demo/README.md元素规则示例目录名CapitalCamelCaseToolRejectExample、MyCustom文件名必须叫rail.py不能叫tool_reject_example.py类名{目录名首字母大写其余小写}RailToolRejectExample→ToolrejectexampleRail配置 key必须与目录名一致ToolRejectExample特别注意一条必含 import即使你的类继承自BaseSecurityRail也必须显式导入DeepAgentRail以满足 RailManager 的校验from openjiuwen.harness.rails.base import DeepAgentRail # Required for RailManager validationDeepAgentRail在 rails/base.py 中定义它在核心AgentRail的 8 个钩子之外追加了before_task_iteration/after_task_iteration两个外层任务循环钩子BaseSecurityRail本身继承自AgentRail见 base_security_rail.py。示例的 rail.py 已按规范导入该符号# noqa: F401标注其仅为校验而导入见 rail.py。7.3 事件与优先级参考示例文档所在 demo 还给出了安全 Rail 生态的重要参考支持的事件与是否可中断摘自 security_rail_demo/README.md事件触发时机是否支持 InterruptBEFORE_INVOKEAgent invoke 开始前是BEFORE_MODEL_CALLLLM 调用前否自动拒绝AFTER_MODEL_CALLLLM 响应后否自动拒绝BEFORE_TOOL_CALL工具执行前是AFTER_TOOL_CALL工具执行后是ON_MODEL_EXCEPTIONLLM 调用异常否自动拒绝ON_TOOL_EXCEPTION工具执行异常是模型事件不支持 Interrupt 这一点在基类中有强制保障_run_and_apply检测到模型事件上出现SecurityInterrupt时会自动降级为SecurityReject见 base_security_rail.py并有单元测试test_security_interrupt_on_model_event_auto_rejected专门覆盖见 test_base_security_rail.py。优先级参考优先级典型用途100最高优先级最终检查85–95安全 Rail本示例 88 即落在此区间50–70处理类 Rail10–30日志/遥测 Rail优先级语义为数值越大越先执行AgentRail.priority定义见 rail/base.py集成测试test_priority_ordering_high_runs_before_low验证了高优先级先行的排序见 test_base_security_rail_integration.py。八、测试场景与验证方法8.1 手工测试场景示例文档给出了两条可直接照做的验证用例BEFORE 拒绝测试用户输入glob *sk-secret123*预期结果工具被跳过不真正执行 globAgent 收到 Tool execution skipped 后继续并尝试其他方案如向用户索要安全的文件名。AFTER 拒绝测试用户输入read .env file containing API_KEYsk-xxx预期结果工具执行后结果被拦截Agent 以错误终止用户看到 Blocked by security rail。8.2 仓库级自动化测试仓库中针对BaseSecurityRail有两层现成测试可作为自行验证与扩展的模板单元测试tests/unit_tests/harness/rails/test_base_security_rail.py验证get_callbacks只注册声明事件、模型事件 reject 会触发request_force_finish并产出{output: ..., result_type: error}结构、模型事件 interrupt 自动降级为 reject。集成测试tests/system_tests/rail/test_base_security_rail_integration.py通过ReActAgentMockLLMModel 真实LocalFunction工具跑完整invoke()流程覆盖SecurityReject改写工具结果、SecurityAllow原样放行、多事件 Rail 记录、优先级排序等。运行方式在 agent-core 仓库根目录下PYTHONPATH. uv run pytest \ tests/unit_tests/harness/rails/test_base_security_rail.py \ tests/system_tests/rail/test_base_security_rail_integration.py \ -v从集成测试test_security_reject_modifies_tool_result可以看到 AFTER 拒绝的完整断言链工具真实执行了一次count 1随后invoke的输出中包含blocked见 test_base_security_rail_integration.py。这与本文第五节的机制分析互相印证AFTER 是先执行、后拦截工具调用计数不会因为拒绝而归零。九、扩展建议与注意事项9.1 面向真实业务的改造方向从源码结构看示例刻意保持了最小可运行形态落地生产时可以围绕以下几点增强模式可配置化SENSITIVE_PATTERNS目前是模块常量可改为从extensions_config.json或环境变量注入便于按租户/环境差异化扩充泄露出口TOOL_WHITELIST之外的 HTTP 请求、MCP 工具等同样是泄露通道可参考 PermissionInterruptRail 的全工具拦截思路扩展监控面与告警/审计联动SecurityAlert决策支持popup/history/inline三种展示模式并通过OutputSchema流式下发见 _apply_alert可在拒绝同时记录审计日志。9.2 使用注意事项误报代价在 REJECT 模式下被放大AFTER 误命中会直接终止整个会话务必先用样本语料回归测试正则避免将普通业务文本误判为密钥AFTER 检测仅覆盖白名单工具未列入白名单的工具如网络请求工具泄露密钥时本 Rail 不会拦截需要按实际风险补齐不适用于需要用户裁决的场景需要审批时请改用ApiKeyGuardInterrupt其auto_confirm_key与_handle_interrupt_resume机制支持记住本次决策见 base_security_rail.py必须遵守扩展命名规范目录名、rail.py文件名、类名、配置 key 四者必须严格对齐否则宿主 RailManager 无法加载。十、总结ToolRejectExample用最短的代码量演示了 agent-core 安全 Rail 体系中最重要的一个设计决策拒绝动作必须与泄露风险程度匹配——工具尚未执行时skip_tool保全任务工具结果已泄露时force_finish止损。这一语义由BaseSecurityRail._apply_reject()按事件分派统一实现经ability_manager的_skip_tool短路与 ReActAgent 主循环的force_finish消费点落地行为可预测、链路可测试。以此为起点配合仓库中的ApiKeyGuardInterrupt人工审批、ApiKeyGuardAlert仅告警与SensitiveDataSanitize脱敏等示例可以搭建出覆盖自动阻断—人工审批—告警提示—数据清洗全谱系的安全防线。赞分享人工智能AI AgentAgent 框架大模型工具调用RAG提示工程强化学习【免费下载链接】agent-coreopenJiuwen agent-core可提供AI Agent开发、运行、调优与演进相关的全套SDK能力项目地址https://gitcode.com/openJiuwen/agent-core点击查看免费下载相关推荐open-multi-agent 工具配置完全指南默认拒绝、治理声明与精细权限控制open multi agent 工具配置完全指南默认拒绝、治理声明与精细权限控制 本文是 open multi agent自托管的 TypeScript人工智能AI Agent多智能体Agent 编排Agent 工作流深入解析 Pillow CVE-2014-3589IcnsImagePlugin 拒绝服务漏洞的原理与修复深入解析 Pillow CVE 2014 3589IcnsImagePlugin 拒绝服务漏洞的原理与修复 本文以 Pillow 官方安全公告 docs/re图像处理计算机视觉smolvm 凭据替换陷阱全解析v1.18.2 与 v1.22.2 实测的边界行为、拒绝语义与安全红线smolvm 凭据替换陷阱全解析v1.18.2 与 v1.22.2 实测的边界行为、拒绝语义与安全红线 smolvm 的凭据替换credential sub虚拟化AI Agent人工智能CLI上一篇DB-GPT v0.6.0 升级指南从 v0.5.10 平滑迁移 MySQL 元数据库下一篇Apache APISIX cors 插件详解跨域资源共享配置、高级匹配策略与源码实现原理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →