Skip to content

Commit 68961d5

Browse files
Violet2314claude
andcommitted
fix(safety): getattr case-fold, schema/compat, fixtures, wrapper language
- Normalize getattr attr names (System/POPEN no longer bypass) - DENY response includes return_code/stdout/stderr for BashTool schema - Scan code blocks with content-aware language inference - CLI manifest gate also checks must_rules - Re-add stable report/audit deliverables; document non-blocking review - Avoid docker package init when creating code execution results Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d0cc85e commit 68961d5

12 files changed

Lines changed: 2196 additions & 99 deletions

File tree

.gitignore

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
1-
.idea
2-
.vscode
3-
.DS_Store
4-
*.lock
5-
*.log
6-
examples/*.log
7-
8-
trpc-agent-py.egg-info
9-
10-
build
11-
dist
12-
.venv
13-
venv
14-
venv*
15-
__pycache__
16-
17-
.coverage
18-
htmlcov
19-
cov.tmp
20-
coverage.xml
21-
test-ngtest-ut-trpc-agent-py.xml
22-
.pytest_cache
23-
24-
node_modules
25-
package-lock.json
26-
pyrightconfig.json
27-
28-
# Generated tool-safety CLI fixtures (regenerate with scripts/tool_safety_check.py)
29-
examples/tool_safety/tool_safety_audit.jsonl
30-
examples/tool_safety/tool_safety_report.json
1+
.idea
2+
.vscode
3+
.DS_Store
4+
*.lock
5+
*.log
6+
examples/*.log
7+
8+
trpc-agent-py.egg-info
9+
10+
build
11+
dist
12+
.venv
13+
venv
14+
venv*
15+
__pycache__
16+
17+
.coverage
18+
htmlcov
19+
cov.tmp
20+
coverage.xml
21+
test-ngtest-ut-trpc-agent-py.xml
22+
.pytest_cache
23+
24+
node_modules
25+
package-lock.json
26+
pyrightconfig.json
27+

examples/tool_safety/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,34 @@ pytest tests/tool_safety/ -v
251251

252252
静态分析无法覆盖所有动态构造。本工具是第一道防线,**不能替代沙箱隔离**
253253

254+
### 非阻断 `needs_human_review` 的可见性
255+
256+
当决策为 `needs_human_review`**** 开启 `block_on_review` / `policy.block_on_review` 时:
257+
258+
- 扫描**不会**阻止 tool 继续执行(`is_continue=True`
259+
- 完整结果仍写入 **审计 JSONL****OpenTelemetry** `tool.safety.*`
260+
- Filter 链路上的 warning 字段可能被后续 tool 返回值覆盖,**调用方不应依赖返回 dict 中的 warning**
261+
- 若要求 review 也必须拦截,请设置 `block_on_review=True`(Filter / BashTool / CodeExecutor 均支持)
262+
263+
---
264+
265+
## 示例 report / audit 交付物
266+
267+
仓库提供稳定示例(posix 路径、固定时间戳占位):
268+
269+
- [`tool_safety_report.json`](tool_safety_report.json)
270+
- [`tool_safety_audit.jsonl`](tool_safety_audit.jsonl)
271+
272+
可用 CLI 重新生成:
273+
274+
```bash
275+
python scripts/tool_safety_check.py \
276+
--samples examples/tool_safety/samples/ \
277+
--policy examples/tool_safety/tool_safety_policy.yaml \
278+
--report examples/tool_safety/tool_safety_report.json \
279+
--audit examples/tool_safety/tool_safety_audit.jsonl
280+
```
281+
254282
---
255283

256284
## 扩展新规则
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "01_safe_python.py", "decision": "allow", "risk_level": "none", "rule_ids": [], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": false, "blocked": false, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/01_safe_python.py", "findings_count": 0}
2+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "02_dangerous_delete.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/02_dangerous_delete.sh", "findings_count": 5}
3+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "03_read_credentials.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/03_read_credentials.py", "findings_count": 3}
4+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "04_network_exfil.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R002_network_egress"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/04_network_exfil.py", "findings_count": 3}
5+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "05_whitelist_network.py", "decision": "allow", "risk_level": "none", "rule_ids": [], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": false, "blocked": false, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/05_whitelist_network.py", "findings_count": 0}
6+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "06_subprocess_call.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R003_process_system"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/06_subprocess_call.py", "findings_count": 5}
7+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "07_shell_injection.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files", "R002_network_egress", "R003_process_system", "R007_code_execution"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/07_shell_injection.sh", "findings_count": 8}
8+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "08_dependency_install.sh", "decision": "deny", "risk_level": "high", "rule_ids": ["R004_dependency_install"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/08_dependency_install.sh", "findings_count": 5}
9+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "09_infinite_loop.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R005_resource_abuse"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/09_infinite_loop.py", "findings_count": 4}
10+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "10_secret_leak.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R002_network_egress", "R006_secret_leak"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/10_secret_leak.py", "findings_count": 7}
11+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "11_bash_pipeline.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files", "R002_network_egress", "R003_process_system", "R005_resource_abuse"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/11_bash_pipeline.sh", "findings_count": 9}
12+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "12_human_review.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R002_network_egress"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/12_human_review.py", "findings_count": 1}
13+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "13_alias_os_system.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R003_process_system"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/13_alias_os_system.py", "findings_count": 1}
14+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "14_from_import_system.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R003_process_system"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/14_from_import_system.py", "findings_count": 1}
15+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "15_base64_pipe.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R003_process_system", "R007_code_execution"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/15_base64_pipe.sh", "findings_count": 2}
16+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "16_pathlib_ssh.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/16_pathlib_ssh.py", "findings_count": 2}
17+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "17_requests_session.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R002_network_egress"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/17_requests_session.py", "findings_count": 1}
18+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "18_env_secret_print.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R006_secret_leak"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/18_env_secret_print.py", "findings_count": 2}
19+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "19_python_c_inline.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files", "R003_process_system"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/19_python_c_inline.sh", "findings_count": 2}
20+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "20_getattr_system.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R003_process_system"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/20_getattr_system.py", "findings_count": 1}
21+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "21_find_delete.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files", "R007_code_execution"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/21_find_delete.sh", "findings_count": 2}
22+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "22_xargs_rm.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files", "R007_code_execution"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/22_xargs_rm.sh", "findings_count": 2}
23+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "23_curl_env_upload.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files", "R002_network_egress", "R006_secret_leak"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/23_curl_env_upload.sh", "findings_count": 4}
24+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "24_dev_tcp.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files", "R002_network_egress"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/24_dev_tcp.sh", "findings_count": 2}
25+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "25_private_key_literal.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R006_secret_leak"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/25_private_key_literal.py", "findings_count": 2}
26+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "26_os_getenv_exfil.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R002_network_egress", "R006_secret_leak"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/26_os_getenv_exfil.py", "findings_count": 3}
27+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "27_socket_access.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R002_network_egress"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/27_socket_access.py", "findings_count": 1}
28+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "28_privilege_escalation.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R003_process_system", "R004_dependency_install"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/28_privilege_escalation.sh", "findings_count": 2}
29+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "29_dynamic_secret_path.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R001_dangerous_files"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/29_dynamic_secret_path.py", "findings_count": 3}
30+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "30_safe_bash.sh", "decision": "allow", "risk_level": "none", "rule_ids": [], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": false, "blocked": false, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/30_safe_bash.sh", "findings_count": 0}
31+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "31_safe_find_grep.sh", "decision": "allow", "risk_level": "none", "rule_ids": [], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": false, "blocked": false, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/31_safe_find_grep.sh", "findings_count": 0}
32+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "32_fork_bomb.sh", "decision": "deny", "risk_level": "critical", "rule_ids": ["R005_resource_abuse"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/32_fork_bomb.sh", "findings_count": 1}
33+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "33_safe_git_status.sh", "decision": "allow", "risk_level": "none", "rule_ids": [], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": false, "blocked": false, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/33_safe_git_status.sh", "findings_count": 0}
34+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "34_safe_python_math.py", "decision": "allow", "risk_level": "none", "rule_ids": [], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": false, "blocked": false, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/34_safe_python_math.py", "findings_count": 0}
35+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "35_long_sleep.sh", "decision": "deny", "risk_level": "high", "rule_ids": ["R005_resource_abuse"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/35_long_sleep.sh", "findings_count": 1}
36+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "36_httpx_client_evil.py", "decision": "deny", "risk_level": "high", "rule_ids": ["R002_network_egress"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/36_httpx_client_evil.py", "findings_count": 1}
37+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "37_eval_exec.py", "decision": "deny", "risk_level": "critical", "rule_ids": ["R003_process_system", "R007_code_execution"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "python", "script_path": "examples/tool_safety/samples/37_eval_exec.py", "findings_count": 5}
38+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "38_npm_install.sh", "decision": "deny", "risk_level": "high", "rule_ids": ["R004_dependency_install"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/38_npm_install.sh", "findings_count": 2}
39+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "39_curl_dynamic_url.sh", "decision": "deny", "risk_level": "high", "rule_ids": ["R002_network_egress"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/39_curl_dynamic_url.sh", "findings_count": 2}
40+
{"timestamp": "1970-01-01T00:00:00+0000", "tool_name": "40_background_nc.sh", "decision": "deny", "risk_level": "high", "rule_ids": ["R002_network_egress", "R003_process_system"], "scan_duration_ms": 0.0, "sanitized": true, "intercepted": true, "blocked": true, "scanner_version": "1.2.0", "language": "bash", "script_path": "examples/tool_safety/samples/40_background_nc.sh", "findings_count": 2}

0 commit comments

Comments
 (0)