Coverage for src/alprina_cli/tools/security/blue_team.py: 15%

181 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-14 11:27 +0100

1""" 

2Blue Team Tool 

3 

4Context Engineering: 

5- Defensive security operations 

6- Threat detection and incident response 

7- Returns structured defensive findings 

8- Memory-aware: Tracks threats and patterns 

9 

10Defend, detect, respond. 

11""" 

12 

13from typing import Dict, Any, List, Literal, Optional 

14from pydantic import BaseModel, Field 

15from loguru import logger 

16from pathlib import Path 

17import re 

18from datetime import datetime 

19 

20from alprina_cli.tools.base import AlprinaToolBase, ToolOk, ToolError 

21 

22 

23class BlueTeamParams(BaseModel): 

24 """ 

25 Parameters for blue team operations. 

26 

27 Context: Focused schema for defensive security. 

28 """ 

29 target: str = Field( 

30 description="Target for blue team operations (logs, systems, network)" 

31 ) 

32 operation: Literal["threat_hunt", "incident_response", "log_analysis", "ioc_search", "baseline", "full_defense"] = Field( 

33 default="threat_hunt", 

34 description="Operation: threat_hunt, incident_response, log_analysis, ioc_search, baseline, full_defense" 

35 ) 

36 severity_threshold: Literal["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"] = Field( 

37 default="MEDIUM", 

38 description="Minimum severity to report" 

39 ) 

40 max_findings: int = Field( 

41 default=50, 

42 description="Maximum findings to return" 

43 ) 

44 

45 

46class BlueTeamTool(AlprinaToolBase[BlueTeamParams]): 

47 """ 

48 Blue team tool for defensive security operations. 

49 

50 Context Engineering Benefits: 

51 - Structured threat findings 

52 - Memory integration for pattern tracking 

53 - Severity-based filtering 

54 - IOC detection and correlation 

55 

56 Operations: 

57 - threat_hunt: Proactive threat hunting 

58 - incident_response: Respond to incidents 

59 - log_analysis: Analyze logs for anomalies 

60 - ioc_search: Search for indicators of compromise 

61 - baseline: Establish security baseline 

62 - full_defense: Comprehensive defensive assessment 

63 

64 Usage: 

65 ```python 

66 tool = BlueTeamTool(memory_service=memory) 

67 result = await tool.execute(BlueTeamParams( 

68 target="/var/log", 

69 operation="threat_hunt", 

70 severity_threshold="HIGH" 

71 )) 

72 ``` 

73 """ 

74 

75 name: str = "BlueTeam" 

76 description: str = """Blue team defensive security operations. 

77 

78Capabilities: 

79- Proactive threat hunting 

80- Incident response procedures 

81- Log analysis and anomaly detection 

82- IOC search and correlation 

83- Security baseline establishment 

84- Comprehensive defensive assessment 

85 

86Returns: Structured defensive findings""" 

87 params: type[BlueTeamParams] = BlueTeamParams 

88 

89 # Common IOCs to search for 

90 KNOWN_IOCS = { 

91 "malicious_ips": ["10.0.0.0/8", "192.168.0.0/16"], # Example private ranges 

92 "suspicious_commands": ["nc -e", "bash -i", "/dev/tcp", "python -c", "perl -e"], 

93 "malware_signatures": ["mimikatz", "powersploit", "metasploit", "cobalt"], 

94 "suspicious_files": [".exe.txt", ".scr", ".vbs", ".ps1"], 

95 } 

96 

97 async def execute(self, params: BlueTeamParams) -> ToolOk | ToolError: 

98 """ 

99 Execute blue team operation. 

100 

101 Context: Returns structured defensive findings. 

102 """ 

103 logger.info(f"BlueTeam: {params.target} (op={params.operation}, severity={params.severity_threshold})") 

104 

105 try: 

106 # Check memory for past threats 

107 if self.memory_service and self.memory_service.is_enabled(): 

108 past_threats = self.memory_service.search( 

109 f"Previous threats found in {params.target}", 

110 limit=5 

111 ) 

112 if past_threats: 

113 logger.info(f"Found {len(past_threats)} past threat records") 

114 

115 # Execute operation 

116 if params.operation == "threat_hunt": 

117 findings = await self._threat_hunt_operation(params) 

118 elif params.operation == "incident_response": 

119 findings = await self._incident_response_operation(params) 

