Coverage for src/alprina_cli/api/routes/scan.py: 17%
221 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-14 11:27 +0100
1"""
2Scan endpoints - /v1/scan/*
3"""
5from fastapi import APIRouter, HTTPException
6from typing import Dict, Any
7import time
8import hashlib
10from ..schemas.scan import CodeScanRequest, TargetScanRequest, ScanResponse, Finding, ScanSummary
11from ...security_engine import run_agent, AGENTS_AVAILABLE
13router = APIRouter()
16@router.post("/scan/code", response_model=ScanResponse)
17async def scan_code(request: CodeScanRequest):
18 """
19 Scan source code for security vulnerabilities.
21 Analyzes the provided code using Alprina's AI-powered security agents
22 to detect vulnerabilities, hardcoded secrets, insecure configurations,
23 and other security issues.
25 **Example:**
26 ```python
27 import requests
29 response = requests.post(
30 "http://localhost:8000/v1/scan/code",
31 json={
32 "code": "API_KEY = 'sk-1234567890'",
33 "language": "python",
34 "profile": "code-audit"
35 }
36 )
37 ```
38 """
39 start_time = time.time()
41 try:
42 # Generate scan ID
43 scan_id = f"scan_{hashlib.md5(request.code.encode()).hexdigest()[:10]}"
45 # Prepare metadata
46 metadata = request.metadata or {}
47 metadata.update({
48 "language": request.language,
49 "profile": request.profile,
50 "safe_only": request.safe_only
51 })
53 # Run security agent
54 results = run_agent(
55 task=request.profile,
56 input_data=request.code,
57 metadata=metadata
58 )
60 # Convert findings to schema
61 findings_list = []
62 for i, finding in enumerate(results.get("findings", []), 1):
63 findings_list.append(
64 Finding(
65 id=f"{scan_id}_finding_{i}",
66 severity=finding.get("severity", "INFO"),
67 type=finding.get("type", "Unknown"),
68 title=finding.get("title", finding.get("type", "Security Finding")),
69 description=finding.get("description", ""),
70 location=finding.get("location"),
71 line=finding.get("line"),
72 confidence=finding.get("confidence")
73 )
74 )
76 # Calculate summary
77 summary = ScanSummary(
78 total_findings=len(findings_list),
79 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
80 high=sum(1 for f in findings_list if f.severity == "HIGH"),
81 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
82 low=sum(1 for f in findings_list if f.severity == "LOW"),
83 info=sum(1 for f in findings_list if f.severity == "INFO")
84 )
86 duration_ms = int((time.time() - start_time) * 1000)
88 return ScanResponse(
89 scan_id=scan_id,
90 status="completed",
91 scanned_by="Alprina Security Engine",
92 alprina_engine=results.get("alprina_engine", "active" if AGENTS_AVAILABLE else "fallback"),
93 findings=findings_list,
94 summary=summary,
95 duration_ms=duration_ms
96 )
98 except Exception as e:
99 raise HTTPException(
100 status_code=500,
101 detail=f"Scan failed: {str(e)}"
102 )
105@router.post("/scan/red-team", response_model=ScanResponse)
106async def red_team_scan(request: TargetScanRequest):
107 """
108 Run offensive security red team scan.
110 Performs attack simulation and penetration testing to identify
111 exploitable vulnerabilities and attack vectors.
112 """
113 start_time = time.time()
115 try:
116 from ...agents.red_teamer import run_red_team_scan
118 scan_id = f"scan_redteam_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
119 results = run_red_team_scan(request.target, safe_only=request.safe_only)
121 findings_list = []
122 for i, finding in enumerate(results.get("findings", []), 1):
123 findings_list.append(
124 Finding(
125 id=f"{scan_id}_finding_{i}",
126 severity=finding.get("severity", "INFO"),
127 type=finding.get("type", "Attack Vector"),
128 title=finding.get("title", "Red Team Finding"),
129 description=finding.get("description", ""),
130 location=finding.get("file"),
131 line=finding.get("line"),
132 confidence=finding.get("confidence")
133 )
134 )
136 summary = ScanSummary(
137 total_findings=len(findings_list),
138 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
139 high=sum(1 for f in findings_list if f.severity == "HIGH"),
140 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
141 low=sum(1 for f in findings_list if f.severity == "LOW"),
142 info=sum(1 for f in findings_list if f.severity == "INFO")
143 )
145 duration_ms = int((time.time() - start_time) * 1000)
147 return ScanResponse(
148 scan_id=scan_id,
149 status="completed",
150 scanned_by="Red Team Agent",
151 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
152 findings=findings_list,
153 summary=summary,
154 duration_ms=duration_ms
155 )
156 except Exception as e:
157 raise HTTPException(status_code=500, detail=f"Red team scan failed: {str(e)}")
160@router.post("/scan/blue-team", response_model=ScanResponse)
161async def blue_team_scan(request: TargetScanRequest):
162 """
163 Run defensive security blue team assessment.
165 Evaluates security posture, validates defenses, and identifies
166 gaps in monitoring and threat detection capabilities.
167 """
168 start_time = time.time()
170 try:
171 from ...agents.blue_teamer import run_blue_team_scan
173 scan_id = f"scan_blueteam_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
174 results = run_blue_team_scan(request.target, safe_only=request.safe_only)
176 findings_list = []
177 for i, finding in enumerate(results.get("findings", []), 1):
178 findings_list.append(
179 Finding(
180 id=f"{scan_id}_finding_{i}",
181 severity=finding.get("severity", "INFO"),
182 type=finding.get("type", "Defense Gap"),
183 title=finding.get("title", "Blue Team Finding"),
184 description=finding.get("description", ""),
185 location=finding.get("file"),
186 line=finding.get("line"),
187 confidence=finding.get("confidence")
188 )
189 )
191 summary = ScanSummary(
192 total_findings=len(findings_list),
193 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
194 high=sum(1 for f in findings_list if f.severity == "HIGH"),
195 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
196 low=sum(1 for f in findings_list if f.severity == "LOW"),
197 info=sum(1 for f in findings_list if f.severity == "INFO")
198 )
200 duration_ms = int((time.time() - start_time) * 1000)
202 return ScanResponse(
203 scan_id=scan_id,
204 status="completed",
205 scanned_by="Blue Team Agent",
206 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
207 findings=findings_list,
208 summary=summary,
209 duration_ms=duration_ms
210 )
211 except Exception as e:
212 raise HTTPException(status_code=500, detail=f"Blue team scan failed: {str(e)}")
215@router.post("/scan/network-analysis", response_model=ScanResponse)
216async def network_analysis_scan(request: TargetScanRequest):
217 """
218 Run network traffic analysis and packet inspection.
220 Analyzes network traffic patterns, protocol security, and
221 identifies suspicious connections and data exfiltration risks.
222 """
223 start_time = time.time()
225 try:
226 from ...agents.network_analyzer import run_network_analyzer_scan
228 scan_id = f"scan_network_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
229 results = run_network_analyzer_scan(request.target, safe_only=request.safe_only)
231 findings_list = []
232 for i, finding in enumerate(results.get("findings", []), 1):
233 findings_list.append(
234 Finding(
235 id=f"{scan_id}_finding_{i}",
236 severity=finding.get("severity", "INFO"),
237 type=finding.get("type", "Network Issue"),
238 title=finding.get("title", "Network Finding"),
239 description=finding.get("description", ""),
240 location=finding.get("file"),
241 line=finding.get("line"),
242 confidence=finding.get("confidence")
243 )
244 )
246 summary = ScanSummary(
247 total_findings=len(findings_list),
248 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
249 high=sum(1 for f in findings_list if f.severity == "HIGH"),
250 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
251 low=sum(1 for f in findings_list if f.severity == "LOW"),
252 info=sum(1 for f in findings_list if f.severity == "INFO")
253 )
255 duration_ms = int((time.time() - start_time) * 1000)
257 return ScanResponse(
258 scan_id=scan_id,
259 status="completed",
260 scanned_by="Network Traffic Analyzer",
261 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
262 findings=findings_list,
263 summary=summary,
264 duration_ms=duration_ms
265 )
266 except Exception as e:
267 raise HTTPException(status_code=500, detail=f"Network analysis scan failed: {str(e)}")
270@router.post("/scan/reverse-engineering", response_model=ScanResponse)
271async def reverse_engineering_scan(request: TargetScanRequest):
272 """
273 Run binary analysis and reverse engineering scan.
275 Performs decompilation, binary analysis, malware detection,
276 and identifies backdoors and obfuscation techniques.
277 """
278 start_time = time.time()
280 try:
281 from ...agents.reverse_engineer import run_reverse_engineer_scan
283 scan_id = f"scan_reverse_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
284 results = run_reverse_engineer_scan(request.target, safe_only=request.safe_only)
286 findings_list = []
287 for i, finding in enumerate(results.get("findings", []), 1):
288 findings_list.append(
289 Finding(
290 id=f"{scan_id}_finding_{i}",
291 severity=finding.get("severity", "INFO"),
292 type=finding.get("type", "Binary Issue"),
293 title=finding.get("title", "Reverse Engineering Finding"),
294 description=finding.get("description", ""),
295 location=finding.get("file"),
296 line=finding.get("line"),
297 confidence=finding.get("confidence")
298 )
299 )
301 summary = ScanSummary(
302 total_findings=len(findings_list),
303 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
304 high=sum(1 for f in findings_list if f.severity == "HIGH"),
305 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
306 low=sum(1 for f in findings_list if f.severity == "LOW"),
307 info=sum(1 for f in findings_list if f.severity == "INFO")
308 )
310 duration_ms = int((time.time() - start_time) * 1000)
312 return ScanResponse(
313 scan_id=scan_id,
314 status="completed",
315 scanned_by="Reverse Engineering Agent",
316 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
317 findings=findings_list,
318 summary=summary,
319 duration_ms=duration_ms
320 )
321 except Exception as e:
322 raise HTTPException(status_code=500, detail=f"Reverse engineering scan failed: {str(e)}")
325@router.post("/scan/forensics", response_model=ScanResponse)
326async def forensics_scan(request: TargetScanRequest):
327 """
328 Run digital forensics and incident response (DFIR) scan.
330 Performs forensic analysis, evidence collection, timeline reconstruction,
331 and identifies indicators of compromise.
332 """
333 start_time = time.time()
335 try:
336 from ...agents.dfir import run_dfir_scan
338 scan_id = f"scan_dfir_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
339 results = run_dfir_scan(request.target, safe_only=request.safe_only)
341 findings_list = []
342 for i, finding in enumerate(results.get("findings", []), 1):
343 findings_list.append(
344 Finding(
345 id=f"{scan_id}_finding_{i}",
346 severity=finding.get("severity", "INFO"),
347 type=finding.get("type", "Forensic Evidence"),
348 title=finding.get("title", "DFIR Finding"),
349 description=finding.get("description", ""),
350 location=finding.get("file"),
351 line=finding.get("line"),
352 confidence=finding.get("confidence")
353 )
354 )
356 summary = ScanSummary(
357 total_findings=len(findings_list),
358 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
359 high=sum(1 for f in findings_list if f.severity == "HIGH"),
360 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
361 low=sum(1 for f in findings_list if f.severity == "LOW"),
362 info=sum(1 for f in findings_list if f.severity == "INFO")
363 )
365 duration_ms = int((time.time() - start_time) * 1000)
367 return ScanResponse(
368 scan_id=scan_id,
369 status="completed",
370 scanned_by="DFIR Agent",
371 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
372 findings=findings_list,
373 summary=summary,
374 duration_ms=duration_ms
375 )
376 except Exception as e:
377 raise HTTPException(status_code=500, detail=f"DFIR scan failed: {str(e)}")
380@router.post("/scan/android", response_model=ScanResponse)
381async def android_scan(request: TargetScanRequest):
382 """
383 Run Android application security testing.
385 Analyzes Android APK files for security vulnerabilities, dangerous
386 permissions, insecure data storage, and network security issues.
387 """
388 start_time = time.time()
390 try:
391 from ...agents.android_sast import run_android_sast_scan
393 scan_id = f"scan_android_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
394 results = run_android_sast_scan(request.target, safe_only=request.safe_only)
396 findings_list = []
397 for i, finding in enumerate(results.get("findings", []), 1):
398 findings_list.append(
399 Finding(
400 id=f"{scan_id}_finding_{i}",
401 severity=finding.get("severity", "INFO"),
402 type=finding.get("type", "Mobile Security Issue"),
403 title=finding.get("title", "Android Finding"),
404 description=finding.get("description", ""),
405 location=finding.get("file"),
406 line=finding.get("line"),
407 confidence=finding.get("confidence")
408 )
409 )
411 summary = ScanSummary(
412 total_findings=len(findings_list),
413 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
414 high=sum(1 for f in findings_list if f.severity == "HIGH"),
415 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
416 low=sum(1 for f in findings_list if f.severity == "LOW"),
417 info=sum(1 for f in findings_list if f.severity == "INFO")
418 )
420 duration_ms = int((time.time() - start_time) * 1000)
422 return ScanResponse(
423 scan_id=scan_id,
424 status="completed",
425 scanned_by="Android SAST Agent",
426 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
427 findings=findings_list,
428 summary=summary,
429 duration_ms=duration_ms
430 )
431 except Exception as e:
432 raise HTTPException(status_code=500, detail=f"Android scan failed: {str(e)}")
435@router.post("/scan/memory-analysis", response_model=ScanResponse)
436async def memory_analysis_scan(request: TargetScanRequest):
437 """
438 Run memory forensics and memory-based attack detection.
440 Analyzes memory dumps for forensic evidence, credential extraction,
441 and memory-resident malware.
442 """
443 start_time = time.time()
445 try:
446 from ...agents.memory_analysis import run_memory_analysis_scan
448 scan_id = f"scan_memory_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
449 results = run_memory_analysis_scan(request.target, safe_only=request.safe_only)
451 findings_list = []
452 for i, finding in enumerate(results.get("findings", []), 1):
453 findings_list.append(
454 Finding(
455 id=f"{scan_id}_finding_{i}",
456 severity=finding.get("severity", "INFO"),
457 type=finding.get("type", "Memory Issue"),
458 title=finding.get("title", "Memory Analysis Finding"),
459 description=finding.get("description", ""),
460 location=finding.get("file"),
461 line=finding.get("line"),
462 confidence=finding.get("confidence")
463 )
464 )
466 summary = ScanSummary(
467 total_findings=len(findings_list),
468 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
469 high=sum(1 for f in findings_list if f.severity == "HIGH"),
470 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
471 low=sum(1 for f in findings_list if f.severity == "LOW"),
472 info=sum(1 for f in findings_list if f.severity == "INFO")
473 )
475 duration_ms = int((time.time() - start_time) * 1000)
477 return ScanResponse(
478 scan_id=scan_id,
479 status="completed",
480 scanned_by="Memory Analysis Agent",
481 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
482 findings=findings_list,
483 summary=summary,
484 duration_ms=duration_ms
485 )
486 except Exception as e:
487 raise HTTPException(status_code=500, detail=f"Memory analysis scan failed: {str(e)}")
490@router.post("/scan/wifi-security", response_model=ScanResponse)
491async def wifi_security_scan(request: TargetScanRequest):
492 """
493 Run wireless network security testing.
495 Tests WiFi network security, encryption analysis, access point
496 security, and wireless protocol vulnerabilities.
497 """
498 start_time = time.time()
500 try:
501 from ...agents.wifi_security import run_wifi_security_scan
503 scan_id = f"scan_wifi_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
504 results = run_wifi_security_scan(request.target, safe_only=request.safe_only)
506 findings_list = []
507 for i, finding in enumerate(results.get("findings", []), 1):
508 findings_list.append(
509 Finding(
510 id=f"{scan_id}_finding_{i}",
511 severity=finding.get("severity", "INFO"),
512 type=finding.get("type", "WiFi Security Issue"),
513 title=finding.get("title", "WiFi Finding"),
514 description=finding.get("description", ""),
515 location=finding.get("file"),
516 line=finding.get("line"),
517 confidence=finding.get("confidence")
518 )
519 )
521 summary = ScanSummary(
522 total_findings=len(findings_list),
523 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
524 high=sum(1 for f in findings_list if f.severity == "HIGH"),
525 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
526 low=sum(1 for f in findings_list if f.severity == "LOW"),
527 info=sum(1 for f in findings_list if f.severity == "INFO")
528 )
530 duration_ms = int((time.time() - start_time) * 1000)
532 return ScanResponse(
533 scan_id=scan_id,
534 status="completed",
535 scanned_by="WiFi Security Tester",
536 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
537 findings=findings_list,
538 summary=summary,
539 duration_ms=duration_ms
540 )
541 except Exception as e:
542 raise HTTPException(status_code=500, detail=f"WiFi security scan failed: {str(e)}")
545@router.post("/scan/replay-attack", response_model=ScanResponse)
546async def replay_attack_scan(request: TargetScanRequest):
547 """
548 Run replay attack detection and session security testing.
550 Tests for replay attack vulnerabilities, session security issues,
551 token validation, and nonce implementation.
552 """
553 start_time = time.time()
555 try:
556 from ...agents.replay_attack import run_replay_attack_scan
558 scan_id = f"scan_replay_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
559 results = run_replay_attack_scan(request.target, safe_only=request.safe_only)
561 findings_list = []
562 for i, finding in enumerate(results.get("findings", []), 1):
563 findings_list.append(
564 Finding(
565 id=f"{scan_id}_finding_{i}",
566 severity=finding.get("severity", "INFO"),
567 type=finding.get("type", "Replay Vulnerability"),
568 title=finding.get("title", "Replay Attack Finding"),
569 description=finding.get("description", ""),
570 location=finding.get("file"),
571 line=finding.get("line"),
572 confidence=finding.get("confidence")
573 )
574 )
576 summary = ScanSummary(
577 total_findings=len(findings_list),
578 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
579 high=sum(1 for f in findings_list if f.severity == "HIGH"),
580 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
581 low=sum(1 for f in findings_list if f.severity == "LOW"),
582 info=sum(1 for f in findings_list if f.severity == "INFO")
583 )
585 duration_ms = int((time.time() - start_time) * 1000)
587 return ScanResponse(
588 scan_id=scan_id,
589 status="completed",
590 scanned_by="Replay Attack Agent",
591 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
592 findings=findings_list,
593 summary=summary,
594 duration_ms=duration_ms
595 )
596 except Exception as e:
597 raise HTTPException(status_code=500, detail=f"Replay attack scan failed: {str(e)}")
600@router.post("/scan/radio-security", response_model=ScanResponse)
601async def radio_security_scan(request: TargetScanRequest):
602 """
603 Run Software Defined Radio (SDR) and RF security analysis.
605 Analyzes radio frequency security, IoT wireless protocols,
606 and Sub-GHz communication vulnerabilities.
607 """
608 start_time = time.time()
610 try:
611 from ...agents.subghz_sdr import run_subghz_sdr_scan
613 scan_id = f"scan_radio_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
614 results = run_subghz_sdr_scan(request.target, safe_only=request.safe_only)
616 findings_list = []
617 for i, finding in enumerate(results.get("findings", []), 1):
618 findings_list.append(
619 Finding(
620 id=f"{scan_id}_finding_{i}",
621 severity=finding.get("severity", "INFO"),
622 type=finding.get("type", "RF Security Issue"),
623 title=finding.get("title", "Radio Security Finding"),
624 description=finding.get("description", ""),
625 location=finding.get("file"),
626 line=finding.get("line"),
627 confidence=finding.get("confidence")
628 )
629 )
631 summary = ScanSummary(
632 total_findings=len(findings_list),
633 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
634 high=sum(1 for f in findings_list if f.severity == "HIGH"),
635 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
636 low=sum(1 for f in findings_list if f.severity == "LOW"),
637 info=sum(1 for f in findings_list if f.severity == "INFO")
638 )
640 duration_ms = int((time.time() - start_time) * 1000)
642 return ScanResponse(
643 scan_id=scan_id,
644 status="completed",
645 scanned_by="Sub-GHz SDR Agent",
646 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
647 findings=findings_list,
648 summary=summary,
649 duration_ms=duration_ms
650 )
651 except Exception as e:
652 raise HTTPException(status_code=500, detail=f"Radio security scan failed: {str(e)}")
655@router.post("/scan/retest", response_model=ScanResponse)
656async def retest_scan(request: TargetScanRequest):
657 """
658 Run vulnerability retesting and fix validation.
660 Re-tests previously identified vulnerabilities to verify fixes,
661 performs regression testing, and validates remediation efforts.
662 """
663 start_time = time.time()
665 try:
666 from ...agents.retester import run_retester_scan
668 scan_id = f"scan_retest_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
669 results = run_retester_scan(request.target, safe_only=request.safe_only)
671 findings_list = []
672 for i, finding in enumerate(results.get("findings", []), 1):
673 findings_list.append(
674 Finding(
675 id=f"{scan_id}_finding_{i}",
676 severity=finding.get("severity", "INFO"),
677 type=finding.get("type", "Retest Result"),
678 title=finding.get("title", "Retest Finding"),
679 description=finding.get("description", ""),
680 location=finding.get("file"),
681 line=finding.get("line"),
682 confidence=finding.get("confidence")
683 )
684 )
686 summary = ScanSummary(
687 total_findings=len(findings_list),
688 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
689 high=sum(1 for f in findings_list if f.severity == "HIGH"),
690 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
691 low=sum(1 for f in findings_list if f.severity == "LOW"),
692 info=sum(1 for f in findings_list if f.severity == "INFO")
693 )
695 duration_ms = int((time.time() - start_time) * 1000)
697 return ScanResponse(
698 scan_id=scan_id,
699 status="completed",
700 scanned_by="Retester Agent",
701 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
702 findings=findings_list,
703 summary=summary,
704 duration_ms=duration_ms
705 )
706 except Exception as e:
707 raise HTTPException(status_code=500, detail=f"Retest scan failed: {str(e)}")
710@router.post("/scan/email-report", response_model=ScanResponse)
711async def email_report_scan(request: TargetScanRequest):
712 """
713 Generate and send email security reports.
715 Creates automated security reports with findings and sends them
716 via email to specified recipients for notification and alerting.
717 """
718 start_time = time.time()
720 try:
721 from ...agents.mail import run_mail_scan
723 scan_id = f"scan_email_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
724 results = run_mail_scan(request.target, safe_only=request.safe_only)
726 findings_list = []
727 for i, finding in enumerate(results.get("findings", []), 1):
728 findings_list.append(
729 Finding(
730 id=f"{scan_id}_finding_{i}",
731 severity=finding.get("severity", "INFO"),
732 type=finding.get("type", "Email Report"),
733 title=finding.get("title", "Email Finding"),
734 description=finding.get("description", ""),
735 location=finding.get("file"),
736 line=finding.get("line"),
737 confidence=finding.get("confidence")
738 )
739 )
741 summary = ScanSummary(
742 total_findings=len(findings_list),
743 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
744 high=sum(1 for f in findings_list if f.severity == "HIGH"),
745 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
746 low=sum(1 for f in findings_list if f.severity == "LOW"),
747 info=sum(1 for f in findings_list if f.severity == "INFO")
748 )
750 duration_ms = int((time.time() - start_time) * 1000)
752 return ScanResponse(
753 scan_id=scan_id,
754 status="completed",
755 scanned_by="Mail Agent",
756 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
757 findings=findings_list,
758 summary=summary,
759 duration_ms=duration_ms
760 )
761 except Exception as e:
762 raise HTTPException(status_code=500, detail=f"Email report generation failed: {str(e)}")
765@router.post("/scan/safety-check", response_model=ScanResponse)
766async def safety_check_scan(request: TargetScanRequest):
767 """
768 Run pre-scan safety validation and guardrails check.
770 Validates scan safety, performs risk assessment, checks permissions,
771 and ensures scans won't cause harm before execution.
772 """
773 start_time = time.time()
775 try:
776 from ...agents.guardrails import run_guardrails_scan
778 scan_id = f"scan_safety_{hashlib.md5(request.target.encode()).hexdigest()[:10]}"
779 results = run_guardrails_scan(request.target, safe_only=request.safe_only)
781 findings_list = []
782 for i, finding in enumerate(results.get("findings", []), 1):
783 findings_list.append(
784 Finding(
785 id=f"{scan_id}_finding_{i}",
786 severity=finding.get("severity", "INFO"),
787 type=finding.get("type", "Safety Check"),
788 title=finding.get("title", "Guardrails Finding"),
789 description=finding.get("description", ""),
790 location=finding.get("file"),
791 line=finding.get("line"),
792 confidence=finding.get("confidence")
793 )
794 )
796 summary = ScanSummary(
797 total_findings=len(findings_list),
798 critical=sum(1 for f in findings_list if f.severity == "CRITICAL"),
799 high=sum(1 for f in findings_list if f.severity == "HIGH"),
800 medium=sum(1 for f in findings_list if f.severity == "MEDIUM"),
801 low=sum(1 for f in findings_list if f.severity == "LOW"),
802 info=sum(1 for f in findings_list if f.severity == "INFO")
803 )
805 duration_ms = int((time.time() - start_time) * 1000)
807 return ScanResponse(
808 scan_id=scan_id,
809 status="completed",
810 scanned_by="Guardrails Agent",
811 alprina_engine="active" if AGENTS_AVAILABLE else "fallback",
812 findings=findings_list,
813 summary=summary,
814 duration_ms=duration_ms
815 )
816 except Exception as e:
817 raise HTTPException(status_code=500, detail=f"Safety check failed: {str(e)}")
820@router.get("/scan/{scan_id}")
821async def get_scan_results(scan_id: str):
822 """
823 Get scan results by ID.
825 **Note:** Currently returns a placeholder. Implement database
826 storage to persist and retrieve scan results.
827 """
828 # TODO: Implement database storage and retrieval
829 return {
830 "scan_id": scan_id,
831 "status": "not_implemented",
832 "message": "Scan result storage not yet implemented"
833 }