Coverage for src/alprina_cli/security_engine.py: 24%

139 statements  

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

1""" 

2Alprina Security Engine. 

3AI-powered vulnerability detection and security analysis. 

4Built on Alprina's proprietary security agent framework. 

5""" 

6 

7import os 

8import hashlib 

9import asyncio 

10from pathlib import Path 

11from typing import Dict, Any, List, Optional 

12from loguru import logger 

13 

14from .llm_provider import get_llm_client 

15 

16# Import Alprina security agents 

17try: 

18 from .agents.red_teamer import run_red_team_scan 

19 from .agents.blue_teamer import run_blue_team_scan 

20 from .agents.network_analyzer import run_network_analyzer_scan 

21 from .agents.reverse_engineer import run_reverse_engineer_scan 

22 from .agents.dfir import run_dfir_scan 

23 from .agents.android_sast import run_android_sast_scan 

24 from .agents.memory_analysis import run_memory_analysis_scan 

25 from .agents.wifi_security import run_wifi_security_scan 

26 from .agents.replay_attack import run_replay_attack_scan 

27 from .agents.subghz_sdr import run_subghz_sdr_scan 

28 from .agents.retester import run_retester_scan 

29 from .agents.mail import run_mail_scan 

30 from .agents.guardrails import run_guardrails_scan 

31 AGENTS_AVAILABLE = True 

32 logger.info("Alprina security engine initialized successfully") 

33except ImportError as e: 

34 AGENTS_AVAILABLE = False 

35 logger.error(f"Alprina agents not available: {e}") 

36 logger.warning("Using fallback LLM analysis methods") 

37 

38# Task to Alprina agent mapping 

39AGENT_MAPPING = { 

40 "offensive-security": run_red_team_scan, 

41 "defensive-security": run_blue_team_scan, 

42 "network-analysis": run_network_analyzer_scan, 

43 "binary-analysis": run_reverse_engineer_scan, 

44 "forensics": run_dfir_scan, 

45 "android-scan": run_android_sast_scan, 

46 "memory-forensics": run_memory_analysis_scan, 

47 "wifi-test": run_wifi_security_scan, 

48 "replay-check": run_replay_attack_scan, 

49 "radio-security": run_subghz_sdr_scan, 

50 "retest": run_retester_scan, 

51 "email-report": run_mail_scan, 

52 "safety-check": run_guardrails_scan, 

53 # Aliases for common tasks 

54 "code-audit": run_red_team_scan, 

55 "web-recon": run_network_analyzer_scan, 

56 "vuln-scan": run_red_team_scan, 

57 "secret-detection": run_red_team_scan, 

58 "config-audit": run_blue_team_scan 

59} 

60 

61 

62def run_agent(task: str, input_data: str, metadata: dict) -> dict: 

63 """ 

64 Run an Alprina security agent for vulnerability analysis. 

65 

66 Args: 

67 task: Task/agent type (e.g., 'code-audit', 'web-recon', 'vuln-scan') 

68 input_data: Input data for the agent 

69 metadata: Additional metadata for the scan 

70 

71 Returns: 

72 Dict containing scan results 

73 """ 

74 logger.info(f"Running Alprina agent: {task}") 

75 logger.debug(f"Input length: {len(input_data)} chars") 

76 logger.debug(f"Metadata: {metadata}") 

77 

78 # Use Alprina agents if available 

79 if AGENTS_AVAILABLE and task in AGENT_MAPPING: 

80 logger.info(f"Using Alprina agent for task: {task}") 

81 agent_func = AGENT_MAPPING[task] 

82 result = agent_func( 

83 target=metadata.get("path", input_data), 

84 safe_only=metadata.get("safe_only", True) 

85 ) 

86 return result 

87 

88 # Fallback to LLM-based analysis if agents not available 

89 logger.warning(f"Agent '{task}' not found in mapping, falling back to LLM analysis") 

90 return _run_llm_analysis(task, input_data, metadata) 

91 

92 

93def _run_llm_analysis(task: str, input_data: str, metadata: dict) -> dict: 

94 """ 

95 Build analysis prompt for Alprina agent. 

96 

97 Args: 

98 task: Task type 

99 content: Code/content to analyze 

100 metadata: Additional context 

101 

102 Returns: 

103 Formatted prompt string 

104 """ 