120 elif params.operation == "log_analysis": 

121 findings = await self._log_analysis_operation(params) 

122 elif params.operation == "ioc_search": 

123 findings = await self._ioc_search_operation(params) 

124 elif params.operation == "baseline": 

125 findings = await self._baseline_operation(params) 

126 else: # full_defense 

127 findings = await self._full_defense_operation(params) 

128 

129 # Filter by severity 

130 findings = self._filter_by_severity(findings, params.severity_threshold) 

131 

132 # Limit findings 

133 if len(findings) > params.max_findings: 

134 findings = findings[:params.max_findings] 

135 truncated = True 

136 else: 

137 truncated = False 

138 

139 # Calculate threat stats 

140 severity_counts = {} 

141 threat_detected = False 

142 for finding in findings: 

143 sev = finding.get("severity", "INFO") 

144 severity_counts[sev] = severity_counts.get(sev, 0) + 1 

145 if finding.get("threat_detected", False): 

146 threat_detected = True 

147 

148 result_content = { 

149 "target": params.target, 

150 "operation": params.operation, 

151 "findings": findings, 

152 "summary": { 

153 "total_findings": len(findings), 

154 "threat_detected": threat_detected, 

155 "by_severity": severity_counts, 

156 "truncated": truncated, 

157 "timestamp": datetime.utcnow().isoformat() 

158 } 

159 } 

160 

161 # Store in memory 

162 if self.memory_service and self.memory_service.is_enabled(): 

163 self.memory_service.add_scan_results( 

164 tool_name="BlueTeam", 

165 target=params.target, 

166 results=result_content 

167 ) 

168 

169 return ToolOk(content=result_content) 

170 

171 except Exception as e: 

172 logger.error(f"Blue team operation failed: {e}") 

173 return ToolError( 

174 message=f"Blue team operation failed: {str(e)}", 

175 brief="Operation failed" 

176 ) 

177 

178 async def _threat_hunt_operation(self, params: BlueTeamParams) -> List[Dict[str, Any]]: 

179 """ 

180 Proactive threat hunting. 

181 

182 Context: Hunt for hidden threats. 

183 """ 

184 findings = [] 

185 

186 target_path = Path(params.target).expanduser() 

187 

188 if target_path.exists(): 

189 if target_path.is_file(): 

190 findings.extend(self._hunt_file(target_path)) 

191 else: 

192 # Hunt directory 

193 files_checked = 0 

194 for file_path in target_path.rglob("*"): 

195 if file_path.is_file() and files_checked < 50: 

196 findings.extend(self._hunt_file(file_path)) 

197 files_checked += 1 

198 

199 # Add hunt summary 

200 findings.append({ 

201 "operation": "threat_hunt", 

202 "technique": "Proactive Hunting", 

203 "severity": "INFO", 

204 "description": f"Completed threat hunt on {params.target}", 

205 "files_checked": files_checked if target_path.is_dir() else 1, 

206 "threat_detected": any(f.get("threat_detected") for f in findings) 

207 }) 

208 

209 return findings 

210 

211 def _hunt_file(self, file_path: Path) -> List[Dict[str, Any]]: 

212 """Hunt for threats in a file""" 

213 findings = [] 

214 

215 try: 

216 # Skip binary files 

217 if self._is_binary(file_path): 

218 return findings 

219 

220 content = file_path.read_text(errors="ignore") 

221 

222 # Check for suspicious commands 

223 for cmd in self.KNOWN_IOCS["suspicious_commands"]: 

224 if cmd in content: 

225 findings.append({ 

226 "operation": "threat_hunt", 

227 "technique": "Suspicious Command Detection", 

228 "severity": "HIGH", 

229 "threat_detected": True, 

230 "description": f"Suspicious command found: {cmd}", 

231 "file": str(file_path), 

232 "ioc": cmd 

233 }) 

234 

235 # Check for malware signatures 

236 for signature in self.KNOWN_IOCS["malware_signatures"]: 

237 if signature.lower() in content.lower(): 

238 findings.append({ 

239 "operation": "threat_hunt", 

240 "technique": "Malware Signature Detection", 

241 "severity": "CRITICAL", 

242 "threat_detected": True, 

243 "description": f"Malware signature detected: {signature}", 

244 "file": str(file_path), 

245 "ioc": signature 

246 }) 

247 

248 # Check for encoded payloads 

