zen-ai-pentest 2.0.4__py3-none-any.whl → 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
api/main.py CHANGED
@@ -60,7 +60,7 @@ async def lifespan(app: FastAPI):
60
60
  app = FastAPI(
61
61
  title="Zen-AI-Pentest API",
62
62
  description="Professional Pentesting Framework API",
63
- version="2.0.0",
63
+ version="2.2.0",
64
64
  lifespan=lifespan
65
65
  )
66
66
 
@@ -500,7 +500,7 @@ async def health_check():
500
500
  """Health check endpoint"""
501
501
  return {
502
502
  "status": "healthy",
503
- "version": "2.0.0",
503
+ "version": "2.2.0",
504
504
  "timestamp": datetime.utcnow().isoformat()
505
505
  }
506
506
 
@@ -1022,6 +1022,18 @@ async def create_jira_ticket(
1022
1022
  # Import models for reports
1023
1023
  from database.models import Report
1024
1024
 
1025
+ # ============================================================================
1026
+ # API v1.0 (Q1 2026)
1027
+ # ============================================================================
1028
+
1029
+ # Import v1 router
1030
+ try:
1031
+ from api.v1.siem import router as siem_v1_router
1032
+ app.include_router(siem_v1_router, prefix="/api/v1/siem", tags=["SIEM v1.0"])
1033
+ logger.info("API v1.0 SIEM endpoints loaded")
1034
+ except ImportError as e:
1035
+ logger.warning(f"Could not load API v1.0 endpoints: {e}")
1036
+
1025
1037
  if __name__ == "__main__":
1026
1038
  import uvicorn
1027
1039
  uvicorn.run(app, host="0.0.0.0", port=8000)
api/websocket_v2.py ADDED
@@ -0,0 +1,181 @@
1
+ """
2
+ WebSocket v2.0 - Real-time Updates
3
+ Q2 2026 Feature
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ from typing import Dict, Set
9
+ from fastapi import WebSocket, WebSocketDisconnect
10
+ from datetime import datetime
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class ConnectionManagerV2:
16
+ """Advanced WebSocket connection manager with rooms"""
17
+
18
+ def __init__(self):
19
+ # Active connections by room
20
+ self.rooms: Dict[str, Set[WebSocket]] = {
21
+ "dashboard": set(),
22
+ "scans": set(),
23
+ "findings": set(),
24
+ "notifications": set()
25
+ }
26
+ # User connections
27
+ self.user_connections: Dict[str, WebSocket] = {}
28
+
29
+ async def connect(self, websocket: WebSocket, room: str = "dashboard", user_id: str = None):
30
+ """Connect client to room"""
31
+ await websocket.accept()
32
+
33
+ if room in self.rooms:
34
+ self.rooms[room].add(websocket)
35
+
36
+ if user_id:
37
+ self.user_connections[user_id] = websocket
38
+
39
+ logger.info(f"Client connected to room: {room}")
40
+
41
+ # Send welcome message
42
+ await websocket.send_json({
43
+ "type": "connection",
44
+ "status": "connected",
45
+ "room": room,
46
+ "timestamp": datetime.utcnow().isoformat()
47
+ })
48
+
49
+ def disconnect(self, websocket: WebSocket, room: str = None):
50
+ """Disconnect client"""
51
+ if room and room in self.rooms:
52
+ self.rooms[room].discard(websocket)
53
+ else:
54
+ # Remove from all rooms
55
+ for room_set in self.rooms.values():
56
+ room_set.discard(websocket)
57
+
58
+ # Remove from user connections
59
+ for user_id, conn in list(self.user_connections.items()):
60
+ if conn == websocket:
61
+ del self.user_connections[user_id]
62
+
63
+ logger.info("Client disconnected")
64
+
65
+ async def broadcast_to_room(self, room: str, message: dict):
66
+ """Broadcast message to all clients in room"""
67
+ if room not in self.rooms:
68
+ return
69
+
70
+ disconnected = set()
71
+ for connection in self.rooms[room]:
72
+ try:
73
+ await connection.send_json(message)
74
+ except Exception:
75
+ disconnected.add(connection)
76
+
77
+ # Clean up disconnected clients
78
+ for conn in disconnected:
79
+ self.rooms[room].discard(conn)
80
+
81
+ async def send_to_user(self, user_id: str, message: dict):
82
+ """Send message to specific user"""
83
+ if user_id in self.user_connections:
84
+ try:
85
+ await self.user_connections[user_id].send_json(message)
86
+ except Exception:
87
+ del self.user_connections[user_id]
88
+
89
+ async def broadcast_scan_update(self, scan_id: str, status: str, progress: int = None):
90
+ """Broadcast scan progress update"""
91
+ await self.broadcast_to_room("scans", {
92
+ "type": "scan_update",
93
+ "scan_id": scan_id,
94
+ "status": status,
95
+ "progress": progress,
96
+ "timestamp": datetime.utcnow().isoformat()
97
+ })
98
+
99
+ async def broadcast_finding(self, finding: dict):
100
+ """Broadcast new finding discovery"""
101
+ await self.broadcast_to_room("findings", {
102
+ "type": "new_finding",
103
+ "finding": finding,
104
+ "timestamp": datetime.utcnow().isoformat()
105
+ })
106
+
107
+ async def broadcast_notification(self, title: str, message: str, severity: str = "info"):
108
+ """Broadcast system notification"""
109
+ await self.broadcast_to_room("notifications", {
110
+ "type": "notification",
111
+ "title": title,
112
+ "message": message,
113
+ "severity": severity,
114
+ "timestamp": datetime.utcnow().isoformat()
115
+ })
116
+
117
+ def get_room_stats(self) -> dict:
118
+ """Get connection statistics"""
119
+ return {
120
+ room: len(connections)
121
+ for room, connections in self.rooms.items()
122
+ }
123
+
124
+
125
+ # Global manager instance
126
+ manager_v2 = ConnectionManagerV2()
127
+
128
+
129
+ async def websocket_dashboard_endpoint(websocket: WebSocket):
130
+ """Dashboard real-time updates"""
131
+ await manager_v2.connect(websocket, room="dashboard")
132
+ try:
133
+ while True:
134
+ # Receive ping from client
135
+ data = await websocket.receive_text()
136
+ message = json.loads(data)
137
+
138
+ if message.get("action") == "ping":
139
+ await websocket.send_json({
140
+ "type": "pong",
141
+ "timestamp": datetime.utcnow().isoformat()
142
+ })
143
+
144
+ except WebSocketDisconnect:
145
+ manager_v2.disconnect(websocket, room="dashboard")
146
+
147
+
148
+ async def websocket_scans_endpoint(websocket: WebSocket):
149
+ """Scan progress real-time updates"""
150
+ await manager_v2.connect(websocket, room="scans")
151
+ try:
152
+ while True:
153
+ data = await websocket.receive_text()
154
+ # Handle scan subscription requests
155
+ message = json.loads(data)
156
+
157
+ if message.get("action") == "subscribe_scan":
158
+ scan_id = message.get("scan_id")
159
+ await websocket.send_json({
160
+ "type": "subscribed",
161
+ "scan_id": scan_id,
162
+ "message": f"Subscribed to scan {scan_id} updates"
163
+ })
164
+
165
+ except WebSocketDisconnect:
166
+ manager_v2.disconnect(websocket, room="scans")
167
+
168
+
169
+ async def websocket_notifications_endpoint(websocket: WebSocket, user_id: str = None):
170
+ """User-specific notifications"""
171
+ await manager_v2.connect(websocket, room="notifications", user_id=user_id)
172
+ try:
173
+ while True:
174
+ data = await websocket.receive_text()
175
+ # Acknowledge receipt
176
+ await websocket.send_json({
177
+ "type": "ack",
178
+ "received": True
179
+ })
180
+ except WebSocketDisconnect:
181
+ manager_v2.disconnect(websocket, room="notifications")
modules/__init__.py CHANGED
@@ -10,7 +10,7 @@ from .osint import (DomainInfo, EmailProfile, OSINTModule, OSINTResult,
10
10
  from .protonvpn import (ProtonVPNManager, VPNProtocol, VPNSecurityLevel,
11
11
  VPNServer, VPNStatus, quick_connect, secure_connect)
12
12
  from .recon import ReconModule
13
- from .report_gen import ReportGenerator
13
+ # from .report_gen import ReportGenerator # Module not available
14
14
  from .sql_injection_db import (DBType, SQLInjectionDatabase, SQLITechnique,
15
15
  SQLPayload)
16
16
  from .vuln_scanner import VulnScannerModule
@@ -19,7 +19,7 @@ __all__ = [
19
19
  "ReconModule",
20
20
  "VulnScannerModule",
21
21
  "ExploitAssistModule",
22
- "ReportGenerator",
22
+ # "ReportGenerator", # Module not available
23
23
  "NucleiIntegration",
24
24
  "NucleiTemplateManager",
25
25
  "SQLInjectionDatabase",
@@ -0,0 +1,207 @@
1
+ """
2
+ Report Export Module
3
+ Q2 2026 - PDF & CSV Export
4
+ """
5
+
6
+ import csv
7
+ import io
8
+ import logging
9
+ from datetime import datetime
10
+ from typing import List, Dict, Any
11
+ from dataclasses import dataclass
12
+
13
+ try:
14
+ from weasyprint import HTML, CSS
15
+ WEASYPRINT_AVAILABLE = True
16
+ except ImportError:
17
+ WEASYPRINT_AVAILABLE = False
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ @dataclass
23
+ class ReportData:
24
+ """Report data structure"""
25
+ title: str
26
+ scan_date: datetime
27
+ target: str
28
+ findings: List[Dict[str, Any]]
29
+ summary: Dict[str, int]
30
+ recommendations: List[str]
31
+
32
+
33
+ class ReportExporter:
34
+ """Export reports to various formats"""
35
+
36
+ def __init__(self):
37
+ self.templates = {
38
+ "executive": self._executive_template,
39
+ "technical": self._technical_template,
40
+ "compliance": self._compliance_template
41
+ }
42
+
43
+ def export_csv(self, findings: List[Dict], filename: str = None) -> bytes:
44
+ """Export findings to CSV"""
45
+ if not filename:
46
+ filename = f"findings_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
47
+
48
+ output = io.StringIO()
49
+ writer = csv.writer(output)
50
+
51
+ # Header
52
+ writer.writerow([
53
+ "ID", "Severity", "Title", "Description", "Target",
54
+ "CVE", "CVSS", "Status", "Discovered"
55
+ ])
56
+
57
+ # Data
58
+ for finding in findings:
59
+ writer.writerow([
60
+ finding.get("id", ""),
61
+ finding.get("severity", ""),
62
+ finding.get("title", ""),
63
+ finding.get("description", ""),
64
+ finding.get("target", ""),
65
+ finding.get("cve_id", ""),
66
+ finding.get("cvss_score", ""),
67
+ finding.get("status", "open"),
68
+ finding.get("discovered_at", "")
69
+ ])
70
+
71
+ return output.getvalue().encode('utf-8')
72
+
73
+ def export_pdf(self, report: ReportData, template: str = "executive") -> bytes:
74
+ """Export report to PDF"""
75
+ if not WEASYPRINT_AVAILABLE:
76
+ logger.error("WeasyPrint not available. Install: pip install weasyprint")
77
+ raise RuntimeError("PDF generation requires WeasyPrint")
78
+
79
+ html_content = self.templates.get(template, self._executive_template)(report)
80
+
81
+ pdf = HTML(string=html_content).write_pdf()
82
+ return pdf
83
+
84
+ def _executive_template(self, report: ReportData) -> str:
85
+ """Executive summary template"""
86
+ findings_html = ""
87
+ for f in report.findings[:10]: # Top 10
88
+ severity_color = {
89
+ "critical": "#dc2626",
90
+ "high": "#ea580c",
91
+ "medium": "#ca8a04",
92
+ "low": "#16a34a"
93
+ }.get(f.get("severity", "low"), "#6b7280")
94
+
95
+ findings_html += f"""
96
+ <div style="margin: 10px 0; padding: 10px; border-left: 4px solid {severity_color}; background: #f9fafb;">
97
+ <strong>{f.get('title', 'Unknown')}</strong>
98
+ <span style="color: {severity_color}; text-transform: uppercase; font-size: 0.8em;">
99
+ {f.get('severity', 'unknown')}
100
+ </span>
101
+ <p style="margin: 5px 0; color: #4b5563;">{f.get('description', '')[:200]}...</p>
102
+ </div>
103
+ """
104
+
105
+ return f"""
106
+ <!DOCTYPE html>
107
+ <html>
108
+ <head>
109
+ <meta charset="UTF-8">
110
+ <title>{report.title}</title>
111
+ <style>
112
+ body {{ font-family: Arial, sans-serif; margin: 40px; }}
113
+ h1 {{ color: #111827; border-bottom: 2px solid #059669; padding-bottom: 10px; }}
114
+ .summary {{ background: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0; }}
115
+ .metric {{ display: inline-block; margin: 10px 20px; }}
116
+ .metric-value {{ font-size: 2em; font-weight: bold; color: #059669; }}
117
+ .metric-label {{ color: #6b7280; font-size: 0.9em; }}
118
+ .findings {{ margin-top: 30px; }}
119
+ </style>
120
+ </head>
121
+ <body>
122
+ <h1>{report.title}</h1>
123
+ <p><strong>Target:</strong> {report.target}</p>
124
+ <p><strong>Scan Date:</strong> {report.scan_date.strftime('%Y-%m-%d %H:%M')}</p>
125
+
126
+ <div class="summary">
127
+ <h2>Summary</h2>
128
+ <div class="metric">
129
+ <div class="metric-value">{report.summary.get('critical', 0)}</div>
130
+ <div class="metric-label">Critical</div>
131
+ </div>
132
+ <div class="metric">
133
+ <div class="metric-value">{report.summary.get('high', 0)}</div>
134
+ <div class="metric-label">High</div>
135
+ </div>
136
+ <div class="metric">
137
+ <div class="metric-value">{report.summary.get('medium', 0)}</div>
138
+ <div class="metric-label">Medium</div>
139
+ </div>
140
+ <div class="metric">
141
+ <div class="metric-value">{report.summary.get('low', 0)}</div>
142
+ <div class="metric-label">Low</div>
143
+ </div>
144
+ </div>
145
+
146
+ <div class="findings">
147
+ <h2>Top Findings</h2>
148
+ {findings_html}
149
+ </div>
150
+
151
+ <div style="margin-top: 40px; padding-top: 20px; border-top: 1px solid #e5e7eb; color: #6b7280; font-size: 0.9em;">
152
+ <p>Generated by Zen AI Pentest v2.1.0</p>
153
+ <p>Confidential - For authorized eyes only</p>
154
+ </div>
155
+ </body>
156
+ </html>
157
+ """
158
+
159
+ def _technical_template(self, report: ReportData) -> str:
160
+ """Technical detailed template"""
161
+ # Similar to executive but with more technical details
162
+ return self._executive_template(report) # Simplified for now
163
+
164
+ def _compliance_template(self, report: ReportData) -> str:
165
+ """Compliance-focused template"""
166
+ # For compliance reporting (PCI-DSS, GDPR, etc.)
167
+ return self._executive_template(report) # Simplified for now
168
+
169
+ def export_json(self, findings: List[Dict]) -> str:
170
+ """Export findings to JSON"""
171
+ import json
172
+ return json.dumps(findings, indent=2)
173
+
174
+ def get_export_formats(self) -> List[str]:
175
+ """List available export formats"""
176
+ formats = ["csv", "json"]
177
+ if WEASYPRINT_AVAILABLE:
178
+ formats.append("pdf")
179
+ return formats
180
+
181
+
182
+ # Convenience function
183
+ def export_findings(findings: List[Dict], format: str = "csv") -> bytes:
184
+ """Quick export function"""
185
+ exporter = ReportExporter()
186
+
187
+ if format == "csv":
188
+ return exporter.export_csv(findings)
189
+ elif format == "json":
190
+ return exporter.export_json(findings).encode('utf-8')
191
+ elif format == "pdf":
192
+ report = ReportData(
193
+ title="Security Assessment Report",
194
+ scan_date=datetime.now(),
195
+ target="Multiple Targets",
196
+ findings=findings,
197
+ summary={
198
+ "critical": len([f for f in findings if f.get("severity") == "critical"]),
199
+ "high": len([f for f in findings if f.get("severity") == "high"]),
200
+ "medium": len([f for f in findings if f.get("severity") == "medium"]),
201
+ "low": len([f for f in findings if f.get("severity") == "low"])
202
+ },
203
+ recommendations=[]
204
+ )
205
+ return exporter.export_pdf(report)
206
+ else:
207
+ raise ValueError(f"Unsupported format: {format}")
@@ -0,0 +1,501 @@
1
+ """
2
+ SIEM Integration Module for Zen AI Pentest
3
+ Supports: Splunk, Elastic, Azure Sentinel, IBM QRadar
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ from abc import ABC, abstractmethod
9
+ from dataclasses import dataclass
10
+ from datetime import datetime
11
+ from typing import Dict, List, Optional, Any
12
+ import requests
13
+ from urllib.parse import urljoin
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class SIEMConfig:
20
+ """SIEM connection configuration"""
21
+ name: str
22
+ type: str # splunk, elastic, sentinel, qradar
23
+ url: str
24
+ api_key: Optional[str] = None
25
+ username: Optional[str] = None
26
+ password: Optional[str] = None
27
+ index: Optional[str] = None
28
+ verify_ssl: bool = True
29
+ timeout: int = 30
30
+
31
+
32
+ @dataclass
33
+ class SecurityEvent:
34
+ """Normalized security event for SIEM ingestion"""
35
+ timestamp: datetime
36
+ severity: str # critical, high, medium, low, info
37
+ event_type: str
38
+ source: str
39
+ target: str
40
+ description: str
41
+ cve_id: Optional[str] = None
42
+ cvss_score: Optional[float] = None
43
+ raw_data: Optional[Dict] = None
44
+
45
+
46
+ class BaseSIEMConnector(ABC):
47
+ """Base class for SIEM connectors"""
48
+
49
+ def __init__(self, config: SIEMConfig):
50
+ self.config = config
51
+ self.session = requests.Session()
52
+ self.session.verify = config.verify_ssl
53
+ self.session.timeout = config.timeout
54
+
55
+ @abstractmethod
56
+ def connect(self) -> bool:
57
+ """Test connection to SIEM"""
58
+ pass
59
+
60
+ @abstractmethod
61
+ def send_event(self, event: SecurityEvent) -> bool:
62
+ """Send security event to SIEM"""
63
+ pass
64
+
65
+ @abstractmethod
66
+ def send_events_batch(self, events: List[SecurityEvent]) -> bool:
67
+ """Send multiple events to SIEM"""
68
+ pass
69
+
70
+ @abstractmethod
71
+ def query_threat_intel(self, indicator: str) -> Optional[Dict]:
72
+ """Query SIEM for threat intelligence"""
73
+ pass
74
+
75
+
76
+ class SplunkConnector(BaseSIEMConnector):
77
+ """Splunk HTTP Event Collector (HEC) connector"""
78
+
79
+ def connect(self) -> bool:
80
+ try:
81
+ url = urljoin(self.config.url, "/services/collector/health")
82
+ headers = {"Authorization": f"Splunk {self.config.api_key}"}
83
+ response = self.session.get(url, headers=headers)
84
+ return response.status_code == 200
85
+ except Exception as e:
86
+ logger.error(f"Splunk connection failed: {e}")
87
+ return False
88
+
89
+ def send_event(self, event: SecurityEvent) -> bool:
90
+ try:
91
+ url = urljoin(self.config.url, "/services/collector/event")
92
+ headers = {
93
+ "Authorization": f"Splunk {self.config.api_key}",
94
+ "Content-Type": "application/json"
95
+ }
96
+
97
+ payload = {
98
+ "time": event.timestamp.timestamp(),
99
+ "sourcetype": "zen_ai_pentest",
100
+ "index": self.config.index or "security",
101
+ "event": {
102
+ "severity": event.severity,
103
+ "event_type": event.event_type,
104
+ "source": event.source,
105
+ "target": event.target,
106
+ "description": event.description,
107
+ "cve_id": event.cve_id,
108
+ "cvss_score": event.cvss_score,
109
+ **(event.raw_data or {})
110
+ }
111
+ }
112
+
113
+ response = self.session.post(url, headers=headers, json=payload)
114
+ return response.status_code == 200
115
+ except Exception as e:
116
+ logger.error(f"Failed to send event to Splunk: {e}")
117
+ return False
118
+
119
+ def send_events_batch(self, events: List[SecurityEvent]) -> bool:
120
+ """Send multiple events to Splunk"""
121
+ try:
122
+ url = urljoin(self.config.url, "/services/collector/event")
123
+ headers = {
124
+ "Authorization": f"Splunk {self.config.api_key}",
125
+ "Content-Type": "application/json"
126
+ }
127
+
128
+ batch_data = ""
129
+ for event in events:
130
+ payload = {
131
+ "time": event.timestamp.timestamp(),
132
+ "sourcetype": "zen_ai_pentest",
133
+ "index": self.config.index or "security",
134
+ "event": {
135
+ "severity": event.severity,
136
+ "event_type": event.event_type,
137
+ "source": event.source,
138
+ "target": event.target,
139
+ "description": event.description,
140
+ "cve_id": event.cve_id,
141
+ "cvss_score": event.cvss_score,
142
+ }
143
+ }
144
+ batch_data += json.dumps(payload) + "\n"
145
+
146
+ response = self.session.post(url, headers=headers, data=batch_data)
147
+ return response.status_code == 200
148
+ except Exception as e:
149
+ logger.error(f"Failed to send batch to Splunk: {e}")
150
+ return False
151
+
152
+ def query_threat_intel(self, indicator: str) -> Optional[Dict]:
153
+ """Query Splunk for threat intelligence"""
154
+ try:
155
+ search_query = f'search index=threat_intel indicator="{indicator}" | head 1'
156
+ url = urljoin(self.config.url, "/services/search/jobs")
157
+ headers = {"Authorization": f"Splunk {self.config.api_key}"}
158
+
159
+ data = {"search": search_query, "output_mode": "json"}
160
+ response = self.session.post(url, headers=headers, data=data)
161
+
162
+ if response.status_code == 200:
163
+ return response.json()
164
+ return None
165
+ except Exception as e:
166
+ logger.error(f"Failed to query Splunk: {e}")
167
+ return None
168
+
169
+
170
+ class ElasticConnector(BaseSIEMConnector):
171
+ """Elastic/Elasticsearch connector"""
172
+
173
+ def connect(self) -> bool:
174
+ try:
175
+ url = urljoin(self.config.url, "_cluster/health")
176
+ headers = {"Authorization": f"ApiKey {self.config.api_key}"}
177
+ response = self.session.get(url, headers=headers)
178
+ return response.status_code == 200
179
+ except Exception as e:
180
+ logger.error(f"Elastic connection failed: {e}")
181
+ return False
182
+
183
+ def send_event(self, event: SecurityEvent) -> bool:
184
+ try:
185
+ index = self.config.index or "zen-ai-security"
186
+ url = urljoin(self.config.url, f"{index}/_doc")
187
+ headers = {"Authorization": f"ApiKey {self.config.api_key}"}
188
+
189
+ document = {
190
+ "@timestamp": event.timestamp.isoformat(),
191
+ "severity": event.severity,
192
+ "event.type": event.event_type,
193
+ "source.ip": event.source,
194
+ "destination.ip": event.target,
195
+ "message": event.description,
196
+ "vulnerability.cve": event.cve_id,
197
+ "vulnerability.cvss": event.cvss_score,
198
+ }
199
+
200
+ response = self.session.post(url, headers=headers, json=document)
201
+ return response.status_code in [200, 201]
202
+ except Exception as e:
203
+ logger.error(f"Failed to send event to Elastic: {e}")
204
+ return False
205
+
206
+ def send_events_batch(self, events: List[SecurityEvent]) -> bool:
207
+ """Send bulk events to Elastic"""
208
+ try:
209
+ index = self.config.index or "zen-ai-security"
210
+ url = urljoin(self.config.url, "_bulk")
211
+ headers = {"Authorization": f"ApiKey {self.config.api_key}"}
212
+
213
+ bulk_data = ""
214
+ for event in events:
215
+ action = json.dumps({"index": {"_index": index}})
216
+ document = json.dumps({
217
+ "@timestamp": event.timestamp.isoformat(),
218
+ "severity": event.severity,
219
+ "event.type": event.event_type,
220
+ "source.ip": event.source,
221
+ "destination.ip": event.target,
222
+ "message": event.description,
223
+ "vulnerability.cve": event.cve_id,
224
+ "vulnerability.cvss": event.cvss_score,
225
+ })
226
+ bulk_data += action + "\n" + document + "\n"
227
+
228
+ response = self.session.post(
229
+ url,
230
+ headers={**headers, "Content-Type": "application/x-ndjson"},
231
+ data=bulk_data
232
+ )
233
+ return response.status_code == 200
234
+ except Exception as e:
235
+ logger.error(f"Failed to send bulk to Elastic: {e}")
236
+ return False
237
+
238
+ def query_threat_intel(self, indicator: str) -> Optional[Dict]:
239
+ """Query Elastic for threat intelligence"""
240
+ try:
241
+ index = "threat-intel"
242
+ url = urljoin(self.config.url, f"{index}/_search")
243
+ headers = {"Authorization": f"ApiKey {self.config.api_key}"}
244
+
245
+ query = {
246
+ "query": {
247
+ "match": {"indicator": indicator}
248
+ }
249
+ }
250
+
251
+ response = self.session.post(url, headers=headers, json=query)
252
+ if response.status_code == 200:
253
+ return response.json()
254
+ return None
255
+ except Exception as e:
256
+ logger.error(f"Failed to query Elastic: {e}")
257
+ return None
258
+
259
+
260
+ class AzureSentinelConnector(BaseSIEMConnector):
261
+ """Azure Sentinel/Log Analytics connector"""
262
+
263
+ def __init__(self, config: SIEMConfig):
264
+ super().__init__(config)
265
+ self.workspace_id = config.username # Workspace ID
266
+ self.shared_key = config.api_key # Shared Key
267
+
268
+ def connect(self) -> bool:
269
+ try:
270
+ # Test connection by querying Log Analytics
271
+ url = f"https://api.loganalytics.io/v1/workspaces/{self.workspace_id}/query"
272
+ headers = self._build_auth_header()
273
+
274
+ response = self.session.get(url, headers=headers)
275
+ return response.status_code in [200, 401] # 401 means auth works but query invalid
276
+ except Exception as e:
277
+ logger.error(f"Sentinel connection failed: {e}")
278
+ return False
279
+
280
+ def _build_auth_header(self) -> Dict[str, str]:
281
+ """Build Azure API authentication header"""
282
+ import hmac
283
+ import hashlib
284
+ import base64
285
+
286
+ date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
287
+ string_to_hash = f"POST\n{len('')}\napplication/json\nx-ms-date:{date}\n/api/logs"
288
+ decoded_key = base64.b64decode(self.shared_key)
289
+ encoded_hash = base64.b64encode(
290
+ hmac.new(decoded_key, string_to_hash.encode(), hashlib.sha256).digest()
291
+ ).decode()
292
+
293
+ return {
294
+ "Content-Type": "application/json",
295
+ "Authorization": f"SharedKey {self.workspace_id}:{encoded_hash}",
296
+ "x-ms-date": date,
297
+ "Log-Type": "ZenAIPentest"
298
+ }
299
+
300
+ def send_event(self, event: SecurityEvent) -> bool:
301
+ try:
302
+ url = f"https://{self.workspace_id}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01"
303
+ headers = self._build_auth_header()
304
+
305
+ log_data = [{
306
+ "TimeGenerated": event.timestamp.isoformat(),
307
+ "Severity": event.severity,
308
+ "EventType": event.event_type,
309
+ "Source": event.source,
310
+ "Target": event.target,
311
+ "Description": event.description,
312
+ "CVE": event.cve_id,
313
+ "CVSS": event.cvss_score
314
+ }]
315
+
316
+ response = self.session.post(url, headers=headers, json=log_data)
317
+ return response.status_code == 200
318
+ except Exception as e:
319
+ logger.error(f"Failed to send event to Sentinel: {e}")
320
+ return False
321
+
322
+ def send_events_batch(self, events: List[SecurityEvent]) -> bool:
323
+ """Send batch to Azure Log Analytics"""
324
+ try:
325
+ url = f"https://{self.workspace_id}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01"
326
+ headers = self._build_auth_header()
327
+
328
+ log_data = []
329
+ for event in events:
330
+ log_data.append({
331
+ "TimeGenerated": event.timestamp.isoformat(),
332
+ "Severity": event.severity,
333
+ "EventType": event.event_type,
334
+ "Source": event.source,
335
+ "Target": event.target,
336
+ "Description": event.description,
337
+ "CVE": event.cve_id,
338
+ "CVSS": event.cvss_score
339
+ })
340
+
341
+ response = self.session.post(url, headers=headers, json=log_data)
342
+ return response.status_code == 200
343
+ except Exception as e:
344
+ logger.error(f"Failed to send batch to Sentinel: {e}")
345
+ return False
346
+
347
+ def query_threat_intel(self, indicator: str) -> Optional[Dict]:
348
+ """Query Sentinel Threat Intelligence"""
349
+ try:
350
+ url = f"https://api.loganalytics.io/v1/workspaces/{self.workspace_id}/query"
351
+ headers = self._build_auth_header()
352
+
353
+ query = f"ThreatIntelligenceIndicator | where Indicator == '{indicator}' | limit 1"
354
+
355
+ response = self.session.post(
356
+ url,
357
+ headers=headers,
358
+ json={"query": query}
359
+ )
360
+
361
+ if response.status_code == 200:
362
+ return response.json()
363
+ return None
364
+ except Exception as e:
365
+ logger.error(f"Failed to query Sentinel: {e}")
366
+ return None
367
+
368
+
369
+ class QRadarConnector(BaseSIEMConnector):
370
+ """IBM QRadar connector"""
371
+
372
+ def connect(self) -> bool:
373
+ try:
374
+ url = urljoin(self.config.url, "/api/system/about")
375
+ headers = self._build_auth_header()
376
+ response = self.session.get(url, headers=headers)
377
+ return response.status_code == 200
378
+ except Exception as e:
379
+ logger.error(f"QRadar connection failed: {e}")
380
+ return False
381
+
382
+ def _build_auth_header(self) -> Dict[str, str]:
383
+ """Build QRadar authentication header"""
384
+ import base64
385
+
386
+ if self.config.api_key:
387
+ return {"SEC": self.config.api_key}
388
+ elif self.config.username and self.config.password:
389
+ credentials = base64.b64encode(
390
+ f"{self.config.username}:{self.config.password}".encode()
391
+ ).decode()
392
+ return {"Authorization": f"Basic {credentials}"}
393
+ return {}
394
+
395
+ def send_event(self, event: SecurityEvent) -> bool:
396
+ try:
397
+ url = urljoin(self.config.url, "/api/asset_model/assets")
398
+ headers = self._build_auth_header()
399
+
400
+ payload = {
401
+ "event": {
402
+ "severity": event.severity,
403
+ "event_type": event.event_type,
404
+ "source": event.source,
405
+ "target": event.target,
406
+ "description": event.description,
407
+ "cve": event.cve_id
408
+ }
409
+ }
410
+
411
+ response = self.session.post(url, headers=headers, json=payload)
412
+ return response.status_code in [200, 201]
413
+ except Exception as e:
414
+ logger.error(f"Failed to send event to QRadar: {e}")
415
+ return False
416
+
417
+ def send_events_batch(self, events: List[SecurityEvent]) -> bool:
418
+ """QRadar batch event submission"""
419
+ for event in events:
420
+ if not self.send_event(event):
421
+ return False
422
+ return True
423
+
424
+ def query_threat_intel(self, indicator: str) -> Optional[Dict]:
425
+ """Query QRadar reference sets"""
426
+ try:
427
+ url = urljoin(self.config.url, f"/api/reference_data/sets/{indicator}")
428
+ headers = self._build_auth_header()
429
+
430
+ response = self.session.get(url, headers=headers)
431
+ if response.status_code == 200:
432
+ return response.json()
433
+ return None
434
+ except Exception as e:
435
+ logger.error(f"Failed to query QRadar: {e}")
436
+ return None
437
+
438
+
439
+ class SIEMIntegrationManager:
440
+ """Manager for multiple SIEM integrations"""
441
+
442
+ def __init__(self):
443
+ self.connectors: Dict[str, BaseSIEMConnector] = {}
444
+ self._connector_classes = {
445
+ "splunk": SplunkConnector,
446
+ "elastic": ElasticConnector,
447
+ "sentinel": AzureSentinelConnector,
448
+ "qradar": QRadarConnector
449
+ }
450
+
451
+ def add_siem(self, config: SIEMConfig) -> bool:
452
+ """Add SIEM integration"""
453
+ connector_class = self._connector_classes.get(config.type)
454
+ if not connector_class:
455
+ logger.error(f"Unknown SIEM type: {config.type}")
456
+ return False
457
+
458
+ connector = connector_class(config)
459
+ if connector.connect():
460
+ self.connectors[config.name] = connector
461
+ logger.info(f"Connected to {config.name} ({config.type})")
462
+ return True
463
+ else:
464
+ logger.error(f"Failed to connect to {config.name}")
465
+ return False
466
+
467
+ def send_to_all(self, event: SecurityEvent) -> Dict[str, bool]:
468
+ """Send event to all configured SIEMs"""
469
+ results = {}
470
+ for name, connector in self.connectors.items():
471
+ results[name] = connector.send_event(event)
472
+ return results
473
+
474
+ def send_batch_to_all(self, events: List[SecurityEvent]) -> Dict[str, bool]:
475
+ """Send batch of events to all SIEMs"""
476
+ results = {}
477
+ for name, connector in self.connectors.items():
478
+ results[name] = connector.send_events_batch(events)
479
+ return results
480
+
481
+ def get_status(self) -> Dict[str, bool]:
482
+ """Get connection status of all SIEMs"""
483
+ return {name: connector.connect() for name, connector in self.connectors.items()}
484
+
485
+
486
+ # Factory function
487
+ def create_siem_connector(config: SIEMConfig) -> Optional[BaseSIEMConnector]:
488
+ """Factory function to create appropriate SIEM connector"""
489
+ connectors = {
490
+ "splunk": SplunkConnector,
491
+ "elastic": ElasticConnector,
492
+ "sentinel": AzureSentinelConnector,
493
+ "qradar": QRadarConnector
494
+ }
495
+
496
+ connector_class = connectors.get(config.type.lower())
497
+ if connector_class:
498
+ return connector_class(config)
499
+
500
+ logger.error(f"Unknown SIEM type: {config.type}")
501
+ return None
risk_engine/__init__.py CHANGED
@@ -58,7 +58,7 @@ from .business_impact_calculator import (
58
58
  )
59
59
 
60
60
  # Additional exports for backwards compatibility
61
- from .scorer import RiskScorer
61
+ from .scorer import RiskScorer, RiskScore, SeverityLevel
62
62
  from .cvss import CVSSCalculator
63
63
  from .epss import EPSSClient
64
64
 
@@ -92,6 +92,8 @@ __all__ = [
92
92
 
93
93
  # Additional scoring modules
94
94
  "RiskScorer",
95
+ "RiskScore",
96
+ "SeverityLevel",
95
97
  "CVSSCalculator",
96
98
  "EPSSClient",
97
99
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zen-ai-pentest
3
- Version: 2.0.4
3
+ Version: 2.2.0
4
4
  Summary: Advanced AI-Powered Penetration Testing Framework with Multi-Agent Orchestration
5
5
  Home-page: https://github.com/SHAdd0WTAka/zen-ai-pentest
6
6
  Author: SHAdd0WTAka
@@ -63,10 +63,53 @@ Dynamic: requires-python
63
63
  [![Docker](https://img.shields.io/badge/Docker-Ready-blue)](docker/)
64
64
  [![Tests](https://img.shields.io/badge/Tests-pytest-brightgreen)](tests/)
65
65
  [![PyPI](https://img.shields.io/pypi/v/zen-ai-pentest)](https://pypi.org/project/zen-ai-pentest/)
66
- [![Version](https://img.shields.io/badge/Version-2.0.3-orange)](https://github.com/SHAdd0WTAka/zen-ai-pentest/releases)
66
+ [![Version](https://img.shields.io/badge/Version-2.0.4-orange)](https://github.com/SHAdd0WTAka/zen-ai-pentest/releases)
67
67
  [![Authors](https://img.shields.io/badge/Authors-SHAdd0WTAka%20%7C%20KimiAI-purple)](#-authors--team)
68
68
  [![Roadmap](https://img.shields.io/badge/Roadmap-2026-blueviolet)](ROADMAP_2026.md)
69
-
69
+ [![Architecture](https://img.shields.io/badge/Architecture-Diagram-blue)](docs/architecture.md)
70
+ ```mermaid
71
+ graph TB
72
+ subgraph "User Interface"
73
+ CLI[CLI]
74
+ API[REST API]
75
+ WebUI[Web UI]
76
+ end
77
+
78
+ subgraph "Core Engine"
79
+ Orchestrator[Agent Orchestrator]
80
+ StateMachine[State Machine]
81
+ RiskEngine[Risk Engine]
82
+ end
83
+
84
+ subgraph "AI Agents"
85
+ Recon[Reconnaissance]
86
+ Vuln[Vulnerability]
87
+ Exploit[Exploit]
88
+ Report[Report]
89
+ end
90
+
91
+ subgraph "Tools"
92
+ Nmap[Nmap]
93
+ SQLMap[SQLMap]
94
+ Metasploit[Metasploit]
95
+ end
96
+
97
+ subgraph "External APIs"
98
+ OpenAI[OpenAI]
99
+ Anthropic[Anthropic]
100
+ ThreatIntel[Threat Intelligence]
101
+ end
102
+
103
+ CLI --> API
104
+ WebUI --> API
105
+ API --> Orchestrator
106
+ Orchestrator --> StateMachine
107
+ StateMachine --> Recon
108
+ StateMachine --> Vuln
109
+ StateMachine --> Exploit
110
+ Exploit --> OpenAI
111
+ RiskEngine --> ThreatIntel
112
+ ```
70
113
  **Zen-AI-Pentest** is an autonomous, AI-powered penetration testing framework that combines cutting-edge language models with professional security tools. Built for security professionals, bug bounty hunters, and enterprise security teams.
71
114
 
72
115
  ---
@@ -12,9 +12,10 @@ agents/react_agent_vm.py,sha256=DF_TMWG_AfWbtvw9s4TB1MAwkFLMRaWpKw2UcM7Pa5U,1112
12
12
  agents/research_agent.py,sha256=f_OjmEvgQX1mUdLRlsj3-QMyB1m5J0oHIsTvFkcRijc,6394
13
13
  api/__init__.py,sha256=HQ2cFR8t_PlbtgLzir7P1Umzh-syS5iEfCNZky5uL_Y,256
14
14
  api/auth.py,sha256=ZqIvj0L0BTAFsytjCznGF53i1kz6DOunhLxd0mPUykA,3934
15
- api/main.py,sha256=NGXwzpd0LuVMGA2f65PdpIDT3veHNZ4Z0fRDkJxWbx8,32324
15
+ api/main.py,sha256=ZeU_ZQVNl9JD73S-L2wWt37n02SRkWzf2eLcGYTqB8k,32801
16
16
  api/schemas.py,sha256=nnE-97OOOWpAAenD_sCsCpcLy-SpWW1g2WW902mbihY,10306
17
17
  api/websocket.py,sha256=DTDKr48g6RBNCBX6LM1WljR1FLTkoYB-BTm1TtMNaOY,3602
18
+ api/websocket_v2.py,sha256=fVFlDhJc4iq95BBy7UdkdeQdv2QgSCbCmYGLzqL9DfM,6019
18
19
  autonomous/__init__.py,sha256=Gv83jnjasidFfyD6ggCXjJlqjn6YwANLi_b0-kpBSok,2858
19
20
  autonomous/agent.py,sha256=LG937JGTdiywhjDr9wCxuYaysgL7dRMbPguYWTnjrt0,8021
20
21
  autonomous/agent_loop.py,sha256=a_WvxaKgImXyMdddXmdoJCANVbjmWz-FBiptHJn2608,47216
@@ -47,18 +48,20 @@ core/plugin_manager.py,sha256=3sG2DMiWbyjAaoydtyz2pXmdJ-G_-SWgCLGs-w_QoSA,19167
47
48
  core/rate_limiter.py,sha256=poEYln6DGFs6C6n2rSnqRykax-YDi5NySk4LJnkgCpk,12423
48
49
  core/secure_config.py,sha256=tTDVXKoLiGKdfCQkLorgEKw2qdVyPjwhaxwyuFlGD_8,10361
49
50
  core/shield_integration.py,sha256=ktL_25WY0JBH5dBNvqcJLw42mQFkKr6ku5lkgH119Mw,9997
50
- modules/__init__.py,sha256=N9gvXDXbB1jAzQuImM8oZky2W9QwURup6SMEets4BJ0,1366
51
+ modules/__init__.py,sha256=eIfsYjpXtuo4cH4TQOIOrXLa6OXrP168ESPM3OuQYT0,1418
51
52
  modules/cve_database.py,sha256=46ucXLdWjEUed4qh4wznn3oWEyoxWay2r3EVgVMmBrg,13137
52
53
  modules/exploit_assist.py,sha256=xMcIYgwP6XSNuJDw8hI3FM1GsvvL9B3uMiYE8w-OBbY,10254
53
54
  modules/nuclei_integration.py,sha256=2pvFCcTpEpC6M6YV2QWoHBxpKYswoYnD7dX4GJTQnX4,14814
54
55
  modules/osint.py,sha256=SZG1rfMQ-zPIasYtKRzFlsjXR3wDSrVLRUtg6extUok,20600
55
56
  modules/protonvpn.py,sha256=4G-tLL3JyjJm-ryQIt4-hQTX4eKX_OTZnKFkZaK_F8Q,18133
56
57
  modules/recon.py,sha256=utS-wfdRBS7XaGtr1mcpMuetyJcCi2B8LZrAmVJjhcM,5002
58
+ modules/report_export.py,sha256=PKZNWrwK_x0XFpSjDzoI3-BoXYBnZ5VyQJ1V-nyhVj8,7622
59
+ modules/siem_integration.py,sha256=GbG8sbyziItdQhRArNku7pV6yZW8jR9AQgUxU2dvJ98,18594
57
60
  modules/sql_injection_db.py,sha256=x9VVIaXkykXo0zFmcPqIMk9gh_CF-Lrs9Ht2hrb9DLk,28945
58
61
  modules/tool_orchestrator.py,sha256=6FwR-EMR-HIrLFFimWf5MUMf_AYIYPUuZPaL8oTUNVU,15868
59
62
  modules/vuln_scanner.py,sha256=L68uCM1YezXrnbqjhJlslVyQOlM9jejnZHWR8j0B1m8,8617
60
63
  modules/wordlist_generator.py,sha256=3qAZseHDKI55x3gAnxKtwN1iMjXSJi1N0uMBnCnogV8,15429
61
- risk_engine/__init__.py,sha256=LKP5c_eYjSlROfSjXc_5mzmTGsi6odEOg3K1yIdR23U,2360
64
+ risk_engine/__init__.py,sha256=PPGuSptgnMfNM2-DtdgE-5h7CL_0W4FdgUFYqnTgLIw,2424
62
65
  risk_engine/business_impact.py,sha256=gAjhNdMLdjx5iQkpPqxb6ugfRL6_1VCc6AIli95kS0E,8657
63
66
  risk_engine/business_impact_calculator.py,sha256=9SgIXfhF-EFVwULyWepv1_Wcr_UOSg2E0oO2_VwwTWk,21222
64
67
  risk_engine/cvss.py,sha256=iI8clKFSwi9w1xIW2Z9tCScUOmGSGWJrowA9jGWW86I,5397
@@ -67,9 +70,9 @@ risk_engine/example_usage.py,sha256=eM54wLDy3eZtiDGjOSZ4YrsCcEUzfVKjV_A_Pkn07is,
67
70
  risk_engine/false_positive_engine.py,sha256=8u0wI3W25fsEq9eXfskUtcHfV3D2V6ElzVSgY_TAA-c,37961
68
71
  risk_engine/scorer.py,sha256=BklUfMo26IaOYOJse-mjvY8NwjpbI4UEIlqZ6MhCmwU,10282
69
72
  web_ui/backend/main.py,sha256=DTVmsvbDH7EvMmeohbcl8vYyKPyaLU8oHMvwWAqAF8k,15402
70
- zen_ai_pentest-2.0.4.dist-info/licenses/LICENSE,sha256=C1sNTmgBbFuCm9vPCctuz6pQr4khkgqoQEMUE6cP-FY,1068
71
- zen_ai_pentest-2.0.4.dist-info/METADATA,sha256=f27GlZvojUOX54PnVcBPJz_pCntJv5T8ifpmI_jfCd8,29761
72
- zen_ai_pentest-2.0.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
73
- zen_ai_pentest-2.0.4.dist-info/entry_points.txt,sha256=qZRuz7yjZdEMiUrfflMiGzMUHUZWasT49-HNE-EKmTE,55
74
- zen_ai_pentest-2.0.4.dist-info/top_level.txt,sha256=ExWeAuK-0xRpcWS7QsL8LEdPVJy1-Z-83qwOJ1-CERA,80
75
- zen_ai_pentest-2.0.4.dist-info/RECORD,,
73
+ zen_ai_pentest-2.2.0.dist-info/licenses/LICENSE,sha256=C1sNTmgBbFuCm9vPCctuz6pQr4khkgqoQEMUE6cP-FY,1068
74
+ zen_ai_pentest-2.2.0.dist-info/METADATA,sha256=i4AXbZaIOQ3ewhiNCQ4vgMblexzd6qs3uuJlFhiiCVE,30802
75
+ zen_ai_pentest-2.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
76
+ zen_ai_pentest-2.2.0.dist-info/entry_points.txt,sha256=qZRuz7yjZdEMiUrfflMiGzMUHUZWasT49-HNE-EKmTE,55
77
+ zen_ai_pentest-2.2.0.dist-info/top_level.txt,sha256=ExWeAuK-0xRpcWS7QsL8LEdPVJy1-Z-83qwOJ1-CERA,80
78
+ zen_ai_pentest-2.2.0.dist-info/RECORD,,