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

1""" 

2安全模块测试 

3 

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) 

21 

22 

23class TestPermissionModel: 

24 """测试权限模型""" 

25 

26 def test_agent_permission_defaults(self): 

27 """测试默认权限配置""" 

28 perm = AgentPermission() 

29 

30 assert perm.access == "scoped" 

31 assert perm.allow_paths == [] 

32 

33 def test_agent_permission_full(self): 

34 """测试完全访问权限""" 

35 perm = AgentPermission(access="full") 

36 

37 assert perm.access == "full" 

38 

39 def test_permission_config_defaults(self): 

40 """测试默认全局配置""" 

41 config = PermissionConfig() 

42 

43 assert config.default_policy == "restricted" 

44 assert config.agents == {} 

45 

46 

47class TestSecurityManager: 

48 """测试安全管理器""" 

49 

50 def test_singleton_pattern(self): 

51 """测试单例模式""" 

52 manager1 = SecurityManager() 

53 manager2 = SecurityManager() 

54 

55 assert manager1 is manager2 

56 

57 def test_get_permission_default_agent(self): 

58 """测试获取 default agent 权限""" 

59 manager = SecurityManager() 

60 perm = manager.get_permission("default") 

61 

62 # default agent 应该有 full access 

63 assert perm.access == "full" 

64 

65 def test_get_permission_unknown_agent(self): 

66 """测试获取未知 agent 权限""" 

67 manager = SecurityManager() 

68 perm = manager.get_permission("unknown_agent") 

69 

70 # 未知 agent 应该使用默认策略 

71 assert perm.access == "scoped" 

72 

73 

74class TestPathSafety: 

75 """测试路径安全检查""" 

76 

77 def test_check_path_in_workspace(self, temp_dir): 

78 """测试工作区内的路径""" 

79 manager = SecurityManager() 

80 

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 # 不抛异常 = 通过 

90 

91 def test_check_path_outside_workspace(self, temp_dir): 

92 """测试工作区外的路径""" 

93 manager = SecurityManager() 

94 

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 ) 

104 

105 assert "无权访问" in str(exc_info.value) 

106 

107 def test_check_path_whitelist(self, temp_dir): 

108 """测试白名单路径""" 

109 manager = SecurityManager() 

110 

111 # 允许访问 /tmp 

112 perm = AgentPermission( 

113 access="scoped", 

114 allow_paths=["/tmp"] 

115 ) 

116 

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 ) 

125 

126 def test_check_path_readonly_write_outside(self, temp_dir): 

127 """测试只读权限在白名单外写入""" 

128 manager = SecurityManager() 

129 

130 # 白名单路径 

131 perm = AgentPermission(access="readonly", allow_paths=["/tmp"]) 

132 

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 ) 

142 

143 assert "只读" in str(exc_info.value) 

144 

145 def test_check_path_readonly_read(self, temp_dir): 

146 """测试只读权限读取""" 

147 manager = SecurityManager() 

148 

149 perm = AgentPermission(access="readonly") 

150 

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 ) 

159 

160 def test_check_protected_path(self, temp_dir): 

161 """测试系统保护路径""" 

162 manager = SecurityManager() 

163 

164 # 保护路径(如 permissions.yaml) 

165 protected_path = str(PROTECTED_PATHS[0]) 

166 

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 ) 

176 

177 assert "系统配置文件" in str(exc_info.value) 

178 

179 

180class TestShellSafety: 

181 """测试 Shell 命令安全检查""" 

182 

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 ] 

193 

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}' 应该被识别为高危" 

199 

200 def test_check_shell_safety_dangerous_command(self, temp_dir): 

201 """测试高危命令检查""" 

202 manager = SecurityManager() 

203 

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 ) 

212 

213 assert "高危命令" in str(exc_info.value) 

214 

215 

216class TestPermissionInheritance: 

217 """测试权限继承""" 

218 

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() 

224 

225 result = _get_effective_agent_id("standalone_agent") 

226 

227 assert result == "standalone_agent" 

228 

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 

232 

233 # 设置父子关系 

234 set_parent_agent("child_agent", "parent_agent") 

235 

236 # 应该返回父 agent 

237 result = _get_effective_agent_id("child_agent") 

238 

239 # 清理 

240 clear_parent_agent("child_agent") 

241 

242 # 由于递归查找,最终应该返回 parent_agent 

243 # 但如果 parent_agent 没有父 agent,就返回 parent_agent 

244 assert result == "parent_agent" 

245 

246 

247class TestAccessCheck: 

248 """测试完整访问检查""" 

249 

250 def test_check_access_full_permission(self, temp_dir): 

251 """测试完全权限""" 

252 manager = SecurityManager() 

253 

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 ) 

261 

262 def test_check_access_scoped_in_workspace(self, temp_dir): 

263 """测试受限权限在 workspace 内""" 

264 manager = SecurityManager() 

265 

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 ) 

273 

274 def test_check_access_scoped_outside_workspace(self, temp_dir): 

275 """测试受限权限在 workspace 外""" 

276 manager = SecurityManager() 

277 

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 ) 

286 

287 def test_check_access_no_path_arg(self, temp_dir): 

288 """测试无路径参数的工具""" 

289 manager = SecurityManager() 

290 

291 # 无路径参数的工具应该跳过路径检查 

292 manager.check_access( 

293 agent_id="test_agent", 

294 tool_name="some_tool", 

295 args={}, 

296 workspace_root=temp_dir 

297 )