249 if re.search(r"base64|fromCharCode|eval\(", content): 

250 findings.append({ 

251 "operation": "threat_hunt", 

252 "technique": "Encoded Payload Detection", 

253 "severity": "MEDIUM", 

254 "threat_detected": True, 

255 "description": "Potential encoded payload", 

256 "file": str(file_path) 

257 }) 

258 

259 except Exception as e: 

260 logger.debug(f"Could not hunt file {file_path}: {e}") 

261 

262 return findings 

263 

264 async def _incident_response_operation(self, params: BlueTeamParams) -> List[Dict[str, Any]]: 

265 """ 

266 Incident response procedures. 

267 

268 Context: Respond to security incidents. 

269 """ 

270 findings = [] 

271 

272 target_path = Path(params.target).expanduser() 

273 

274 # Check for incident artifacts 

275 if target_path.exists(): 

276 # Look for recently modified files (potential compromise) 

277 if target_path.is_dir(): 

278 recent_files = [] 

279 for file_path in target_path.rglob("*"): 

280 if file_path.is_file(): 

281 recent_files.append(file_path) 

282 if len(recent_files) >= 10: 

283 break 

284 

285 if recent_files: 

286 findings.append({ 

287 "operation": "incident_response", 

288 "technique": "Timeline Analysis", 

289 "severity": "INFO", 

290 "description": f"Analyzed {len(recent_files)} recent files", 

291 "files": [str(f) for f in recent_files[:5]] 

292 }) 

293 

294 # Check for suspicious file names 

295 if target_path.is_dir(): 

296 for ext in self.KNOWN_IOCS["suspicious_files"]: 

297 suspicious = list(target_path.rglob(f"*{ext}"))[:5] 

298 if suspicious: 

299 findings.append({ 

300 "operation": "incident_response", 

301 "technique": "Suspicious File Discovery", 

302 "severity": "HIGH", 

303 "threat_detected": True, 

304 "description": f"Found suspicious files: {ext}", 

305 "files": [str(f) for f in suspicious] 

306 }) 

307 

308 findings.append({ 

309 "operation": "incident_response", 

310 "technique": "IR Procedures", 

311 "severity": "INFO", 

312 "description": "Incident response analysis complete", 

313 "recommendation": "Check: modified files, persistence mechanisms, lateral movement indicators" 

314 }) 

315 

316 return findings 

317 

318 async def _log_analysis_operation(self, params: BlueTeamParams) -> List[Dict[str, Any]]: 

319 """ 

320 Log analysis for anomalies. 

321 

322 Context: Analyze logs for threats. 

323 """ 

324 findings = [] 

325 

326 target_path = Path(params.target).expanduser() 

327 

328 if target_path.exists(): 

329 log_files = [] 

330 

331 if target_path.is_file(): 

332 log_files = [target_path] 

333 else: 

334 # Find log files 

335 for pattern in ["*.log", "*.txt", "*access*", "*error*"]: 

336 log_files.extend(list(target_path.rglob(pattern))[:10]) 

337 

338 for log_file in log_files[:5]: 

339 try: 

340 content = log_file.read_text(errors="ignore") 

341 lines = content.splitlines() 

342 

343 # Check for failed auth attempts 

344 failed_auth = sum(1 for line in lines if re.search(r"(failed|denied|unauthorized|403|401)", line, re.IGNORECASE)) 

345 if failed_auth > 5: 

346 findings.append({ 

347 "operation": "log_analysis", 

348 "technique": "Failed Authentication Detection", 

349 "severity": "HIGH", 

350 "threat_detected": True, 

351 "description": f"Multiple failed auth attempts: {failed_auth}", 

352 "file": str(log_file) 

353 }) 

354 

355 # Check for errors 

356 errors = sum(1 for line in lines if re.search(r"(error|exception|fatal)", line, re.IGNORECASE)) 

357 if errors > 10: 

358 findings.append({ 

359 "operation": "log_analysis", 

360 "technique": "Error Pattern Analysis", 

361 "severity": "MEDIUM", 

362 "description": f"High error rate detected: {errors} errors", 

363 "file": str(log_file) 

364 }) 

365 

366 except Exception as e: 

367 logger.debug(f"Could not analyze log {log_file}: {e}") 

368 

