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,480 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Nuclei Integration Module
|
|
4
|
+
Template management and vulnerability scanning with Nuclei
|
|
5
|
+
Author: SHAdd0WTAka
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import subprocess
|
|
14
|
+
from dataclasses import asdict, dataclass
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("ZenAI")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class NucleiTemplate:
|
|
23
|
+
"""Represents a Nuclei template"""
|
|
24
|
+
|
|
25
|
+
id: str
|
|
26
|
+
name: str
|
|
27
|
+
severity: str
|
|
28
|
+
description: str
|
|
29
|
+
tags: List[str]
|
|
30
|
+
author: str
|
|
31
|
+
reference: List[str]
|
|
32
|
+
classification: Dict
|
|
33
|
+
template_path: str
|
|
34
|
+
protocol: str
|
|
35
|
+
cvss_score: Optional[float] = None
|
|
36
|
+
cwe: List[str] = None
|
|
37
|
+
cve: List[str] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class NucleiFinding:
|
|
42
|
+
"""Represents a Nuclei scan finding"""
|
|
43
|
+
|
|
44
|
+
template_id: str
|
|
45
|
+
template_name: str
|
|
46
|
+
severity: str
|
|
47
|
+
host: str
|
|
48
|
+
matched_at: str
|
|
49
|
+
extract_results: List[str]
|
|
50
|
+
timestamp: str
|
|
51
|
+
curl_command: Optional[str] = None
|
|
52
|
+
request: Optional[str] = None
|
|
53
|
+
response: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class NucleiIntegration:
|
|
57
|
+
"""
|
|
58
|
+
Integration with ProjectDiscovery Nuclei scanner
|
|
59
|
+
Manages templates and executes scans
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
SEVERITY_ORDER = {"critical": 5, "high": 4, "medium": 3, "low": 2, "info": 1}
|
|
63
|
+
|
|
64
|
+
def __init__(self, orchestrator=None, nuclei_path: str = "nuclei"):
|
|
65
|
+
self.orchestrator = orchestrator
|
|
66
|
+
self.nuclei_path = nuclei_path
|
|
67
|
+
self.templates_dir = "data/nuclei_templates"
|
|
68
|
+
self.custom_templates = []
|
|
69
|
+
self.scan_results = []
|
|
70
|
+
|
|
71
|
+
# Ensure directories exist
|
|
72
|
+
os.makedirs(self.templates_dir, exist_ok=True)
|
|
73
|
+
|
|
74
|
+
async def check_nuclei_installed(self) -> bool:
|
|
75
|
+
"""Check if Nuclei is installed"""
|
|
76
|
+
try:
|
|
77
|
+
result = subprocess.run(
|
|
78
|
+
[self.nuclei_path, "-version"],
|
|
79
|
+
capture_output=True,
|
|
80
|
+
text=True,
|
|
81
|
+
timeout=10,
|
|
82
|
+
)
|
|
83
|
+
return result.returncode == 0
|
|
84
|
+
except:
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
async def update_templates(self) -> bool:
|
|
88
|
+
"""Update Nuclei templates from official repository"""
|
|
89
|
+
try:
|
|
90
|
+
logger.info("[Nuclei] Updating templates...")
|
|
91
|
+
result = subprocess.run(
|
|
92
|
+
[self.nuclei_path, "-update-templates"],
|
|
93
|
+
capture_output=True,
|
|
94
|
+
text=True,
|
|
95
|
+
timeout=300,
|
|
96
|
+
)
|
|
97
|
+
success = result.returncode == 0
|
|
98
|
+
if success:
|
|
99
|
+
logger.info("[Nuclei] Templates updated successfully")
|
|
100
|
+
else:
|
|
101
|
+
logger.error(f"[Nuclei] Update failed: {result.stderr}")
|
|
102
|
+
return success
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"[Nuclei] Update error: {e}")
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
def get_template_categories(self) -> Dict[str, List[str]]:
|
|
108
|
+
"""Get available template categories"""
|
|
109
|
+
categories = {
|
|
110
|
+
"cves": [],
|
|
111
|
+
"vulnerabilities": [],
|
|
112
|
+
"misconfiguration": [],
|
|
113
|
+
"exposures": [],
|
|
114
|
+
"technologies": [],
|
|
115
|
+
"token-spray": [],
|
|
116
|
+
"default-logins": [],
|
|
117
|
+
"dns": [],
|
|
118
|
+
"fuzzing": [],
|
|
119
|
+
"helpers": [],
|
|
120
|
+
"headless": [],
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Get from nuclei if available
|
|
124
|
+
try:
|
|
125
|
+
result = subprocess.run(
|
|
126
|
+
[self.nuclei_path, "-tl"], capture_output=True, text=True, timeout=30
|
|
127
|
+
)
|
|
128
|
+
if result.returncode == 0:
|
|
129
|
+
for line in result.stdout.split("\n"):
|
|
130
|
+
for category in categories.keys():
|
|
131
|
+
if f"{category}/" in line.lower():
|
|
132
|
+
categories[category].append(line.strip())
|
|
133
|
+
except:
|
|
134
|
+
pass
|
|
135
|
+
|
|
136
|
+
return categories
|
|
137
|
+
|
|
138
|
+
async def scan_target(
|
|
139
|
+
self,
|
|
140
|
+
target: str,
|
|
141
|
+
severity: List[str] = None,
|
|
142
|
+
tags: List[str] = None,
|
|
143
|
+
templates: List[str] = None,
|
|
144
|
+
rate_limit: int = 150,
|
|
145
|
+
) -> List[NucleiFinding]:
|
|
146
|
+
"""
|
|
147
|
+
Run Nuclei scan against target
|
|
148
|
+
"""
|
|
149
|
+
if not await self.check_nuclei_installed():
|
|
150
|
+
logger.error(
|
|
151
|
+
"[Nuclei] Nuclei not installed. Install from: https://nuclei.projectdiscovery.io/"
|
|
152
|
+
)
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
cmd = [
|
|
156
|
+
self.nuclei_path,
|
|
157
|
+
"-u",
|
|
158
|
+
target,
|
|
159
|
+
"-json",
|
|
160
|
+
"-rate-limit",
|
|
161
|
+
str(rate_limit),
|
|
162
|
+
"-timeout",
|
|
163
|
+
"10",
|
|
164
|
+
"-retries",
|
|
165
|
+
"2",
|
|
166
|
+
]
|
|
167
|
+
|
|
168
|
+
# Add severity filter
|
|
169
|
+
if severity:
|
|
170
|
+
cmd.extend(["-severity", ",".join(severity)])
|
|
171
|
+
|
|
172
|
+
# Add tags filter
|
|
173
|
+
if tags:
|
|
174
|
+
cmd.extend(["-tags", ",".join(tags)])
|
|
175
|
+
|
|
176
|
+
# Add specific templates
|
|
177
|
+
if templates:
|
|
178
|
+
for tmpl in templates:
|
|
179
|
+
cmd.extend(["-t", tmpl])
|
|
180
|
+
|
|
181
|
+
logger.info(f"[Nuclei] Starting scan: {' '.join(cmd)}")
|
|
182
|
+
|
|
183
|
+
findings = []
|
|
184
|
+
try:
|
|
185
|
+
process = await asyncio.create_subprocess_exec(
|
|
186
|
+
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
while True:
|
|
190
|
+
line = await process.stdout.readline()
|
|
191
|
+
if not line:
|
|
192
|
+
break
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
data = json.loads(line.decode().strip())
|
|
196
|
+
finding = self._parse_nuclei_output(data)
|
|
197
|
+
if finding:
|
|
198
|
+
findings.append(finding)
|
|
199
|
+
logger.info(
|
|
200
|
+
f"[Nuclei] Found: {finding.template_name} ({finding.severity})"
|
|
201
|
+
)
|
|
202
|
+
except json.JSONDecodeError:
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
await process.wait()
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"[Nuclei] Scan error: {e}")
|
|
209
|
+
|
|
210
|
+
self.scan_results.extend(findings)
|
|
211
|
+
return findings
|
|
212
|
+
|
|
213
|
+
def _parse_nuclei_output(self, data: Dict) -> Optional[NucleiFinding]:
|
|
214
|
+
"""Parse Nuclei JSON output"""
|
|
215
|
+
try:
|
|
216
|
+
info = data.get("info", {})
|
|
217
|
+
|
|
218
|
+
return NucleiFinding(
|
|
219
|
+
template_id=info.get("id", "unknown"),
|
|
220
|
+
template_name=info.get("name", "Unknown"),
|
|
221
|
+
severity=info.get("severity", "info"),
|
|
222
|
+
host=data.get("host", ""),
|
|
223
|
+
matched_at=data.get("matched-at", ""),
|
|
224
|
+
extract_results=data.get("extracted-results", []),
|
|
225
|
+
timestamp=datetime.now().isoformat(),
|
|
226
|
+
curl_command=data.get("curl-command"),
|
|
227
|
+
request=data.get("request"),
|
|
228
|
+
response=data.get("response"),
|
|
229
|
+
)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"[Nuclei] Parse error: {e}")
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
async def scan_with_ai_analysis(self, target: str) -> Dict:
|
|
235
|
+
"""
|
|
236
|
+
Run Nuclei scan and analyze results with LLM
|
|
237
|
+
"""
|
|
238
|
+
# Run the scan
|
|
239
|
+
findings = await self.scan_target(
|
|
240
|
+
target, severity=["critical", "high", "medium"]
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
if not findings:
|
|
244
|
+
return {"findings": [], "analysis": "No vulnerabilities found"}
|
|
245
|
+
|
|
246
|
+
# Prepare data for LLM analysis
|
|
247
|
+
findings_summary = "\n".join(
|
|
248
|
+
[
|
|
249
|
+
f"- [{f.severity.upper()}] {f.template_name} at {f.matched_at}"
|
|
250
|
+
for f in findings[:20] # Limit for token efficiency
|
|
251
|
+
]
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
if self.orchestrator:
|
|
255
|
+
prompt = f"""
|
|
256
|
+
Analyze these Nuclei scan findings for {target}:
|
|
257
|
+
|
|
258
|
+
{findings_summary}
|
|
259
|
+
|
|
260
|
+
Provide:
|
|
261
|
+
1. Risk assessment summary
|
|
262
|
+
2. Prioritized remediation steps
|
|
263
|
+
3. Potential attack chains (how vulnerabilities might be combined)
|
|
264
|
+
4. Immediate actions required
|
|
265
|
+
"""
|
|
266
|
+
analysis = await self.orchestrator.process(prompt)
|
|
267
|
+
analysis_text = analysis.content
|
|
268
|
+
else:
|
|
269
|
+
analysis_text = "LLM analysis not available (no orchestrator)"
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
"findings": findings,
|
|
273
|
+
"analysis": analysis_text,
|
|
274
|
+
"severity_summary": self._get_severity_summary(findings),
|
|
275
|
+
"timestamp": datetime.now().isoformat(),
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
def _get_severity_summary(self, findings: List[NucleiFinding]) -> Dict[str, int]:
|
|
279
|
+
"""Get severity summary of findings"""
|
|
280
|
+
summary = {"critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0}
|
|
281
|
+
for f in findings:
|
|
282
|
+
sev = f.severity.lower()
|
|
283
|
+
if sev in summary:
|
|
284
|
+
summary[sev] += 1
|
|
285
|
+
return summary
|
|
286
|
+
|
|
287
|
+
def export_results(
|
|
288
|
+
self, findings: List[NucleiFinding], filename: str = None
|
|
289
|
+
) -> str:
|
|
290
|
+
"""Export findings to JSON"""
|
|
291
|
+
if not filename:
|
|
292
|
+
filename = (
|
|
293
|
+
f"logs/nuclei_scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
data = {
|
|
297
|
+
"scan_date": datetime.now().isoformat(),
|
|
298
|
+
"total_findings": len(findings),
|
|
299
|
+
"severity_summary": self._get_severity_summary(findings),
|
|
300
|
+
"findings": [asdict(f) for f in findings],
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
with open(filename, "w") as f:
|
|
304
|
+
json.dump(data, f, indent=2)
|
|
305
|
+
|
|
306
|
+
logger.info(f"[Nuclei] Results exported: {filename}")
|
|
307
|
+
return filename
|
|
308
|
+
|
|
309
|
+
def get_critical_cves(self) -> List[Dict]:
|
|
310
|
+
"""Get list of critical CVE templates"""
|
|
311
|
+
critical_cves = [
|
|
312
|
+
{
|
|
313
|
+
"id": "CVE-2024-XXXX",
|
|
314
|
+
"name": "Placeholder for latest critical CVEs",
|
|
315
|
+
"severity": "critical",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"id": "CVE-2023-44487",
|
|
319
|
+
"name": "HTTP/2 Rapid Reset",
|
|
320
|
+
"severity": "critical",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "CVE-2023-38545",
|
|
324
|
+
"name": "cURL SOCKS5 heap buffer overflow",
|
|
325
|
+
"severity": "high",
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"id": "CVE-2023-29357",
|
|
329
|
+
"name": "Microsoft SharePoint Privilege Escalation",
|
|
330
|
+
"severity": "critical",
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"id": "CVE-2023-21716",
|
|
334
|
+
"name": "Microsoft Word Remote Code Execution",
|
|
335
|
+
"severity": "critical",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"id": "CVE-2022-44877",
|
|
339
|
+
"name": "CentOS Web Panel RCE",
|
|
340
|
+
"severity": "critical",
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"id": "CVE-2022-42889",
|
|
344
|
+
"name": "Apache Commons Text RCE (Text4Shell)",
|
|
345
|
+
"severity": "critical",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"id": "CVE-2022-41741",
|
|
349
|
+
"name": "VMware Workspace ONE Access RCE",
|
|
350
|
+
"severity": "critical",
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"id": "CVE-2022-26134",
|
|
354
|
+
"name": "Atlassian Confluence OGNL Injection",
|
|
355
|
+
"severity": "critical",
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"id": "CVE-2022-22965",
|
|
359
|
+
"name": "Spring Framework RCE (Spring4Shell)",
|
|
360
|
+
"severity": "critical",
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"id": "CVE-2022-22947",
|
|
364
|
+
"name": "Spring Cloud Gateway RCE",
|
|
365
|
+
"severity": "critical",
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"id": "CVE-2021-44228",
|
|
369
|
+
"name": "Log4j RCE (Log4Shell)",
|
|
370
|
+
"severity": "critical",
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"id": "CVE-2021-45046",
|
|
374
|
+
"name": "Log4j Denial of Service",
|
|
375
|
+
"severity": "critical",
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"id": "CVE-2021-41773",
|
|
379
|
+
"name": "Apache Path Traversal",
|
|
380
|
+
"severity": "critical",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"id": "CVE-2021-3129",
|
|
384
|
+
"name": "Laravel Ignition RCE",
|
|
385
|
+
"severity": "critical",
|
|
386
|
+
},
|
|
387
|
+
{"id": "CVE-2020-1472", "name": "Zerologon", "severity": "critical"},
|
|
388
|
+
{
|
|
389
|
+
"id": "CVE-2020-14882",
|
|
390
|
+
"name": "Oracle WebLogic RCE",
|
|
391
|
+
"severity": "critical",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"id": "CVE-2019-19781",
|
|
395
|
+
"name": "Citrix ADC RCE (Shitrix)",
|
|
396
|
+
"severity": "critical",
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"id": "CVE-2019-11510",
|
|
400
|
+
"name": "Pulse Secure VPN Arbitrary File Reading",
|
|
401
|
+
"severity": "critical",
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"id": "CVE-2018-13379",
|
|
405
|
+
"name": "Fortinet VPN Path Traversal",
|
|
406
|
+
"severity": "critical",
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"id": "CVE-2017-0144",
|
|
410
|
+
"name": "EternalBlue (MS17-010)",
|
|
411
|
+
"severity": "critical",
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"id": "CVE-2017-5638",
|
|
415
|
+
"name": "Apache Struts RCE",
|
|
416
|
+
"severity": "critical",
|
|
417
|
+
},
|
|
418
|
+
]
|
|
419
|
+
return critical_cves
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
class NucleiTemplateManager:
|
|
423
|
+
"""Manages custom Nuclei templates"""
|
|
424
|
+
|
|
425
|
+
def __init__(self, templates_dir: str = "data/nuclei_templates"):
|
|
426
|
+
self.templates_dir = templates_dir
|
|
427
|
+
os.makedirs(templates_dir, exist_ok=True)
|
|
428
|
+
|
|
429
|
+
def create_template(
|
|
430
|
+
self,
|
|
431
|
+
template_id: str,
|
|
432
|
+
name: str,
|
|
433
|
+
severity: str,
|
|
434
|
+
request: Dict,
|
|
435
|
+
matchers: List[Dict],
|
|
436
|
+
description: str = "",
|
|
437
|
+
) -> str:
|
|
438
|
+
"""Create a new Nuclei template"""
|
|
439
|
+
|
|
440
|
+
template = {
|
|
441
|
+
"id": template_id,
|
|
442
|
+
"info": {
|
|
443
|
+
"name": name,
|
|
444
|
+
"author": "zen-ai-pentest",
|
|
445
|
+
"severity": severity,
|
|
446
|
+
"description": description,
|
|
447
|
+
"tags": ["custom", "autogenerated"],
|
|
448
|
+
"metadata": {
|
|
449
|
+
"generated_at": datetime.now().isoformat(),
|
|
450
|
+
"tool": "zen-ai-pentest",
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
"http": [
|
|
454
|
+
{
|
|
455
|
+
"method": request.get("method", "GET"),
|
|
456
|
+
"path": request.get("path", ["/"]),
|
|
457
|
+
"headers": request.get("headers", {}),
|
|
458
|
+
"body": request.get("body", ""),
|
|
459
|
+
"matchers": matchers,
|
|
460
|
+
"extractors": request.get("extractors", []),
|
|
461
|
+
}
|
|
462
|
+
],
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
filepath = os.path.join(self.templates_dir, f"{template_id}.yaml")
|
|
466
|
+
|
|
467
|
+
import yaml
|
|
468
|
+
|
|
469
|
+
with open(filepath, "w") as f:
|
|
470
|
+
yaml.dump(template, f, default_flow_style=False, sort_keys=False)
|
|
471
|
+
|
|
472
|
+
return filepath
|
|
473
|
+
|
|
474
|
+
def list_templates(self) -> List[str]:
|
|
475
|
+
"""List all custom templates"""
|
|
476
|
+
templates = []
|
|
477
|
+
for f in os.listdir(self.templates_dir):
|
|
478
|
+
if f.endswith(".yaml") or f.endswith(".yml"):
|
|
479
|
+
templates.append(os.path.join(self.templates_dir, f))
|
|
480
|
+
return templates
|