Coverage for tests / test_security.py: 100%
95 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 02:55 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 02:55 +0800
1"""
2安全模块测试
4测试覆盖:
5- 权限配置加载
6- 路径访问检查
7- 命令安全检查
8- 权限继承
9"""
10import pytest
11from pathlib import Path
12from qrclaw.security import (
13 SecurityManager,
14 AgentPermission,
15 PermissionConfig,
16 _get_effective_agent_id,
17 DANGEROUS_COMMANDS,
18 PROTECTED_PATHS,
19 PERMISSION_DENIED_SUFFIX,
20)
23class TestPermissionModel:
24 """测试权限模型"""
26 def test_agent_permission_defaults(self):
27 """测试默认权限配置"""
28 perm = AgentPermission()
30 assert perm.access == "scoped"
31 assert perm.allow_paths == []
33 def test_agent_permission_full(self):
34 """测试完全访问权限"""
35 perm = AgentPermission(access="full")
37 assert perm.access == "full"
39 def test_permission_config_defaults(self):
40 """测试默认全局配置"""
41 config = PermissionConfig()
43 assert config.default_policy == "restricted"
44 assert config.agents == {}
47class TestSecurityManager:
48 """测试安全管理器"""
50 def test_singleton_pattern(self):
51 """测试单例模式"""
52 manager1 = SecurityManager()
53 manager2 = SecurityManager()
55 assert manager1 is manager2
57 def test_get_permission_default_agent(self):
58 """测试获取 default agent 权限"""
59 manager = SecurityManager()
60 perm = manager.get_permission("default")
62 # default agent 应该有 full access
63 assert perm.access == "full"
65 def test_get_permission_unknown_agent(self):
66 """测试获取未知 agent 权限"""
67 manager = SecurityManager()
68 perm = manager.get_permission("unknown_agent")
70 # 未知 agent 应该使用默认策略
71 assert perm.access == "scoped"
74class TestPathSafety:
75 """测试路径安全检查"""
77 def test_check_path_in_workspace(self, temp_dir):
78 """测试工作区内的路径"""
79 manager = SecurityManager()
81 # scoped agent 访问 workspace 内的文件应该放行
82 manager._check_path_safety(
83 path_str=str(temp_dir / "test.txt"),
84 perm=AgentPermission(access="scoped"),
85 workspace_root=temp_dir,
86 tool_name="read_file",
87 agent_id="test_agent"
88 )
89 # 不抛异常 = 通过
91 def test_check_path_outside_workspace(self, temp_dir):
92 """测试工作区外的路径"""
93 manager = SecurityManager()
95 # scoped agent 访问 workspace 外的文件应该拒绝
96 with pytest.raises(PermissionError) as exc_info:
97 manager._check_path_safety(
98 path_str="/tmp/outside.txt",
99 perm=AgentPermission(access="scoped"),
100 workspace_root=temp_dir,
101 tool_name="read_file",
102 agent_id="test_agent"
103 )
105 assert "无权访问" in str(exc_info.value)
107 def test_check_path_whitelist(self, temp_dir):
108 """测试白名单路径"""
109 manager = SecurityManager()
111 # 允许访问 /tmp
112 perm = AgentPermission(
113 access="scoped",
114 allow_paths=["/tmp"]
115 )
117 # 应该放行
118 manager._check_path_safety(
119 path_str="/tmp/allowed.txt",
120 perm=perm,
121 workspace_root=temp_dir,
122 tool_name="read_file",
123 agent_id="test_agent"
124 )
126 def test_check_path_readonly_write_outside(self, temp_dir):
127 """测试只读权限在白名单外写入"""
128 manager = SecurityManager()
130 # 白名单路径
131 perm = AgentPermission(access="readonly", allow_paths=["/tmp"])
133 # 只读权限尝试写入白名单路径应该拒绝
134 with pytest.raises(PermissionError) as exc_info:
135 manager._check_path_safety(
136 path_str="/tmp/test.txt",
137 perm=perm,
138 workspace_root=temp_dir,
139 tool_name="write_file",
140 agent_id="test_agent"
141 )
143 assert "只读" in str(exc_info.value)
145 def test_check_path_readonly_read(self, temp_dir):
146 """测试只读权限读取"""
147 manager = SecurityManager()
149 perm = AgentPermission(access="readonly")
151 # 只读权限读取应该放行
152 manager._check_path_safety(
153 path_str=str(temp_dir / "test.txt"),
154 perm=perm,
155 workspace_root=temp_dir,
156 tool_name="read_file",
157 agent_id="test_agent"
158 )
160 def test_check_protected_path(self, temp_dir):
161 """测试系统保护路径"""
162 manager = SecurityManager()
164 # 保护路径(如 permissions.yaml)
165 protected_path = str(PROTECTED_PATHS[0])
167 # 非 default agent 应该拒绝
168 with pytest.raises(PermissionError) as exc_info:
169 manager._check_path_safety(
170 path_str=protected_path,
171 perm=AgentPermission(access="full"),
172 workspace_root=temp_dir,
173 tool_name="read_file",
174 agent_id="not_default"
175 )
177 assert "系统配置文件" in str(exc_info.value)
180class TestShellSafety:
181 """测试 Shell 命令安全检查"""
183 def test_dangerous_commands_patterns(self):
184 """测试高危命令正则"""
185 # 测试一些高危命令
186 dangerous_examples = [
187 "rm -rf /",
188 "rm -rf ~",
189 "rm -rf *",
190 "mkfs /dev/sda1",
191 "chmod 777 /",
192 ]
194 import re
195 for cmd in dangerous_examples:
196 # 至少匹配一个高危模式
197 matches = [re.search(pattern, cmd, re.IGNORECASE) for pattern in DANGEROUS_COMMANDS]
198 assert any(matches), f"命令 '{cmd}' 应该被识别为高危"
200 def test_check_shell_safety_dangerous_command(self, temp_dir):
201 """测试高危命令检查"""
202 manager = SecurityManager()
204 # 高危命令应该直接拒绝
205 with pytest.raises(PermissionError) as exc_info:
206 manager._check_shell_safety(
207 command="rm -rf /",
208 perm=AgentPermission(access="scoped"),
209 workspace_root=temp_dir,
210 agent_id="test_agent"
211 )
213 assert "高危命令" in str(exc_info.value)
216class TestPermissionInheritance:
217 """测试权限继承"""
219 def test_get_effective_agent_id_no_parent(self):
220 """测试没有父 agent 的情况"""
221 # 清空父 agent 映射
222 from qrclaw.tools import spawn_agent
223 spawn_agent._parent_agent_map.clear()
225 result = _get_effective_agent_id("standalone_agent")
227 assert result == "standalone_agent"
229 def test_get_effective_agent_id_with_parent(self):
230 """测试有父 agent 的情况"""
231 from qrclaw.tools.spawn_agent import set_parent_agent, clear_parent_agent
233 # 设置父子关系
234 set_parent_agent("child_agent", "parent_agent")
236 # 应该返回父 agent
237 result = _get_effective_agent_id("child_agent")
239 # 清理
240 clear_parent_agent("child_agent")
242 # 由于递归查找,最终应该返回 parent_agent
243 # 但如果 parent_agent 没有父 agent,就返回 parent_agent
244 assert result == "parent_agent"
247class TestAccessCheck:
248 """测试完整访问检查"""
250 def test_check_access_full_permission(self, temp_dir):
251 """测试完全权限"""
252 manager = SecurityManager()
254 # full access 应该放行所有操作
255 manager.check_access(
256 agent_id="default",
257 tool_name="read_file",
258 args={"path": "/any/path"},
259 workspace_root=temp_dir
260 )
262 def test_check_access_scoped_in_workspace(self, temp_dir):
263 """测试受限权限在 workspace 内"""
264 manager = SecurityManager()
266 # scoped 在 workspace 内应该放行
267 manager.check_access(
268 agent_id="test_agent",
269 tool_name="read_file",
270 args={"path": str(temp_dir / "test.txt")},
271 workspace_root=temp_dir
272 )
274 def test_check_access_scoped_outside_workspace(self, temp_dir):
275 """测试受限权限在 workspace 外"""
276 manager = SecurityManager()
278 # scoped 在 workspace 外应该拒绝
279 with pytest.raises(PermissionError):
280 manager.check_access(
281 agent_id="test_agent",
282 tool_name="read_file",
283 args={"path": "/tmp/outside.txt"},
284 workspace_root=temp_dir
285 )
287 def test_check_access_no_path_arg(self, temp_dir):
288 """测试无路径参数的工具"""
289 manager = SecurityManager()
291 # 无路径参数的工具应该跳过路径检查
292 manager.check_access(
293 agent_id="test_agent",
294 tool_name="some_tool",
295 args={},
296 workspace_root=temp_dir
297 )