|
| 1 | +# Tool Script Safety Guard 设计说明 |
| 2 | + |
| 3 | +## 目标与边界 |
| 4 | + |
| 5 | +Safety Guard 是执行前治理层。它对脚本、命令、参数、工作目录、环境变量名称和 tool 元数据做静态检查,输出 `allow`、`deny` 或 `needs_human_review`。它不能替代进程隔离、只读文件系统、最小权限、网络出口控制和运行时资源限制。 |
| 6 | + |
| 7 | +数据流如下: |
| 8 | + |
| 9 | +```text |
| 10 | +Tool / MCP Tool / Skill / CodeExecutor 请求 |
| 11 | + | |
| 12 | + v |
| 13 | +SafetyScanRequest(不保留环境变量值) |
| 14 | + | |
| 15 | + v |
| 16 | +ToolSafetyScanner + ToolSafetyPolicy + SafetyRule[] |
| 17 | + | |
| 18 | + v |
| 19 | +SafetyFinding[] --聚合--> SafetyReport |
| 20 | + | | |
| 21 | + v v |
| 22 | +JSONL AuditEvent Filter / Guard 执行决策 |
| 23 | + | |
| 24 | + v |
| 25 | +OpenTelemetry tool.safety.* 属性 |
| 26 | +``` |
| 27 | + |
| 28 | +扫描发生在真实 handler 或 executor delegate 之前。`deny` 必须阻断;`needs_human_review` 是否阻断由 `block_on_review` 决定。人工批准应由上层审批系统显式记录,不能把 review 自动降级为 allow。 |
| 29 | + |
| 30 | +## 接入示例 |
| 31 | + |
| 32 | +### Tool 与 MCP Tool Filter |
| 33 | + |
| 34 | +Tool filter 从实际 tool 上下文获取名称,只接收参数中的脚本字段,不要求模型伪造 `tool_name`: |
| 35 | + |
| 36 | +```python |
| 37 | +from trpc_agent_sdk.tools import BashTool |
| 38 | +from trpc_agent_sdk.tools.safety import JsonlAuditSink |
| 39 | +from trpc_agent_sdk.tools.safety import ToolSafetyFilter, ToolSafetyGuard, ToolSafetyPolicy |
| 40 | + |
| 41 | +policy = ToolSafetyPolicy.from_yaml("tool_safety_policy.yaml") |
| 42 | +safety_guard = ToolSafetyGuard(policy, audit_sink=JsonlAuditSink("tool_safety_audit.jsonl")) |
| 43 | +safety_filter = ToolSafetyFilter(safety_guard) |
| 44 | +tool = BashTool() |
| 45 | +tool.add_one_filter(safety_filter) |
| 46 | +``` |
| 47 | + |
| 48 | +Safety Filter 带有最终授权标记,框架会让其他参数转换 Filter 和 tool callback 先运行,再在最靠近真实 handler 的位置扫描最终参数,避免下游原地修改已扫描内容。 |
| 49 | + |
| 50 | +Filter 会在扫描前合并可信的 tool 固定 override、默认 timeout 和默认 cwd。以示例策略的 `max_timeout_seconds: 120` 为例,`BashTool` 缺省的 300 秒 timeout 会被拒绝,调用方必须显式请求不超过策略的值。参数转换、scanner 或报告聚合异常会生成脱敏的 `SCAN-INPUT` / `SCAN-ERROR` deny 报告并记录一次审计事件,不会以普通异常形式绕过审计。 |
| 51 | + |
| 52 | +`StreamingProgressTool` 在启动用户 async generator 之前运行同一套有序 Filter 和 tool callback;被拒绝时只返回结构化阻断结果,generator 不会开始执行。最终授权只针对完整组装后的 tool call,不应对早期参数分片做放行判断。 |
| 53 | + |
| 54 | +MCP Tool 应在本地代理真正发出 MCP 调用前应用同一个 Filter。远端 MCP server 仍需独立鉴权、最小权限和审计,因为本地静态扫描无法证明远端实现的实际行为。 |
| 55 | + |
| 56 | +### Skill |
| 57 | + |
| 58 | +`skill_run` 的 `command`、`cwd`、`env` 名称和 timeout 都应在 workspace runner 启动进程前进入 Filter: |
| 59 | + |
| 60 | +```python |
| 61 | +from trpc_agent_sdk.skills.tools import SkillRunTool |
| 62 | +from trpc_agent_sdk.tools.safety import ToolSafetyFilter |
| 63 | + |
| 64 | +skill_tool = SkillRunTool(repository=repository, filters=[ToolSafetyFilter(safety_guard)]) |
| 65 | +``` |
| 66 | + |
| 67 | +Skill 内容可能在扫描后被更新,因此还要固定 skill 版本或内容摘要,并在执行环境中限制挂载、网络和凭据。 |
| 68 | + |
| 69 | +### CodeExecutor wrapper |
| 70 | + |
| 71 | +CodeExecutor 使用委托包装,逐个保留代码块语言,再聚合报告: |
| 72 | + |
| 73 | +```python |
| 74 | +from trpc_agent_sdk.code_executors import UnsafeLocalCodeExecutor |
| 75 | +from trpc_agent_sdk.tools.safety import SafetyGuardedCodeExecutor |
| 76 | + |
| 77 | +delegate = UnsafeLocalCodeExecutor(timeout=30) |
| 78 | +executor = SafetyGuardedCodeExecutor(inner=delegate, guard=safety_guard) |
| 79 | +``` |
| 80 | + |
| 81 | +包装器需要镜像 delegate 的 `stateful`、workspace runtime、delimiter 和重试配置,阻断时不得调用 delegate。 |
| 82 | + |
| 83 | +## 规则与策略 |
| 84 | + |
| 85 | +Python 使用 AST 提取调用、常量路径和数据流信号;Bash 使用不执行命令的 token/语法模式扫描。每个 finding 至少包含: |
| 86 | + |
| 87 | +- `rule_id` 和风险分类; |
| 88 | +- `risk_level` 与局部决策; |
| 89 | +- 已脱敏、长度受限的 `evidence`; |
| 90 | +- 可执行的 `recommendation`; |
| 91 | +- 可选行列位置和不含秘密值的 metadata。 |
| 92 | + |
| 93 | +自定义规则通过 scanner 的 `rules` 参数注入,不修改内置 scanner: |
| 94 | + |
| 95 | +```python |
| 96 | +from trpc_agent_sdk.tools.safety import SafetyDecision, SafetyFinding |
| 97 | +from trpc_agent_sdk.tools.safety import RiskCategory, RiskLevel, ToolSafetyScanner |
| 98 | + |
| 99 | +class DenyInternalBinaryRule: |
| 100 | + rule_id = "CUSTOM-INTERNAL-BINARY" |
| 101 | + |
| 102 | + def scan(self, context, policy): |
| 103 | + if "/internal/bin/" not in context.request.script: |
| 104 | + return [] |
| 105 | + return [SafetyFinding( |
| 106 | + rule_id=self.rule_id, |
| 107 | + category=RiskCategory.PROCESS_EXECUTION, |
| 108 | + risk_level=RiskLevel.HIGH, |
| 109 | + decision=SafetyDecision.DENY, |
| 110 | + evidence="/internal/bin/<redacted>", |
| 111 | + recommendation="Use an explicitly approved command.", |
| 112 | + )] |
| 113 | + |
| 114 | +scanner = ToolSafetyScanner(policy, rules=[DenyInternalBinaryRule()]) |
| 115 | +``` |
| 116 | + |
| 117 | +自定义规则必须是纯静态、确定性且无副作用;异常由 scanner 按 policy 的 fail-closed 行为处理。 |
| 118 | + |
| 119 | +## 审计与监控 |
| 120 | + |
| 121 | +审计事件至少包含 `tool_name`、`decision`、`risk_level`、`rule_ids`、扫描耗时、`redacted`、`blocked`、人工批准状态、脚本 SHA-256 和策略版本。不得写入脚本文本、环境变量值、Authorization header 或私钥正文。 |
| 122 | + |
| 123 | +当前 span 应设置: |
| 124 | + |
| 125 | +- `tool.safety.decision` |
| 126 | +- `tool.safety.risk_level` |
| 127 | +- `tool.safety.rule_id` |
| 128 | +- `tool.safety.rule_ids` |
| 129 | +- `tool.safety.duration_ms` |
| 130 | +- `tool.safety.redacted` |
| 131 | +- `tool.safety.blocked` |
| 132 | + |
| 133 | +策略版本和脚本 SHA-256 只写入审计事件,不写入当前 span,以控制 telemetry 基数。 |
| 134 | + |
| 135 | +监控系统可以按 deny/review 比例、rule id、tool name 和扫描失败率告警,但不能把 telemetry 成功视为安全放行条件。 |
| 136 | + |
| 137 | +## 已知限制 |
| 138 | + |
| 139 | +- **误报**:Bash 的复杂引用、合法管道、安全的 subprocess 和测试夹具可能触发 review;通过窄化白名单或单条 rule action 调整,不应关闭整个 guard。 |
| 140 | +- **漏报**:编码、压缩、别名、间接导入、反射、动态属性和多阶段下载执行可能绕过静态模式。 |
| 141 | +- **动态代码**:`eval`、`exec`、运行时拼接 URL/路径和下载后的脚本无法在首次扫描时完全解析,应阻断或人工复核,并在下一执行边界重新扫描。 |
| 142 | +- **TOCTOU**:扫描后文件、Skill、cwd、符号链接或策略可能变化。执行端应校验内容摘要,固定版本,并尽量在隔离 workspace 内完成扫描和执行。 |
| 143 | +- **MCP**:本地只能分析请求参数,无法验证远端 tool 是否执行了额外命令、访问其他数据或正确实施资源限制。 |
| 144 | +- **Streaming**:分片参数在结束前可能不是完整语法。只能在完整 tool call 组装后做最终授权,不能因早期分片看似安全而提前执行。 |
| 145 | +- **资源限制**:静态规则只能识别明显循环、sleep、fork bomb 和大写入信号;CPU、内存、进程数、磁盘、输出和墙钟时间必须由 sandbox/runtime 强制限制。 |
| 146 | +- **运行时注入**:Guard 会检查调用参数、固定 tool override 和可见默认值,但 workspace/repository 在 handler 内部追加的可信环境或文件仍需由运行时白名单、固定配置和 sandbox 约束。 |
| 147 | + |
| 148 | +因此生产部署应组合:Safety Guard + Container/Cube sandbox + 最小凭据 + 出网白名单 + 资源限制 + 不可篡改审计。 |
0 commit comments