105 file_info = metadata.get("file", "unknown file") 

106 safe_only = metadata.get("safe_only", True) 

107 

108 prompts = { 

109 "code-audit": f"""Perform a comprehensive security audit of this code. 

110 

111File: {file_info} 

112Safe Mode: {safe_only} 

113 

114Code to analyze: 

115``` 

116{content[:5000]} # Limit to 5000 chars for efficiency 

117``` 

118 

119Please identify: 

1201. Security vulnerabilities (SQL injection, XSS, etc.) 

1212. Hardcoded secrets (API keys, passwords, tokens) 

1223. Insecure configurations 

1234. Authentication/authorization issues 

1245. Input validation problems 

1256. Cryptographic weaknesses 

126 

127For each finding, provide: 

128- Severity (HIGH/MEDIUM/LOW) 

129- Type/Category 

130- Description 

131- Location (line number if possible) 

132- Remediation steps 

133 

134Format your response as a structured analysis.""", 

135 

136 "secret-detection": f"""Scan this code for hardcoded secrets and sensitive information. 

137 

138File: {file_info} 

139 

140Code: 

141``` 

142{content[:5000]} 

143``` 

144 

145Look for: 

146- API keys 

147- Passwords 

148- Tokens 

149- Private keys 

150- Database credentials 

151- AWS/Cloud credentials 

152 

153Report each finding with severity and location.""", 

154 

155 "config-audit": f"""Audit this configuration file for security issues. 

156 

157File: {file_info} 

158 

159Configuration: 

160``` 

161{content[:5000]} 

162``` 

163 

164Check for: 

165- Insecure settings 

166- Default credentials 

167- Exposed secrets 

168- Weak permissions 

169- Security misconfigurations 

170 

171Provide remediation for each issue.""" 

172 } 

173 

174 return prompts.get(task, prompts["code-audit"]) 

175 

176 

177def _parse_alprina_result(result: str, metadata: dict) -> List[Dict[str, Any]]: 

178 """ 

179 Parse Alprina agent result into structured findings. 

180 

181 Args: 

182 result: Agent response string 

183 metadata: Original metadata 

184 

185 Returns: 

186 List of finding dictionaries 

187 """ 

188 findings = [] 

189 

190 # Simple parsing - look for severity keywords and extract findings 

191 # This is a basic parser - can be enhanced based on actual Alprina agent output format 

192 

193 lines = result.split('\n') 

194 current_finding = None 

195 

196 for line in lines: 

197 line_lower = line.lower() 

198 

199 # Check for severity markers 

200 if any(sev in line_lower for sev in ['high', 'critical', 'severe']): 

201 if current_finding: 

202 findings.append(current_finding) 

203 current_finding = { 

204 "severity": "HIGH", 

205 "type": "Security Issue", 

206 "description": line.strip(), 

207 "location": metadata.get("file", "unknown"), 

208 "line": None 

209 } 

210 elif any(sev in line_lower for sev in ['medium', 'moderate']): 

211 if current_finding: 

212 findings.append(current_finding) 

213 current_finding = { 

214 "severity": "MEDIUM", 

215 "type": "Security Issue", 

216 "description": line.strip(), 

217 "location": metadata.get("file", "unknown"), 

218 "line": None 

219 } 

220 elif any(sev in line_lower for sev in ['low', 'minor', 'info']): 

221 if current_finding: 

222 findings.append(current_finding) 

223 current_finding = { 

224 "severity": "LOW", 

225 "type": "Security Issue", 

226 "description": line.strip(), 

227 "location": metadata.get("file", "unknown"), 

228 "line": None 

229 } 

230 elif current_finding and line.strip(): 

231 # Add to current finding description 

232 current_finding["description"] += " " + line.strip() 

233 

234 if current_finding: 

235 findings.append(current_finding) 

236 

237 # If no findings parsed, create a summary finding 

238 if not findings and len(result) > 50: 

239 findings.append({ 

240 "severity": "INFO", 

241 "type": "Analysis Complete", 

242 "description": result[:500], # First 500 chars 

243 "location": metadata.get("file", "unknown"), 

244 "line": None 

245 }) 

246 

247 return findings 

248 

249 

