zen-ai-pentest 2.0.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.
- agents/__init__.py +28 -0
- agents/agent_base.py +239 -0
- agents/agent_orchestrator.py +346 -0
- agents/analysis_agent.py +225 -0
- agents/cli.py +258 -0
- agents/exploit_agent.py +224 -0
- agents/integration.py +211 -0
- agents/post_scan_agent.py +937 -0
- agents/react_agent.py +384 -0
- agents/react_agent_enhanced.py +616 -0
- agents/react_agent_vm.py +298 -0
- agents/research_agent.py +176 -0
- api/__init__.py +11 -0
- api/auth.py +123 -0
- api/main.py +1027 -0
- api/schemas.py +357 -0
- api/websocket.py +97 -0
- autonomous/__init__.py +122 -0
- autonomous/agent.py +253 -0
- autonomous/agent_loop.py +1370 -0
- autonomous/exploit_validator.py +1537 -0
- autonomous/memory.py +448 -0
- autonomous/react.py +339 -0
- autonomous/tool_executor.py +488 -0
- backends/__init__.py +16 -0
- backends/chatgpt_direct.py +133 -0
- backends/claude_direct.py +130 -0
- backends/duckduckgo.py +138 -0
- backends/openrouter.py +120 -0
- benchmarks/__init__.py +149 -0
- benchmarks/benchmark_engine.py +904 -0
- benchmarks/ci_benchmark.py +785 -0
- benchmarks/comparison.py +729 -0
- benchmarks/metrics.py +553 -0
- benchmarks/run_benchmarks.py +809 -0
- ci_cd/__init__.py +2 -0
- core/__init__.py +17 -0
- core/async_pool.py +282 -0
- core/asyncio_fix.py +222 -0
- core/cache.py +472 -0
- core/container.py +277 -0
- core/database.py +114 -0
- core/input_validator.py +353 -0
- core/models.py +288 -0
- core/orchestrator.py +611 -0
- core/plugin_manager.py +571 -0
- core/rate_limiter.py +405 -0
- core/secure_config.py +328 -0
- core/shield_integration.py +296 -0
- modules/__init__.py +46 -0
- modules/cve_database.py +362 -0
- modules/exploit_assist.py +330 -0
- modules/nuclei_integration.py +480 -0
- modules/osint.py +604 -0
- modules/protonvpn.py +554 -0
- modules/recon.py +165 -0
- modules/sql_injection_db.py +826 -0
- modules/tool_orchestrator.py +498 -0
- modules/vuln_scanner.py +292 -0
- modules/wordlist_generator.py +566 -0
- risk_engine/__init__.py +99 -0
- risk_engine/business_impact.py +267 -0
- risk_engine/business_impact_calculator.py +563 -0
- risk_engine/cvss.py +156 -0
- risk_engine/epss.py +190 -0
- risk_engine/example_usage.py +294 -0
- risk_engine/false_positive_engine.py +1073 -0
- risk_engine/scorer.py +304 -0
- web_ui/backend/main.py +471 -0
- zen_ai_pentest-2.0.0.dist-info/METADATA +795 -0
- zen_ai_pentest-2.0.0.dist-info/RECORD +75 -0
- zen_ai_pentest-2.0.0.dist-info/WHEEL +5 -0
- zen_ai_pentest-2.0.0.dist-info/entry_points.txt +2 -0
- zen_ai_pentest-2.0.0.dist-info/licenses/LICENSE +21 -0
- zen_ai_pentest-2.0.0.dist-info/top_level.txt +10 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Real Tool Execution Framework
|
|
3
|
+
|
|
4
|
+
Executes actual security tools (nmap, nuclei, sqlmap, etc.) in sandboxed environments.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import tempfile
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Any, Dict, List, Optional, Callable
|
|
18
|
+
import shlex
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SafetyLevel(Enum):
|
|
22
|
+
"""Safety levels for tool execution."""
|
|
23
|
+
READ_ONLY = 1 # Passive recon only (nmap -sS)
|
|
24
|
+
NON_DESTRUCTIVE = 2 # Active but safe (nuclei, ffuf)
|
|
25
|
+
DESTRUCTIVE = 3 # May modify state (sqlmap --dump)
|
|
26
|
+
EXPLOIT = 4 # Full exploitation (metasploit)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ToolResult:
|
|
31
|
+
"""Result of tool execution."""
|
|
32
|
+
tool: str
|
|
33
|
+
command: str
|
|
34
|
+
return_code: int
|
|
35
|
+
stdout: str
|
|
36
|
+
stderr: str
|
|
37
|
+
duration: float
|
|
38
|
+
parsed_output: Dict[str, Any] = field(default_factory=dict)
|
|
39
|
+
findings: List[Dict] = field(default_factory=list)
|
|
40
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
41
|
+
success: bool = True
|
|
42
|
+
error_message: Optional[str] = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class ToolDefinition:
|
|
47
|
+
"""Definition of a security tool."""
|
|
48
|
+
name: str
|
|
49
|
+
description: str
|
|
50
|
+
command_template: str
|
|
51
|
+
safety_level: SafetyLevel
|
|
52
|
+
category: str
|
|
53
|
+
needs_docker: bool = False
|
|
54
|
+
output_parser: Optional[Callable] = None
|
|
55
|
+
timeout: int = 300
|
|
56
|
+
installed: bool = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ToolRegistry:
|
|
60
|
+
"""Registry of available security tools."""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self.tools: Dict[str, ToolDefinition] = {}
|
|
64
|
+
self._register_default_tools()
|
|
65
|
+
|
|
66
|
+
def _register_default_tools(self):
|
|
67
|
+
"""Register default security tools."""
|
|
68
|
+
|
|
69
|
+
# Network Reconnaissance
|
|
70
|
+
self.register(ToolDefinition(
|
|
71
|
+
name="nmap",
|
|
72
|
+
description="Network discovery and security auditing",
|
|
73
|
+
command_template="nmap {options} {target}",
|
|
74
|
+
safety_level=SafetyLevel.READ_ONLY,
|
|
75
|
+
category="recon",
|
|
76
|
+
timeout=600
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
self.register(ToolDefinition(
|
|
80
|
+
name="masscan",
|
|
81
|
+
description="Fast port scanner",
|
|
82
|
+
command_template="masscan {target} -p{ports} {options}",
|
|
83
|
+
safety_level=SafetyLevel.READ_ONLY,
|
|
84
|
+
category="recon",
|
|
85
|
+
timeout=300
|
|
86
|
+
))
|
|
87
|
+
|
|
88
|
+
# Web Scanning
|
|
89
|
+
self.register(ToolDefinition(
|
|
90
|
+
name="nuclei",
|
|
91
|
+
description="Fast vulnerability scanner",
|
|
92
|
+
command_template="nuclei -u {target} {options}",
|
|
93
|
+
safety_level=SafetyLevel.NON_DESTRUCTIVE,
|
|
94
|
+
category="web",
|
|
95
|
+
timeout=600
|
|
96
|
+
))
|
|
97
|
+
|
|
98
|
+
self.register(ToolDefinition(
|
|
99
|
+
name="ffuf",
|
|
100
|
+
description="Fast web fuzzer",
|
|
101
|
+
command_template="ffuf -u {target}/FUZZ {options}",
|
|
102
|
+
safety_level=SafetyLevel.NON_DESTRUCTIVE,
|
|
103
|
+
category="web",
|
|
104
|
+
timeout=300
|
|
105
|
+
))
|
|
106
|
+
|
|
107
|
+
self.register(ToolDefinition(
|
|
108
|
+
name="gospider",
|
|
109
|
+
description="Web crawler",
|
|
110
|
+
command_template="gospider -s {target} {options}",
|
|
111
|
+
safety_level=SafetyLevel.READ_ONLY,
|
|
112
|
+
category="web",
|
|
113
|
+
timeout=300
|
|
114
|
+
))
|
|
115
|
+
|
|
116
|
+
# SQL Injection
|
|
117
|
+
self.register(ToolDefinition(
|
|
118
|
+
name="sqlmap",
|
|
119
|
+
description="Automatic SQL injection tool",
|
|
120
|
+
command_template="sqlmap -u {target} {options}",
|
|
121
|
+
safety_level=SafetyLevel.DESTRUCTIVE,
|
|
122
|
+
category="exploitation",
|
|
123
|
+
timeout=600
|
|
124
|
+
))
|
|
125
|
+
|
|
126
|
+
# Subdomain Enumeration
|
|
127
|
+
self.register(ToolDefinition(
|
|
128
|
+
name="subfinder",
|
|
129
|
+
description="Subdomain discovery",
|
|
130
|
+
command_template="subfinder -d {target} {options}",
|
|
131
|
+
safety_level=SafetyLevel.READ_ONLY,
|
|
132
|
+
category="recon",
|
|
133
|
+
timeout=300
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
self.register(ToolDefinition(
|
|
137
|
+
name="amass",
|
|
138
|
+
description="In-depth attack surface mapping",
|
|
139
|
+
command_template="amass enum -d {target} {options}",
|
|
140
|
+
safety_level=SafetyLevel.READ_ONLY,
|
|
141
|
+
category="recon",
|
|
142
|
+
timeout=600
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
# DNS
|
|
146
|
+
self.register(ToolDefinition(
|
|
147
|
+
name="dnsrecon",
|
|
148
|
+
description="DNS enumeration",
|
|
149
|
+
command_template="dnsrecon -d {target} {options}",
|
|
150
|
+
safety_level=SafetyLevel.READ_ONLY,
|
|
151
|
+
category="recon",
|
|
152
|
+
timeout=300
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
# SSL/TLS
|
|
156
|
+
self.register(ToolDefinition(
|
|
157
|
+
name="sslscan",
|
|
158
|
+
description="SSL/TLS scanner",
|
|
159
|
+
command_template="sslscan {target} {options}",
|
|
160
|
+
safety_level=SafetyLevel.READ_ONLY,
|
|
161
|
+
category="web",
|
|
162
|
+
timeout=120
|
|
163
|
+
))
|
|
164
|
+
|
|
165
|
+
# Content Discovery
|
|
166
|
+
self.register(ToolDefinition(
|
|
167
|
+
name="gobuster",
|
|
168
|
+
description="Directory/file brute forcer",
|
|
169
|
+
command_template="gobuster dir -u {target} {options}",
|
|
170
|
+
safety_level=SafetyLevel.NON_DESTRUCTIVE,
|
|
171
|
+
category="web",
|
|
172
|
+
timeout=300
|
|
173
|
+
))
|
|
174
|
+
|
|
175
|
+
# Vulnerability Scanning
|
|
176
|
+
self.register(ToolDefinition(
|
|
177
|
+
name="nikto",
|
|
178
|
+
description="Web server scanner",
|
|
179
|
+
command_template="nikto -h {target} {options}",
|
|
180
|
+
safety_level=SafetyLevel.NON_DESTRUCTIVE,
|
|
181
|
+
category="web",
|
|
182
|
+
timeout=600
|
|
183
|
+
))
|
|
184
|
+
|
|
185
|
+
def register(self, tool: ToolDefinition):
|
|
186
|
+
"""Register a new tool."""
|
|
187
|
+
self.tools[tool.name] = tool
|
|
188
|
+
|
|
189
|
+
def get(self, name: str) -> Optional[ToolDefinition]:
|
|
190
|
+
"""Get a tool by name."""
|
|
191
|
+
return self.tools.get(name)
|
|
192
|
+
|
|
193
|
+
def list_tools(self, category: Optional[str] = None, safety: Optional[SafetyLevel] = None) -> List[ToolDefinition]:
|
|
194
|
+
"""List available tools with optional filtering."""
|
|
195
|
+
tools = list(self.tools.values())
|
|
196
|
+
|
|
197
|
+
if category:
|
|
198
|
+
tools = [t for t in tools if t.category == category]
|
|
199
|
+
|
|
200
|
+
if safety:
|
|
201
|
+
tools = [t for t in tools if t.safety_level.value <= safety.value]
|
|
202
|
+
|
|
203
|
+
return tools
|
|
204
|
+
|
|
205
|
+
def check_installed(self, name: str) -> bool:
|
|
206
|
+
"""Check if a tool is installed."""
|
|
207
|
+
try:
|
|
208
|
+
result = subprocess.run(
|
|
209
|
+
['which', name],
|
|
210
|
+
capture_output=True,
|
|
211
|
+
timeout=5
|
|
212
|
+
)
|
|
213
|
+
return result.return_code == 0
|
|
214
|
+
except:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class ToolExecutor:
|
|
219
|
+
"""Executes security tools with safety controls."""
|
|
220
|
+
|
|
221
|
+
def __init__(
|
|
222
|
+
self,
|
|
223
|
+
registry: Optional[ToolRegistry] = None,
|
|
224
|
+
safety_level: SafetyLevel = SafetyLevel.NON_DESTRUCTIVE,
|
|
225
|
+
use_docker: bool = False,
|
|
226
|
+
output_dir: Optional[str] = None
|
|
227
|
+
):
|
|
228
|
+
self.registry = registry or ToolRegistry()
|
|
229
|
+
self.max_safety = safety_level
|
|
230
|
+
self.use_docker = use_docker
|
|
231
|
+
self.output_dir = output_dir or tempfile.gettempdir()
|
|
232
|
+
self.logger = logging.getLogger(__name__)
|
|
233
|
+
|
|
234
|
+
# Track execution for audit
|
|
235
|
+
self.execution_log: List[Dict] = []
|
|
236
|
+
|
|
237
|
+
def get_available_tools(self) -> List[Dict]:
|
|
238
|
+
"""Get list of available tools for LLM."""
|
|
239
|
+
tools = self.registry.list_tools(safety=self.max_safety)
|
|
240
|
+
return [
|
|
241
|
+
{
|
|
242
|
+
'name': t.name,
|
|
243
|
+
'description': t.description,
|
|
244
|
+
'category': t.category,
|
|
245
|
+
'safety': t.safety_level.name,
|
|
246
|
+
'installed': self.registry.check_installed(t.name)
|
|
247
|
+
}
|
|
248
|
+
for t in tools
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
async def execute(
|
|
252
|
+
self,
|
|
253
|
+
tool_name: str,
|
|
254
|
+
parameters: Dict[str, Any],
|
|
255
|
+
timeout: Optional[int] = None
|
|
256
|
+
) -> ToolResult:
|
|
257
|
+
"""
|
|
258
|
+
Execute a tool with the given parameters.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
tool_name: Name of the tool to execute
|
|
262
|
+
parameters: Tool parameters (target, options, etc.)
|
|
263
|
+
timeout: Override default timeout
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
ToolResult with output and parsed findings
|
|
267
|
+
"""
|
|
268
|
+
tool = self.registry.get(tool_name)
|
|
269
|
+
if not tool:
|
|
270
|
+
return ToolResult(
|
|
271
|
+
tool=tool_name,
|
|
272
|
+
command="",
|
|
273
|
+
return_code=-1,
|
|
274
|
+
stdout="",
|
|
275
|
+
stderr=f"Tool '{tool_name}' not found in registry",
|
|
276
|
+
duration=0,
|
|
277
|
+
success=False,
|
|
278
|
+
error_message=f"Unknown tool: {tool_name}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Safety check
|
|
282
|
+
if tool.safety_level.value > self.max_safety.value:
|
|
283
|
+
return ToolResult(
|
|
284
|
+
tool=tool_name,
|
|
285
|
+
command="",
|
|
286
|
+
return_code=-1,
|
|
287
|
+
stdout="",
|
|
288
|
+
stderr=f"Tool '{tool_name}' exceeds safety level ({tool.safety_level.name} > {self.max_safety.name})",
|
|
289
|
+
duration=0,
|
|
290
|
+
success=False,
|
|
291
|
+
error_message=f"Safety violation: {tool_name}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Build command
|
|
295
|
+
try:
|
|
296
|
+
command = self._build_command(tool, parameters)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
return ToolResult(
|
|
299
|
+
tool=tool_name,
|
|
300
|
+
command="",
|
|
301
|
+
return_code=-1,
|
|
302
|
+
stdout="",
|
|
303
|
+
stderr=str(e),
|
|
304
|
+
duration=0,
|
|
305
|
+
success=False,
|
|
306
|
+
error_message=f"Command build failed: {str(e)}"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
self.logger.info(f"Executing: {command}")
|
|
310
|
+
|
|
311
|
+
# Execute
|
|
312
|
+
start_time = datetime.now()
|
|
313
|
+
try:
|
|
314
|
+
if self.use_docker:
|
|
315
|
+
stdout, stderr, return_code = await self._execute_docker(tool, command, timeout or tool.timeout)
|
|
316
|
+
else:
|
|
317
|
+
stdout, stderr, return_code = await self._execute_local(command, timeout or tool.timeout)
|
|
318
|
+
|
|
319
|
+
duration = (datetime.now() - start_time).total_seconds()
|
|
320
|
+
|
|
321
|
+
# Parse output
|
|
322
|
+
parsed_output = self._parse_output(tool_name, stdout)
|
|
323
|
+
findings = self._extract_findings(tool_name, stdout, parsed_output)
|
|
324
|
+
|
|
325
|
+
# Log execution
|
|
326
|
+
self.execution_log.append({
|
|
327
|
+
'tool': tool_name,
|
|
328
|
+
'command': command,
|
|
329
|
+
'timestamp': datetime.now().isoformat(),
|
|
330
|
+
'duration': duration,
|
|
331
|
+
'success': return_code == 0
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
return ToolResult(
|
|
335
|
+
tool=tool_name,
|
|
336
|
+
command=command,
|
|
337
|
+
return_code=return_code,
|
|
338
|
+
stdout=stdout,
|
|
339
|
+
stderr=stderr,
|
|
340
|
+
duration=duration,
|
|
341
|
+
parsed_output=parsed_output,
|
|
342
|
+
findings=findings,
|
|
343
|
+
success=return_code == 0
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
except asyncio.TimeoutError:
|
|
347
|
+
duration = (datetime.now() - start_time).total_seconds()
|
|
348
|
+
return ToolResult(
|
|
349
|
+
tool=tool_name,
|
|
350
|
+
command=command,
|
|
351
|
+
return_code=-1,
|
|
352
|
+
stdout="",
|
|
353
|
+
stderr=f"Timeout after {timeout or tool.timeout} seconds",
|
|
354
|
+
duration=duration,
|
|
355
|
+
success=False,
|
|
356
|
+
error_message="Execution timeout"
|
|
357
|
+
)
|
|
358
|
+
except Exception as e:
|
|
359
|
+
duration = (datetime.now() - start_time).total_seconds()
|
|
360
|
+
return ToolResult(
|
|
361
|
+
tool=tool_name,
|
|
362
|
+
command=command,
|
|
363
|
+
return_code=-1,
|
|
364
|
+
stdout="",
|
|
365
|
+
stderr=str(e),
|
|
366
|
+
duration=duration,
|
|
367
|
+
success=False,
|
|
368
|
+
error_message=str(e)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def _build_command(self, tool: ToolDefinition, parameters: Dict[str, Any]) -> str:
|
|
372
|
+
"""Build the command string from template."""
|
|
373
|
+
target = parameters.get('target', '')
|
|
374
|
+
options = parameters.get('options', '')
|
|
375
|
+
|
|
376
|
+
# Sanitize inputs
|
|
377
|
+
target = shlex.quote(target) if target else ''
|
|
378
|
+
|
|
379
|
+
command = tool.command_template.format(
|
|
380
|
+
target=target,
|
|
381
|
+
options=options,
|
|
382
|
+
ports=parameters.get('ports', '1-65535'),
|
|
383
|
+
**parameters
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
return command
|
|
387
|
+
|
|
388
|
+
async def _execute_local(self, command: str, timeout: int) -> tuple:
|
|
389
|
+
"""Execute command locally."""
|
|
390
|
+
process = await asyncio.create_subprocess_shell(
|
|
391
|
+
command,
|
|
392
|
+
stdout=asyncio.subprocess.PIPE,
|
|
393
|
+
stderr=asyncio.subprocess.PIPE
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
stdout, stderr = await asyncio.wait_for(
|
|
398
|
+
process.communicate(),
|
|
399
|
+
timeout=timeout
|
|
400
|
+
)
|
|
401
|
+
return (
|
|
402
|
+
stdout.decode('utf-8', errors='ignore'),
|
|
403
|
+
stderr.decode('utf-8', errors='ignore'),
|
|
404
|
+
process.returncode
|
|
405
|
+
)
|
|
406
|
+
except asyncio.TimeoutError:
|
|
407
|
+
process.kill()
|
|
408
|
+
raise
|
|
409
|
+
|
|
410
|
+
async def _execute_docker(self, tool: ToolDefinition, command: str, timeout: int) -> tuple:
|
|
411
|
+
"""Execute in Docker container."""
|
|
412
|
+
# Docker execution for isolation
|
|
413
|
+
docker_cmd = f"docker run --rm --network=host {tool.name} {command}"
|
|
414
|
+
return await self._execute_local(docker_cmd, timeout)
|
|
415
|
+
|
|
416
|
+
def _parse_output(self, tool_name: str, stdout: str) -> Dict[str, Any]:
|
|
417
|
+
"""Parse tool output into structured format."""
|
|
418
|
+
parsers = {
|
|
419
|
+
'nmap': self._parse_nmap,
|
|
420
|
+
'nuclei': self._parse_nuclei,
|
|
421
|
+
'subfinder': self._parse_subfinder,
|
|
422
|
+
# Add more parsers
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
parser = parsers.get(tool_name)
|
|
426
|
+
if parser:
|
|
427
|
+
try:
|
|
428
|
+
return parser(stdout)
|
|
429
|
+
except Exception as e:
|
|
430
|
+
self.logger.error(f"Parse error for {tool_name}: {e}")
|
|
431
|
+
|
|
432
|
+
return {'raw': stdout}
|
|
433
|
+
|
|
434
|
+
def _parse_nmap(self, output: str) -> Dict:
|
|
435
|
+
"""Parse nmap output."""
|
|
436
|
+
ports = []
|
|
437
|
+
for line in output.split('\n'):
|
|
438
|
+
if '/tcp' in line and 'open' in line:
|
|
439
|
+
parts = line.split()
|
|
440
|
+
if len(parts) >= 3:
|
|
441
|
+
port = parts[0].split('/')[0]
|
|
442
|
+
service = parts[2]
|
|
443
|
+
version = ' '.join(parts[3:]) if len(parts) > 3 else ''
|
|
444
|
+
ports.append({
|
|
445
|
+
'port': port,
|
|
446
|
+
'service': service,
|
|
447
|
+
'version': version
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
return {'open_ports': ports}
|
|
451
|
+
|
|
452
|
+
def _parse_nuclei(self, output: str) -> Dict:
|
|
453
|
+
"""Parse nuclei output."""
|
|
454
|
+
findings = []
|
|
455
|
+
for line in output.split('\n'):
|
|
456
|
+
if '[' in line and ']' in line:
|
|
457
|
+
# Basic parsing, enhance as needed
|
|
458
|
+
findings.append({'raw': line})
|
|
459
|
+
|
|
460
|
+
return {'findings': findings}
|
|
461
|
+
|
|
462
|
+
def _parse_subfinder(self, output: str) -> Dict:
|
|
463
|
+
"""Parse subfinder output."""
|
|
464
|
+
subdomains = [line.strip() for line in output.split('\n') if line.strip()]
|
|
465
|
+
return {'subdomains': subdomains}
|
|
466
|
+
|
|
467
|
+
def _extract_findings(self, tool_name: str, stdout: str, parsed: Dict) -> List[Dict]:
|
|
468
|
+
"""Extract security findings from output."""
|
|
469
|
+
findings = []
|
|
470
|
+
|
|
471
|
+
# Add generic findings based on tool
|
|
472
|
+
if tool_name == 'nmap' and parsed.get('open_ports'):
|
|
473
|
+
for port in parsed['open_ports']:
|
|
474
|
+
findings.append({
|
|
475
|
+
'type': 'open_port',
|
|
476
|
+
'severity': 'info',
|
|
477
|
+
'details': port
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
elif tool_name == 'nuclei' and parsed.get('findings'):
|
|
481
|
+
for finding in parsed['findings']:
|
|
482
|
+
findings.append({
|
|
483
|
+
'type': 'vulnerability',
|
|
484
|
+
'severity': 'unknown',
|
|
485
|
+
'details': finding
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
return findings
|
backends/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LLM Backends for Zen AI
|
|
3
|
+
Supports multiple free and authenticated LLM providers
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .chatgpt_direct import ChatGPTDirectBackend
|
|
7
|
+
from .claude_direct import ClaudeDirectBackend
|
|
8
|
+
from .duckduckgo import DuckDuckGoBackend
|
|
9
|
+
from .openrouter import OpenRouterBackend
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DuckDuckGoBackend",
|
|
13
|
+
"OpenRouterBackend",
|
|
14
|
+
"ChatGPTDirectBackend",
|
|
15
|
+
"ClaudeDirectBackend",
|
|
16
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
ChatGPT Direct Backend (Reverse Engineered)
|
|
4
|
+
Uses internal WebSocket/HTTP API with session token
|
|
5
|
+
Requires manual token extraction once
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import random
|
|
11
|
+
import sys
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import aiohttp
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
17
|
+
from utils.async_fixes import safe_close_session
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("ZenAI")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ChatGPTDirectBackend:
|
|
23
|
+
"""
|
|
24
|
+
Direct ChatGPT API Backend
|
|
25
|
+
- Uses access token from browser session
|
|
26
|
+
- No browser automation overhead
|
|
27
|
+
- Requires token renewal every 2-4 weeks
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, access_token: str = None):
|
|
31
|
+
self.name = "ChatGPT-Direct"
|
|
32
|
+
self.priority = 3 # Lower priority (requires manual setup)
|
|
33
|
+
self.access_token = access_token
|
|
34
|
+
self.refresh_token = None
|
|
35
|
+
self.conversation_id = None
|
|
36
|
+
self.session: Optional[aiohttp.ClientSession] = None
|
|
37
|
+
|
|
38
|
+
async def __aenter__(self):
|
|
39
|
+
self.session = aiohttp.ClientSession()
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
43
|
+
if self.session:
|
|
44
|
+
await safe_close_session(self.session)
|
|
45
|
+
|
|
46
|
+
async def chat(self, prompt: str, context: str = "") -> Optional[str]:
|
|
47
|
+
"""Send chat request via direct API"""
|
|
48
|
+
if not self.access_token:
|
|
49
|
+
logger.warning("[ChatGPT-Direct] No access token provided")
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
headers = {
|
|
54
|
+
"Authorization": f"Bearer {self.access_token}",
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Generate unique IDs
|
|
60
|
+
msg_id = str(random.randint(1000000000, 9999999999))
|
|
61
|
+
parent_id = str(random.randint(1000000000, 9999999999))
|
|
62
|
+
|
|
63
|
+
payload = {
|
|
64
|
+
"action": "next",
|
|
65
|
+
"messages": [
|
|
66
|
+
{
|
|
67
|
+
"id": msg_id,
|
|
68
|
+
"author": {"role": "user"},
|
|
69
|
+
"content": {"content_type": "text", "parts": [prompt]},
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"conversation_id": self.conversation_id,
|
|
73
|
+
"parent_message_id": parent_id,
|
|
74
|
+
"model": "text-davinci-002-render-sha",
|
|
75
|
+
"timezone_offset_min": -120,
|
|
76
|
+
"history_and_training_disabled": False,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logger.info("[ChatGPT-Direct] Sending request...")
|
|
80
|
+
|
|
81
|
+
async with self.session.post(
|
|
82
|
+
"https://chat.openai.com/backend-api/conversation",
|
|
83
|
+
json=payload,
|
|
84
|
+
headers=headers,
|
|
85
|
+
timeout=aiohttp.ClientTimeout(total=60),
|
|
86
|
+
) as resp:
|
|
87
|
+
if resp.status == 401:
|
|
88
|
+
logger.error("[ChatGPT-Direct] Token expired!")
|
|
89
|
+
return None
|
|
90
|
+
elif resp.status == 429:
|
|
91
|
+
logger.warning("[ChatGPT-Direct] Rate limited")
|
|
92
|
+
return None
|
|
93
|
+
elif resp.status != 200:
|
|
94
|
+
logger.error(f"[ChatGPT-Direct] HTTP Error: {resp.status}")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
# ChatGPT streams events
|
|
98
|
+
text = await resp.text()
|
|
99
|
+
lines = text.split("\n")
|
|
100
|
+
|
|
101
|
+
full_response = ""
|
|
102
|
+
for line in lines:
|
|
103
|
+
if line.startswith("data: "):
|
|
104
|
+
try:
|
|
105
|
+
import json
|
|
106
|
+
|
|
107
|
+
data = json.loads(line[6:])
|
|
108
|
+
if data.get("message") and data["message"].get("content"):
|
|
109
|
+
full_response = data["message"]["content"]["parts"][0]
|
|
110
|
+
# Update conversation ID for context
|
|
111
|
+
if data.get("conversation_id"):
|
|
112
|
+
self.conversation_id = data["conversation_id"]
|
|
113
|
+
except:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
return full_response
|
|
117
|
+
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"[ChatGPT-Direct] Error: {e}")
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
async def health_check(self) -> bool:
|
|
123
|
+
"""Check if backend is available"""
|
|
124
|
+
if not self.access_token:
|
|
125
|
+
return False
|
|
126
|
+
try:
|
|
127
|
+
headers = {"Authorization": f"Bearer {self.access_token}"}
|
|
128
|
+
async with self.session.get(
|
|
129
|
+
"https://chat.openai.com/backend-api/models", headers=headers
|
|
130
|
+
) as resp:
|
|
131
|
+
return resp.status == 200
|
|
132
|
+
except:
|
|
133
|
+
return False
|