Coverage for src/alprina_cli/tools/security/network_analyzer.py: 31%

86 statements  

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

1""" 

2Network Analyzer Tool 

3 

4Context Engineering: 

5- Lightweight tool (not full agent) 

6- Returns high-signal summaries (not verbose logs) 

7- Async by default for composability 

8- CAI integration as optional enhancement 

9 

10Based on: agents/network_analyzer.py (refactored to tool pattern) 

11""" 

12 

13import re 

14from typing import Dict, Any, List 

15from pydantic import BaseModel, Field 

16from loguru import logger 

17 

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

19 

20 

21# Try to import CAI (optional enhancement) 

22try: 

23 from alprina.agents import get_agent_by_name 

24 CAI_AVAILABLE = True 

25except ImportError: 

26 CAI_AVAILABLE = False 

27 logger.debug("CAI not available - using built-in network analysis") 

28 

29 

30class NetworkAnalyzerParams(BaseModel): 

31 """ 

32 Parameters for network analysis. 

33 

34 Context: Clear, minimal schema for type safety. 

35 """ 

36 target: str = Field( 

37 description="Target to analyze (IP, domain, or pcap file path)" 

38 ) 

39 safe_only: bool = Field( 

40 default=True, 

41 description="Only perform safe, non-intrusive analysis" 

42 ) 

43 max_findings: int = Field( 

44 default=10, 

45 description="Maximum number of findings to return (context efficiency)" 

46 ) 

47 

48 

49class NetworkAnalyzerTool(AlprinaToolBase[NetworkAnalyzerParams]): 

50 """ 

51 Network traffic and packet analysis tool. 

52 

53 Context Engineering Benefits: 

54 - Returns compressed summaries (not full packet dumps) 

55 - Configurable max_findings for context control 

56 - Async for composability with other tools 

57 - Optional CAI enhancement (not required) 

58 

59 Usage: 

60 ```python 

61 tool = NetworkAnalyzerTool() 

62 result = await tool.execute(NetworkAnalyzerParams( 

63 target="192.168.1.1", 

64 safe_only=True 

65 )) 

66 ``` 

67 """ 

68 

69 name: str = "NetworkAnalyzer" 

70 description: str = """Analyze network traffic patterns, protocols, and connections. 

71 

72Capabilities: 

73- Network traffic pattern analysis 

74- Protocol inspection (TCP/UDP/ICMP) 

75- Suspicious connection detection 

76- Port scan detection 

77- Network vulnerability identification 

78 

79Returns: High-level summary with key findings (not raw packet data)""" 

80 params: type[NetworkAnalyzerParams] = NetworkAnalyzerParams 

81 

82 def __init__(self, **kwargs): 

83 super().__init__(**kwargs) 

84 self._alprina_agent = None 

85 

86 def _get_alprina_agent(self): 

87 """ 

88 Get Alprina agent if available. 

89 

90 Context: Optional enhancement - tool works without CAI. 

91 """ 

92 if not CAI_AVAILABLE: 

93 return None 

94 

95 if self._alprina_agent is None: 

96 try: 

97 self._alprina_agent = get_agent_by_name("network_traffic_analyzer") 

98 logger.debug("CAI network analyzer initialized") 

99 except Exception as e: 

100 logger.debug(f"Alprina agent unavailable: {e}") 

101 return None 

102 

103 return self._alprina_agent 

104 

105 async def execute(self, params: NetworkAnalyzerParams) -> ToolOk | ToolError: 

106 """ 

107 Execute network analysis. 

108 

109 Context: Returns compressed findings, not verbose logs. 

110 """ 

111 logger.info(f"NetworkAnalyzer: {params.target} (safe_only={params.safe_only})") 

112 

113 try: 

114 # Try CAI-enhanced analysis first 

115 alprina_agent = self._get_alprina_agent() 

116 if alprina_agent: 

117 result = await self._analyze_with_cai(params, alprina_agent) 

118 else: 

119 result = await self._analyze_builtin(params) 

120 

121 # Limit findings for context efficiency 

122 if len(result["findings"]) > params.max_findings: 

123 result["findings"] = result["findings"][:params.max_findings] 

124 result["summary"]["truncated"] = True 

125 result["summary"]["total_found"] = len(result["findings"]) 

126 

127 return ToolOk(content=result) 

128 

129 except Exception as e: 

130 logger.error(f"Network analysis failed: {e}") 

131 return ToolError( 

132 message=f"Network analysis failed: {str(e)}", 

133 brief="Analysis failed" 

134 ) 

135 

136 async def _analyze_with_cai( 

137 self, 

138 params: NetworkAnalyzerParams, 

139 alprina_agent 

140 ) -> Dict[str, Any]: 