250def _run_llm_analysis(task: str, input_data: str, metadata: dict) -> dict: 

251 """ 

252 Fallback LLM-based analysis when CAI not available. 

253 

254 Args: 

255 task: Analysis task 

256 input_data: Content to analyze 

257 metadata: Additional context 

258 

259 Returns: 

260 Dict with findings 

261 """ 

262 logger.info("Using LLM fallback analysis") 

263 

264 try: 

265 llm_client = get_llm_client() 

266 

267 if task in ["code-audit", "secret-detection", "config-audit"]: 

268 filename = metadata.get("file", "unknown") 

269 result = llm_client.analyze_code(input_data, filename, task) 

270 return result 

271 else: 

272 # For other tasks, use basic pattern matching 

273 return _pattern_based_analysis(input_data, metadata) 

274 

275 except Exception as e: 

276 logger.error(f"LLM analysis error: {e}") 

277 return _pattern_based_analysis(input_data, metadata) 

278 

279 

280def _pattern_based_analysis(content: str, metadata: dict) -> dict: 

281 """ 

282 Basic pattern-based security analysis as last resort fallback. 

283 

284 Args: 

285 content: Content to analyze 

286 metadata: Context metadata 

287 

288 Returns: 

289 Dict with findings 

290 """ 

291 findings = [] 

292 

293 # Pattern-based detection 

294 patterns = { 

295 "Hardcoded Secret": [ 

296 (r"password\s*=\s*['\"][^'\"]+['\"]", "HIGH"), 

297 (r"api_key\s*=\s*['\"][^'\"]+['\"]", "HIGH"), 

298 (r"secret\s*=\s*['\"][^'\"]+['\"]", "HIGH"), 

299 (r"token\s*=\s*['\"][^'\"]+['\"]", "MEDIUM"), 

300 ], 

301 "Debug Mode": [ 

302 (r"debug\s*=\s*true", "MEDIUM"), 

303 (r"DEBUG\s*=\s*True", "MEDIUM"), 

304 ], 

305 "SQL Injection Risk": [ 

306 (r"execute\(['\"].*%s.*['\"]", "HIGH"), 

307 (r"execute\(['\"].*\+.*['\"]", "HIGH"), 

308 ], 

309 "Insecure Function": [ 

310 (r"eval\(", "HIGH"), 

311 (r"exec\(", "HIGH"), 

312 ] 

313 } 

314 

315 import re 

316 

317 for vuln_type, pattern_list in patterns.items(): 

318 for pattern, severity in pattern_list: 

319 matches = re.finditer(pattern, content, re.IGNORECASE) 

320 for match in matches: 

321 findings.append({ 

322 "severity": severity, 

323 "type": vuln_type, 

324 "description": f"Pattern detected: {match.group(0)[:100]}", 

325 "location": metadata.get("file", "unknown"), 

326 "line": content[:match.start()].count('\n') + 1 

327 }) 

328 

329 # Environment file check 

330 if ".env" in metadata.get("file", ""): 

331 findings.append({ 

332 "severity": "LOW", 

333 "type": "Environment File", 

334 "description": "Environment file detected - ensure it's in .gitignore", 

335 "location": metadata.get("file", "unknown"), 

336 "line": None 

337 }) 

338 

339 return { 

340 "findings": findings, 

341 "metadata": metadata, 

342 "alprina_enabled": False, 

343 "analysis_method": "pattern_based" 

344 } 

345 

346 

347def run_local_scan(path: str, profile: str = "code-audit", safe_only: bool = True) -> dict: 

348 """ 

349 Scan local files/directories for security issues using Alprina agents. 

350 

351 Args: 

352 path: Path to file or directory 

353 profile: Scan profile to use 

354 safe_only: Only run safe, non-intrusive checks 

355 

356 Returns: 

357 Dict containing scan results 

358 """ 

359 logger.info(f"Starting local scan: {path} (Engine: {'active' if AGENTS_AVAILABLE else 'fallback'})") 

360 

361 target_path = Path(path) 

362 

363 if not target_path.exists(): 

364 raise FileNotFoundError(f"Path does not exist: {path}") 

365 

366 # Collect files to scan 

367 if target_path.is_file(): 

368 files = [target_path] 

369 else: 

