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,498 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool Orchestrator - Integration with Classic Pentesting Tools
|
|
3
|
+
Connects Zen AI Pentest with containerized classic tools via Integration Bridge
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
import aiohttp
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.table import Table
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
console = Console()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ToolConfig:
|
|
24
|
+
"""Configuration for a pentesting tool"""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
bridge_endpoint: str
|
|
28
|
+
description: str
|
|
29
|
+
typical_duration: int # seconds
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Tool registry
|
|
33
|
+
PENTEST_TOOLS = {
|
|
34
|
+
"nmap": ToolConfig(
|
|
35
|
+
name="nmap",
|
|
36
|
+
bridge_endpoint="/api/v1/scan/nmap",
|
|
37
|
+
description="Network port scanner with NSE scripting",
|
|
38
|
+
typical_duration=300,
|
|
39
|
+
),
|
|
40
|
+
"sqlmap": ToolConfig(
|
|
41
|
+
name="sqlmap",
|
|
42
|
+
bridge_endpoint="/api/v1/scan/sqlmap",
|
|
43
|
+
description="Automated SQL injection scanner",
|
|
44
|
+
typical_duration=600,
|
|
45
|
+
),
|
|
46
|
+
"metasploit": ToolConfig(
|
|
47
|
+
name="metasploit",
|
|
48
|
+
bridge_endpoint="/api/v1/scan/metasploit",
|
|
49
|
+
description="Exploitation framework",
|
|
50
|
+
typical_duration=300,
|
|
51
|
+
),
|
|
52
|
+
"nuclei": ToolConfig(
|
|
53
|
+
name="nuclei",
|
|
54
|
+
bridge_endpoint="/api/v1/scan/nuclei",
|
|
55
|
+
description="Fast vulnerability scanner",
|
|
56
|
+
typical_duration=600,
|
|
57
|
+
),
|
|
58
|
+
"gobuster": ToolConfig(
|
|
59
|
+
name="gobuster",
|
|
60
|
+
bridge_endpoint="/api/v1/scan/gobuster",
|
|
61
|
+
description="Directory/file bruteforcer",
|
|
62
|
+
typical_duration=180,
|
|
63
|
+
),
|
|
64
|
+
"amass": ToolConfig(
|
|
65
|
+
name="amass",
|
|
66
|
+
bridge_endpoint="/api/v1/scan/amass",
|
|
67
|
+
description="Subdomain enumeration",
|
|
68
|
+
typical_duration=300,
|
|
69
|
+
),
|
|
70
|
+
"wpscan": ToolConfig(
|
|
71
|
+
name="wpscan",
|
|
72
|
+
bridge_endpoint="/api/v1/scan/wpscan",
|
|
73
|
+
description="WordPress vulnerability scanner",
|
|
74
|
+
typical_duration=300,
|
|
75
|
+
),
|
|
76
|
+
"nikto": ToolConfig(
|
|
77
|
+
name="nikto",
|
|
78
|
+
bridge_endpoint="/api/v1/scan/nikto",
|
|
79
|
+
description="Web server scanner",
|
|
80
|
+
typical_duration=600,
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ToolOrchestrator:
|
|
86
|
+
"""
|
|
87
|
+
Orchestrates classic pentesting tools through the Integration Bridge
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(self, bridge_url: str = "http://integration-bridge:8080"):
|
|
91
|
+
self.bridge_url = bridge_url
|
|
92
|
+
self.session: Optional[aiohttp.ClientSession] = None
|
|
93
|
+
self.active_scans: Dict[str, Dict[str, Any]] = {}
|
|
94
|
+
|
|
95
|
+
async def __aenter__(self):
|
|
96
|
+
self.session = aiohttp.ClientSession()
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
100
|
+
if self.session:
|
|
101
|
+
await self.session.close()
|
|
102
|
+
|
|
103
|
+
async def health_check(self) -> bool:
|
|
104
|
+
"""Check if integration bridge is available"""
|
|
105
|
+
try:
|
|
106
|
+
async with self.session.get(f"{self.bridge_url}/health") as resp:
|
|
107
|
+
return resp.status == 200
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Bridge health check failed: {e}")
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
async def scan_with_nmap(
|
|
113
|
+
self,
|
|
114
|
+
target: str,
|
|
115
|
+
scan_type: str = "tcp_syn",
|
|
116
|
+
ports: str = "top-1000",
|
|
117
|
+
scripts: Optional[List[str]] = None,
|
|
118
|
+
) -> Dict[str, Any]:
|
|
119
|
+
"""
|
|
120
|
+
Run Nmap scan against target
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
target: Target IP/hostname
|
|
124
|
+
scan_type: tcp_syn, tcp_connect, udp, comprehensive
|
|
125
|
+
ports: Port specification (top-1000, all, or specific range)
|
|
126
|
+
scripts: List of NSE scripts to run
|
|
127
|
+
"""
|
|
128
|
+
payload = {
|
|
129
|
+
"target": target,
|
|
130
|
+
"scan_type": scan_type,
|
|
131
|
+
"ports": ports,
|
|
132
|
+
"scripts": scripts or [],
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return await self._trigger_scan("nmap", payload)
|
|
136
|
+
|
|
137
|
+
async def scan_with_sqlmap(
|
|
138
|
+
self,
|
|
139
|
+
url: str,
|
|
140
|
+
method: str = "GET",
|
|
141
|
+
data: Optional[str] = None,
|
|
142
|
+
level: int = 1,
|
|
143
|
+
risk: int = 1,
|
|
144
|
+
) -> Dict[str, Any]:
|
|
145
|
+
"""
|
|
146
|
+
Run SQLMap scan against URL
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
url: Target URL
|
|
150
|
+
method: HTTP method
|
|
151
|
+
data: POST data
|
|
152
|
+
level: Test level (1-5)
|
|
153
|
+
risk: Risk level (1-3)
|
|
154
|
+
"""
|
|
155
|
+
payload = {
|
|
156
|
+
"target": url,
|
|
157
|
+
"url": url,
|
|
158
|
+
"method": method,
|
|
159
|
+
"data": data,
|
|
160
|
+
"level": level,
|
|
161
|
+
"risk": risk,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return await self._trigger_scan("sqlmap", payload)
|
|
165
|
+
|
|
166
|
+
async def scan_with_nuclei(
|
|
167
|
+
self,
|
|
168
|
+
target: str,
|
|
169
|
+
severity: Optional[str] = None,
|
|
170
|
+
templates: Optional[List[str]] = None,
|
|
171
|
+
concurrency: int = 50,
|
|
172
|
+
) -> Dict[str, Any]:
|
|
173
|
+
"""
|
|
174
|
+
Run Nuclei vulnerability scan
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
target: Target URL
|
|
178
|
+
severity: Filter by severity (info, low, medium, high, critical)
|
|
179
|
+
templates: Specific templates to run
|
|
180
|
+
concurrency: Number of concurrent requests
|
|
181
|
+
"""
|
|
182
|
+
payload = {
|
|
183
|
+
"target": target,
|
|
184
|
+
"options": {
|
|
185
|
+
"severity": severity,
|
|
186
|
+
"templates": templates or [],
|
|
187
|
+
"concurrency": concurrency,
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return await self._trigger_scan("nuclei", payload)
|
|
192
|
+
|
|
193
|
+
async def scan_with_gobuster(
|
|
194
|
+
self,
|
|
195
|
+
target: str,
|
|
196
|
+
mode: str = "dir",
|
|
197
|
+
wordlist: str = "/wordlists/dirb/common.txt",
|
|
198
|
+
extensions: Optional[str] = None,
|
|
199
|
+
) -> Dict[str, Any]:
|
|
200
|
+
"""
|
|
201
|
+
Run Gobuster directory scan
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
target: Target URL
|
|
205
|
+
mode: dir, dns, fuzz
|
|
206
|
+
wordlist: Wordlist path
|
|
207
|
+
extensions: File extensions to search
|
|
208
|
+
"""
|
|
209
|
+
payload = {
|
|
210
|
+
"target": target,
|
|
211
|
+
"options": {"mode": mode, "wordlist": wordlist, "extensions": extensions},
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return await self._trigger_scan("gobuster", payload)
|
|
215
|
+
|
|
216
|
+
async def enumerate_subdomains(
|
|
217
|
+
self, domain: str, active: bool = False
|
|
218
|
+
) -> Dict[str, Any]:
|
|
219
|
+
"""
|
|
220
|
+
Enumerate subdomains with Amass
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
domain: Target domain
|
|
224
|
+
active: Enable active reconnaissance
|
|
225
|
+
"""
|
|
226
|
+
payload = {"target": domain, "options": {"active": active}}
|
|
227
|
+
|
|
228
|
+
return await self._trigger_scan("amass", payload)
|
|
229
|
+
|
|
230
|
+
async def scan_wordpress(
|
|
231
|
+
self, url: str, enumerate_plugins: bool = True
|
|
232
|
+
) -> Dict[str, Any]:
|
|
233
|
+
"""
|
|
234
|
+
Scan WordPress site with WPScan
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
url: WordPress site URL
|
|
238
|
+
enumerate_plugins: Enumerate plugins/themes
|
|
239
|
+
"""
|
|
240
|
+
payload = {"target": url, "options": {"enumerate": enumerate_plugins}}
|
|
241
|
+
|
|
242
|
+
return await self._trigger_scan("wpscan", payload)
|
|
243
|
+
|
|
244
|
+
async def run_msf_module(
|
|
245
|
+
self,
|
|
246
|
+
module: str,
|
|
247
|
+
rhosts: str,
|
|
248
|
+
rport: int = 443,
|
|
249
|
+
options: Optional[Dict[str, Any]] = None,
|
|
250
|
+
) -> Dict[str, Any]:
|
|
251
|
+
"""
|
|
252
|
+
Run Metasploit module
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
module: Metasploit module path
|
|
256
|
+
rhosts: Target hosts
|
|
257
|
+
rport: Target port
|
|
258
|
+
options: Additional module options
|
|
259
|
+
"""
|
|
260
|
+
payload = {
|
|
261
|
+
"module": module,
|
|
262
|
+
"rhosts": rhosts,
|
|
263
|
+
"rport": rport,
|
|
264
|
+
"options": options or {},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return await self._trigger_scan("metasploit", payload)
|
|
268
|
+
|
|
269
|
+
async def _trigger_scan(self, tool: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
270
|
+
"""Trigger scan via integration bridge"""
|
|
271
|
+
tool_config = PENTEST_TOOLS.get(tool)
|
|
272
|
+
if not tool_config:
|
|
273
|
+
raise ValueError(f"Unknown tool: {tool}")
|
|
274
|
+
|
|
275
|
+
url = f"{self.bridge_url}{tool_config.bridge_endpoint}"
|
|
276
|
+
|
|
277
|
+
logger.info(f"Triggering {tool} scan: {payload.get('target', 'N/A')}")
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
async with self.session.post(url, json=payload) as resp:
|
|
281
|
+
if resp.status != 200:
|
|
282
|
+
error_text = await resp.text()
|
|
283
|
+
raise RuntimeError(f"Bridge returned {resp.status}: {error_text}")
|
|
284
|
+
|
|
285
|
+
result = await resp.json()
|
|
286
|
+
self.active_scans[result["scan_id"]] = {
|
|
287
|
+
"tool": tool,
|
|
288
|
+
"target": payload.get("target", "N/A"),
|
|
289
|
+
"status": "triggered",
|
|
290
|
+
"start_time": datetime.utcnow(),
|
|
291
|
+
}
|
|
292
|
+
return result
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.error(f"Failed to trigger {tool} scan: {e}")
|
|
296
|
+
raise
|
|
297
|
+
|
|
298
|
+
async def wait_for_scan(
|
|
299
|
+
self, scan_id: str, poll_interval: int = 10, timeout: int = 3600
|
|
300
|
+
) -> Dict[str, Any]:
|
|
301
|
+
"""
|
|
302
|
+
Wait for scan to complete
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
scan_id: Scan ID to wait for
|
|
306
|
+
poll_interval: Seconds between status checks
|
|
307
|
+
timeout: Maximum wait time in seconds
|
|
308
|
+
"""
|
|
309
|
+
start_time = datetime.utcnow()
|
|
310
|
+
|
|
311
|
+
while True:
|
|
312
|
+
# Check timeout
|
|
313
|
+
elapsed = (datetime.utcnow() - start_time).total_seconds()
|
|
314
|
+
if elapsed > timeout:
|
|
315
|
+
raise TimeoutError(f"Scan {scan_id} timed out after {timeout}s")
|
|
316
|
+
|
|
317
|
+
# Get status
|
|
318
|
+
status = await self.get_scan_status(scan_id)
|
|
319
|
+
current_status = status.get("status", "unknown")
|
|
320
|
+
|
|
321
|
+
if current_status in ("completed", "failed", "cancelled"):
|
|
322
|
+
return status
|
|
323
|
+
|
|
324
|
+
logger.debug(f"Scan {scan_id} status: {current_status}, waiting...")
|
|
325
|
+
await asyncio.sleep(poll_interval)
|
|
326
|
+
|
|
327
|
+
async def get_scan_status(self, scan_id: str) -> Dict[str, Any]:
|
|
328
|
+
"""Get scan status from bridge"""
|
|
329
|
+
url = f"{self.bridge_url}/api/v1/scan/{scan_id}"
|
|
330
|
+
|
|
331
|
+
async with self.session.get(url) as resp:
|
|
332
|
+
if resp.status == 404:
|
|
333
|
+
return {"status": "not_found"}
|
|
334
|
+
resp.raise_for_status()
|
|
335
|
+
return await resp.json()
|
|
336
|
+
|
|
337
|
+
async def get_scan_results(self, scan_id: str) -> Dict[str, Any]:
|
|
338
|
+
"""Get parsed scan results"""
|
|
339
|
+
url = f"{self.bridge_url}/api/v1/scan/{scan_id}/results"
|
|
340
|
+
|
|
341
|
+
async with self.session.get(url) as resp:
|
|
342
|
+
resp.raise_for_status()
|
|
343
|
+
return await resp.json()
|
|
344
|
+
|
|
345
|
+
async def run_comprehensive_scan(
|
|
346
|
+
self, target: str, scan_type: str = "web", tools: Optional[List[str]] = None
|
|
347
|
+
) -> Dict[str, Any]:
|
|
348
|
+
"""
|
|
349
|
+
Run comprehensive multi-tool scan
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
target: Target host/URL
|
|
353
|
+
scan_type: web, network, full
|
|
354
|
+
tools: Specific tools to use (None = auto-select)
|
|
355
|
+
"""
|
|
356
|
+
results = {
|
|
357
|
+
"target": target,
|
|
358
|
+
"scan_type": scan_type,
|
|
359
|
+
"start_time": datetime.utcnow().isoformat(),
|
|
360
|
+
"scans": {},
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
# Auto-select tools based on scan type
|
|
364
|
+
if not tools:
|
|
365
|
+
if scan_type == "web":
|
|
366
|
+
tools = ["nmap", "nuclei", "gobuster", "nikto"]
|
|
367
|
+
elif scan_type == "network":
|
|
368
|
+
tools = ["nmap", "amass"]
|
|
369
|
+
elif scan_type == "full":
|
|
370
|
+
tools = ["nmap", "nuclei", "gobuster", "amass", "nikto"]
|
|
371
|
+
|
|
372
|
+
console.print(
|
|
373
|
+
f"[bold green]Starting comprehensive {scan_type} scan against {target}[/]"
|
|
374
|
+
)
|
|
375
|
+
console.print(f"[dim]Tools: {', '.join(tools)}[/]\n")
|
|
376
|
+
|
|
377
|
+
# Run scans concurrently
|
|
378
|
+
scan_tasks = []
|
|
379
|
+
for tool in tools:
|
|
380
|
+
if tool in PENTEST_TOOLS:
|
|
381
|
+
task = self._run_tool_with_progress(tool, target)
|
|
382
|
+
scan_tasks.append((tool, task))
|
|
383
|
+
|
|
384
|
+
# Execute all scans
|
|
385
|
+
for tool, task in scan_tasks:
|
|
386
|
+
try:
|
|
387
|
+
scan_result = await task
|
|
388
|
+
results["scans"][tool] = scan_result
|
|
389
|
+
console.print(f"[green]✓[/] {tool} completed")
|
|
390
|
+
except Exception as e:
|
|
391
|
+
console.print(f"[red]✗[/] {tool} failed: {e}")
|
|
392
|
+
results["scans"][tool] = {"error": str(e)}
|
|
393
|
+
|
|
394
|
+
results["end_time"] = datetime.utcnow().isoformat()
|
|
395
|
+
|
|
396
|
+
return results
|
|
397
|
+
|
|
398
|
+
async def _run_tool_with_progress(self, tool: str, target: str) -> Dict[str, Any]:
|
|
399
|
+
"""Run tool with progress indication"""
|
|
400
|
+
config = PENTEST_TOOLS[tool]
|
|
401
|
+
|
|
402
|
+
with console.status(f"[bold cyan]Running {tool}...[/]"):
|
|
403
|
+
# Trigger scan
|
|
404
|
+
if tool == "nmap":
|
|
405
|
+
result = await self.scan_with_nmap(target)
|
|
406
|
+
elif tool == "nuclei":
|
|
407
|
+
result = await self.scan_with_nuclei(target)
|
|
408
|
+
elif tool == "gobuster":
|
|
409
|
+
result = await self.scan_with_gobuster(target)
|
|
410
|
+
elif tool == "amass":
|
|
411
|
+
result = await self.enumerate_subdomains(target)
|
|
412
|
+
elif tool == "wpscan":
|
|
413
|
+
result = await self.scan_wordpress(target)
|
|
414
|
+
else:
|
|
415
|
+
raise ValueError(f"Tool {tool} not implemented")
|
|
416
|
+
|
|
417
|
+
scan_id = result["scan_id"]
|
|
418
|
+
|
|
419
|
+
# Wait for completion
|
|
420
|
+
final_status = await self.wait_for_scan(
|
|
421
|
+
scan_id, poll_interval=5, timeout=config.typical_duration * 2
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Get results
|
|
425
|
+
try:
|
|
426
|
+
scan_results = await self.get_scan_results(scan_id)
|
|
427
|
+
except Exception:
|
|
428
|
+
scan_results = {
|
|
429
|
+
"status": "completed",
|
|
430
|
+
"raw": final_status.get("raw_output", ""),
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
"scan_id": scan_id,
|
|
435
|
+
"status": final_status.get("status"),
|
|
436
|
+
"results": scan_results,
|
|
437
|
+
"duration": config.typical_duration,
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
def display_scan_summary(self, results: Dict[str, Any]):
|
|
441
|
+
"""Display scan results in formatted table"""
|
|
442
|
+
table = Table(title=f"Scan Results: {results['target']}")
|
|
443
|
+
table.add_column("Tool", style="cyan")
|
|
444
|
+
table.add_column("Status", style="green")
|
|
445
|
+
table.add_column("Findings", style="yellow")
|
|
446
|
+
table.add_column("Duration", style="dim")
|
|
447
|
+
|
|
448
|
+
for tool, result in results.get("scans", {}).items():
|
|
449
|
+
status = result.get("status", "unknown")
|
|
450
|
+
status_emoji = (
|
|
451
|
+
"✓" if status == "completed" else "✗" if "error" in result else "⏳"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# Count findings if available
|
|
455
|
+
findings = "N/A"
|
|
456
|
+
if "results" in result:
|
|
457
|
+
if isinstance(result["results"], list):
|
|
458
|
+
findings = str(len(result["results"]))
|
|
459
|
+
elif isinstance(result["results"], dict):
|
|
460
|
+
findings = str(len(result["results"].get("hosts", [])))
|
|
461
|
+
|
|
462
|
+
duration = str(result.get("duration", "N/A"))
|
|
463
|
+
|
|
464
|
+
table.add_row(tool, f"{status_emoji} {status}", findings, f"{duration}s")
|
|
465
|
+
|
|
466
|
+
console.print(table)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
# Convenience functions for direct usage
|
|
470
|
+
async def quick_nmap_scan(
|
|
471
|
+
target: str, bridge_url: str = "http://localhost:8080"
|
|
472
|
+
) -> Dict[str, Any]:
|
|
473
|
+
"""Quick Nmap scan wrapper"""
|
|
474
|
+
async with ToolOrchestrator(bridge_url) as orch:
|
|
475
|
+
result = await orch.scan_with_nmap(target, scan_type="quick")
|
|
476
|
+
return await orch.wait_for_scan(result["scan_id"])
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
async def quick_web_scan(
|
|
480
|
+
url: str, bridge_url: str = "http://localhost:8080"
|
|
481
|
+
) -> Dict[str, Any]:
|
|
482
|
+
"""Quick web vulnerability scan with Nuclei"""
|
|
483
|
+
async with ToolOrchestrator(bridge_url) as orch:
|
|
484
|
+
result = await orch.scan_with_nuclei(url)
|
|
485
|
+
return await orch.wait_for_scan(result["scan_id"])
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
async def find_subdomains(
|
|
489
|
+
domain: str, bridge_url: str = "http://localhost:8080"
|
|
490
|
+
) -> List[str]:
|
|
491
|
+
"""Enumerate subdomains with Amass"""
|
|
492
|
+
async with ToolOrchestrator(bridge_url) as orch:
|
|
493
|
+
result = await orch.enumerate_subdomains(domain, active=False)
|
|
494
|
+
status = await orch.wait_for_scan(result["scan_id"])
|
|
495
|
+
|
|
496
|
+
# Parse results
|
|
497
|
+
results = await orch.get_scan_results(result["scan_id"])
|
|
498
|
+
return results.get("results", [])
|