369 findings.append({ 

370 "operation": "log_analysis", 

371 "technique": "Log Review", 

372 "severity": "INFO", 

373 "description": f"Analyzed {len(log_files)} log files" 

374 }) 

375 

376 return findings 

377 

378 async def _ioc_search_operation(self, params: BlueTeamParams) -> List[Dict[str, Any]]: 

379 """ 

380 Search for indicators of compromise. 

381 

382 Context: Hunt for known IOCs. 

383 """ 

384 findings = [] 

385 

386 target_path = Path(params.target).expanduser() 

387 

388 if target_path.exists(): 

389 # Search for all IOC types 

390 for ioc_type, ioc_list in self.KNOWN_IOCS.items(): 

391 for ioc in ioc_list: 

392 # Search in files 

393 if target_path.is_dir(): 

394 matches = [] 

395 for file_path in target_path.rglob("*"): 

396 if file_path.is_file() and not self._is_binary(file_path): 

397 try: 

398 content = file_path.read_text(errors="ignore") 

399 if ioc.lower() in content.lower(): 

400 matches.append(str(file_path)) 

401 if len(matches) >= 3: 

402 break 

403 except Exception: 

404 pass 

405 

406 if matches: 

407 findings.append({ 

408 "operation": "ioc_search", 

409 "technique": "IOC Correlation", 

410 "severity": "HIGH", 

411 "threat_detected": True, 

412 "description": f"IOC found: {ioc}", 

413 "ioc_type": ioc_type, 

414 "ioc": ioc, 

415 "matches": matches 

416 }) 

417 

418 findings.append({ 

419 "operation": "ioc_search", 

420 "technique": "IOC Hunting", 

421 "severity": "INFO", 

422 "description": f"Searched for {sum(len(v) for v in self.KNOWN_IOCS.values())} known IOCs" 

423 }) 

424 

425 return findings 

426 

427 async def _baseline_operation(self, params: BlueTeamParams) -> List[Dict[str, Any]]: 

428 """ 

429 Establish security baseline. 

430 

431 Context: Create baseline for monitoring. 

432 """ 

433 findings = [] 

434 

435 target_path = Path(params.target).expanduser() 

436 

437 if target_path.exists(): 

438 # Baseline statistics 

439 stats = { 

440 "total_files": 0, 

441 "total_size": 0, 

442 "file_types": {} 

443 } 

444 

445 if target_path.is_dir(): 

446 for file_path in target_path.rglob("*"): 

447 if file_path.is_file(): 

448 stats["total_files"] += 1 

449 stats["total_size"] += file_path.stat().st_size 

450 ext = file_path.suffix or "no_extension" 

451 stats["file_types"][ext] = stats["file_types"].get(ext, 0) + 1 

452 

453 findings.append({ 

454 "operation": "baseline", 

455 "technique": "Baseline Establishment", 

456 "severity": "INFO", 

457 "description": "Security baseline created", 

458 "baseline": stats, 

459 "recommendation": "Monitor for deviations from this baseline" 

460 }) 

461 

462 return findings 

463 

464 async def _full_defense_operation(self, params: BlueTeamParams) -> List[Dict[str, Any]]: 

465 """ 

466 Full defensive assessment. 

467 

468 Context: Comprehensive defensive check. 

469 """ 

470 findings = [] 

471 

472 # Execute all defensive operations 

473 findings.extend(await self._threat_hunt_operation(params)) 

474 findings.extend(await self._log_analysis_operation(params)) 

475 findings.extend(await self._ioc_search_operation(params)) 

476 findings.extend(await self._baseline_operation(params)) 

477 

478 return findings 

479 

480 def _filter_by_severity(self, findings: List[Dict[str, Any]], threshold: str) -> List[Dict[str, Any]]: 

481 """Filter findings by severity threshold""" 

482 severity_order = {"INFO": 0, "LOW": 1, "MEDIUM": 2, "HIGH": 3, "CRITICAL": 4} 

483 threshold_level = severity_order.get(threshold, 2) 

484 

485 return [ 

486 f for f in findings 

487 if severity_order.get(f.get("severity", "INFO"), 0) >= threshold_level 

488 ] 

489 

490 def _is_binary(self, file_path: Path) -> bool: 

491 """Check if file is binary""" 

492 try: 

493 with open(file_path, 'rb') as f: 

494 chunk = f.read(8192) 

495 return b'\x00' in chunk 

496 except Exception: 

497 return False