370 files = _collect_scannable_files(target_path) 

371 

372 logger.info(f"Found {len(files)} files to scan") 

373 

374 results = { 

375 "mode": "local", 

376 "target": str(path), 

377 "profile": profile, 

378 "files_scanned": len(files), 

379 "findings": [], 

380 "alprina_engine": "active" if AGENTS_AVAILABLE else "fallback" 

381 } 

382 

383 # Scan each file 

384 for i, file_path in enumerate(files, 1): 

385 try: 

386 logger.info(f"Scanning file {i}/{len(files)}: {file_path.name}") 

387 file_results = _scan_file(file_path, profile, safe_only) 

388 results["findings"].extend(file_results) 

389 except Exception as e: 

390 logger.error(f"Error scanning {file_path}: {e}") 

391 

392 logger.info(f"Scan complete: {len(results['findings'])} findings") 

393 return results 

394 

395 

396def run_remote_scan(target: str, profile: str = "web-recon", safe_only: bool = True) -> dict: 

397 """ 

398 Scan remote target (URL, domain, or IP) using Alprina security agents. 

399 

400 Args: 

401 target: Target URL, domain, or IP 

402 profile: Scan profile to use 

403 safe_only: Only run safe, non-intrusive checks 

404 

405 Returns: 

406 Dict containing scan results 

407 """ 

408 logger.info(f"Starting remote scan: {target} (Engine: {'active' if AGENTS_AVAILABLE else 'fallback'})") 

409 

410 # Use Alprina security agent for remote scanning 

411 results = run_agent( 

412 task=profile, 

413 input_data=target, 

414 metadata={ 

415 "safe_only": safe_only, 

416 "mode": "remote" 

417 } 

418 ) 

419 

420 return { 

421 "mode": "remote", 

422 "target": target, 

423 "profile": profile, 

424 "alprina_engine": "active" if AGENTS_AVAILABLE else "fallback", 

425 **results 

426 } 

427 

428 

429def _collect_scannable_files(directory: Path) -> List[Path]: 

430 """ 

431 Collect files that should be scanned for security issues. 

432 """ 

433 scannable_extensions = ( 

434 ".py", ".js", ".ts", ".jsx", ".tsx", ".java", ".go", ".rs", 

435 ".env", ".yaml", ".yml", ".json", ".xml", ".ini", ".conf", 

436 ".sh", ".bash", ".zsh", ".dockerfile", ".tf", ".hcl", ".php", 

437 ".rb", ".c", ".cpp", ".h", ".cs", ".swift", ".kt" 

438 ) 

439 

440 scannable_names = ( 

441 "Dockerfile", "Makefile", "docker-compose.yml", "docker-compose.yaml", 

442 ".env", ".env.local", ".env.production", ".env.development", 

443 "config.json", "settings.json", "secrets.json" 

444 ) 

445 

446 files = [] 

447 

448 for item in directory.rglob("*"): 

449 if item.is_file(): 

450 # Check by extension or name 

451 if item.suffix.lower() in scannable_extensions or item.name in scannable_names: 

452 # Skip common directories 

453 if not any(part.startswith(".") or part in ["node_modules", "venv", "__pycache__", "dist", "build", "target"] 

454 for part in item.parts): 

455 files.append(item) 

456 

457 return files 

458 

459 

460def _scan_file(file_path: Path, profile: str, safe_only: bool) -> List[Dict[str, Any]]: 

461 """ 

462 Scan a single file for security issues using Alprina agents. 

463 """ 

464 findings = [] 

465 

466 try: 

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

468 file_hash = hashlib.md5(content.encode()).hexdigest() 

469 

470 # Run Alprina agent on file content 

471 result = run_agent( 

472 task=profile, 

473 input_data=content, 

474 metadata={ 

475 "file": str(file_path), 

476 "hash": file_hash, 

477 "safe_only": safe_only, 

478 "file_type": file_path.suffix 

479 } 

480 ) 

481 

482 # Extract findings from result 

483 if "findings" in result: 

484 for finding in result["findings"]: 

485 finding["location"] = str(file_path) 

486 findings.append(finding) 

487 

488 except Exception as e: 

489 logger.error(f"Error scanning file {file_path}: {e}") 

490 

491 return findings