141 """ 

142 CAI-enhanced network analysis. 

143 

144 Context: Leverages CAI expertise when available. 

145 """ 

146 prompt = f"""Perform network traffic analysis on: {params.target} 

147 

148Focus areas: 

149- Traffic patterns and anomalies 

150- Protocol analysis (TCP/UDP/ICMP) 

151- Suspicious connections or behaviors 

152- Network vulnerabilities 

153- Port scanning activity 

154 

155Provide concise findings with severity levels.""" 

156 

157 messages = [{"role": "user", "content": prompt}] 

158 result = await alprina_agent.run(messages) 

159 

160 findings = self._parse_cai_response(result.value, params.target) 

161 

162 return { 

163 "target": params.target, 

164 "findings": findings, 

165 "summary": { 

166 "total_findings": len(findings), 

167 "powered_by": "CAI", 

168 "safe_mode": params.safe_only 

169 } 

170 } 

171 

172 async def _analyze_builtin( 

173 self, 

174 params: NetworkAnalyzerParams 

175 ) -> Dict[str, Any]: 

176 """ 

177 Built-in network analysis (fallback). 

178 

179 Context: Basic analysis when CAI unavailable. 

180 Works for common cases without external dependencies. 

181 """ 

182 findings = [] 

183 

184 # Built-in heuristics for network analysis 

185 target = params.target 

186 

187 # Check if target looks like IP address 

188 if self._is_ip_address(target): 

189 findings.append({ 

190 "type": "Network Configuration", 

191 "severity": "INFO", 

192 "title": "IP Address Target", 

193 "description": f"Analyzing IP address: {target}", 

194 "confidence": 1.0 

195 }) 

196 

197 # Check for common ports in target (if URL) 

198 if ":" in target: 

199 port = target.split(":")[-1] 

200 if port.isdigit(): 

201 port_num = int(port) 

202 if port_num in [22, 23, 3389]: # SSH, Telnet, RDP 

203 findings.append({ 

204 "type": "Sensitive Port", 

205 "severity": "MEDIUM", 

206 "title": f"Management Port Open: {port_num}", 

207 "description": f"Port {port_num} is typically used for remote management", 

208 "confidence": 0.8 

209 }) 

210 

211 # If no specific findings, return general assessment 

212 if not findings: 

213 findings.append({ 

214 "type": "Analysis Complete", 

215 "severity": "INFO", 

216 "title": "Network Analysis Complete", 

217 "description": f"Basic analysis of {target} complete. Enable CAI for deep inspection.", 

218 "confidence": 0.7 

219 }) 

220 

221 return { 

222 "target": params.target, 

223 "findings": findings, 

224 "summary": { 

225 "total_findings": len(findings), 

226 "powered_by": "built-in", 

227 "safe_mode": params.safe_only 

228 } 

229 } 

230 

231 def _parse_cai_response( 

232 self, 

233 response: str, 

234 target: str 

235 ) -> List[Dict[str, Any]]: 

236 """ 

237 Parse CAI response into structured findings. 

238 

239 Context: Extracts high-signal information, discards noise. 

240 """ 

241 findings = [] 

242 

243 # Patterns for severity levels 

244 patterns = [ 

245 ("CRITICAL", r"(?i)(critical|severe|urgent).*?(?=\n\n|\Z)"), 

246 ("HIGH", r"(?i)high.*?(?=\n\n|\Z)"), 

247 ("MEDIUM", r"(?i)(medium|moderate).*?(?=\n\n|\Z)"), 

248 ("LOW", r"(?i)(low|minor|info).*?(?=\n\n|\Z)") 

249 ] 

250 

251 for severity, pattern in patterns: 

252 matches = re.finditer(pattern, response, re.DOTALL) 

253 for match in matches: 

254 finding_text = match.group(0).strip() 

255 lines = finding_text.split('\n') 

256 title = lines[0] if lines else "Network Finding" 

257 

258 findings.append({ 

259 "type": "Network Issue", 

260 "severity": severity, 

261 "title": title[:100], # Truncate for context 

262 "description": finding_text[:300], # Limit description 

263 "confidence": 0.85 

264 }) 

265 

266 # If no structured findings, create summary 

267 if not findings and len(response) > 50: 

268 findings.append({ 

269 "type": "Analysis Summary", 

270 "severity": "INFO", 

271 "title": "Network Analysis Complete", 

272 "description": response[:400], # Compressed summary 

273 "confidence": 0.9 

274 }) 

275 

276 return findings 

277 

278 def _is_ip_address(self, target: str) -> bool: 

279 """Check if target looks like IP address""" 

280 parts = target.split(".") 

281 if len(parts) == 4: 

282 return all(p.isdigit() and 0 <= int(p) <= 255 for p in parts) 

283 return False