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
agents/react_agent.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ReAct Agent Loop für Zen-AI-Pentest
|
|
3
|
+
Implementiert Reasoning-Acting-Observing-Reflecting Pattern mit LangGraph
|
|
4
|
+
|
|
5
|
+
Phase 1: Echter agentischer Loop (2026 Roadmap)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, TypedDict, Annotated, Literal
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
from langchain_core.tools import tool, BaseTool
|
|
14
|
+
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage, SystemMessage
|
|
15
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
16
|
+
from langchain_core.runnables import RunnableConfig
|
|
17
|
+
from langgraph.graph import StateGraph, START, END, add_messages
|
|
18
|
+
from langgraph.checkpoint.memory import MemorySaver
|
|
19
|
+
|
|
20
|
+
# Zen-AI-Pentest Imports
|
|
21
|
+
from ..core.llm_backend import LLMBackend
|
|
22
|
+
from ..tools.nmap_integration import NmapTool
|
|
23
|
+
from ..tools.nuclei_integration import NucleiTool
|
|
24
|
+
from ..tools.ffuf_integration import FfufTool
|
|
25
|
+
from ..database.cve_database import CVEDatabase
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AgentState(TypedDict):
|
|
32
|
+
"""
|
|
33
|
+
State für den Agent-Loop.
|
|
34
|
+
Persistiert über Iterationen via LangGraph.
|
|
35
|
+
"""
|
|
36
|
+
messages: Annotated[List[BaseMessage], add_messages]
|
|
37
|
+
findings: List[dict] # Pentest-Ergebnisse strukturiert
|
|
38
|
+
target: str # Aktuelles Ziel
|
|
39
|
+
iteration: int # Loop-Counter
|
|
40
|
+
max_iterations: int # Safety-Limit
|
|
41
|
+
status: Literal["running", "paused", "completed", "error"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ReActAgentConfig:
|
|
46
|
+
"""Konfiguration für den ReAct-Agenten"""
|
|
47
|
+
max_iterations: int = 10
|
|
48
|
+
enable_sandbox: bool = True
|
|
49
|
+
auto_approve_dangerous: bool = False
|
|
50
|
+
use_human_in_the_loop: bool = True
|
|
51
|
+
llm_model: str = "gpt-4o"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ReActAgent:
|
|
55
|
+
"""
|
|
56
|
+
ReAct Agent für autonomes Pentesting.
|
|
57
|
+
|
|
58
|
+
Flow:
|
|
59
|
+
1. Reason/Plan -> LLM entscheidet nächsten Schritt
|
|
60
|
+
2. Act -> Führt Tools aus (sandboxed)
|
|
61
|
+
3. Observe -> Sammelt Ergebnisse
|
|
62
|
+
4. Reflect -> Bewertet Fortschritt
|
|
63
|
+
5. Loop oder End
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, config: ReActAgentConfig = None):
|
|
67
|
+
self.config = config or ReActAgentConfig()
|
|
68
|
+
self.llm = LLMBackend(model=self.config.llm_model)
|
|
69
|
+
self.cve_db = CVEDatabase()
|
|
70
|
+
|
|
71
|
+
# Tools initialisieren
|
|
72
|
+
self.tools = self._initialize_tools()
|
|
73
|
+
self.tools_by_name = {t.name: t for t in self.tools}
|
|
74
|
+
|
|
75
|
+
# LangGraph Workflow
|
|
76
|
+
self.graph = self._build_graph()
|
|
77
|
+
|
|
78
|
+
logger.info(f"ReActAgent initialisiert mit {len(self.tools)} Tools")
|
|
79
|
+
|
|
80
|
+
def _initialize_tools(self) -> List[BaseTool]:
|
|
81
|
+
"""Initialisiert Pentest-Tools"""
|
|
82
|
+
tools = []
|
|
83
|
+
|
|
84
|
+
# Port Scanning
|
|
85
|
+
@tool
|
|
86
|
+
def scan_ports(target: str, ports: str = "top-1000") -> str:
|
|
87
|
+
"""Scannt Ports auf dem Target mit Nmap"""
|
|
88
|
+
nmap = NmapTool()
|
|
89
|
+
result = nmap.scan(target, ports)
|
|
90
|
+
return json.dumps(result, indent=2)
|
|
91
|
+
|
|
92
|
+
# Vulnerability Scanning
|
|
93
|
+
@tool
|
|
94
|
+
def scan_vulnerabilities(target: str, templates: str = "critical,high") -> str:
|
|
95
|
+
"""Scannt nach CVEs mit Nuclei"""
|
|
96
|
+
nuclei = NucleiTool()
|
|
97
|
+
result = nuclei.scan(target, severity=templates)
|
|
98
|
+
return json.dumps(result, indent=2)
|
|
99
|
+
|
|
100
|
+
# Directory Bruteforce
|
|
101
|
+
@tool
|
|
102
|
+
def enumerate_directories(target: str, wordlist: str = "common.txt") -> str:
|
|
103
|
+
"""Enumerate directories mit ffuf"""
|
|
104
|
+
ffuf = FfufTool()
|
|
105
|
+
result = ffuf.directory_bruteforce(target, wordlist)
|
|
106
|
+
return json.dumps(result, indent=2)
|
|
107
|
+
|
|
108
|
+
# CVE Lookup
|
|
109
|
+
@tool
|
|
110
|
+
def lookup_cve(cve_id: str) -> str:
|
|
111
|
+
"""Sucht CVE-Details in der Datenbank"""
|
|
112
|
+
cve = self.cve_db.get_cve(cve_id)
|
|
113
|
+
if cve:
|
|
114
|
+
return json.dumps({
|
|
115
|
+
"id": cve.id,
|
|
116
|
+
"severity": cve.severity,
|
|
117
|
+
"cvss": cve.cvss_score,
|
|
118
|
+
"description": cve.description,
|
|
119
|
+
"epss": cve.epss_score
|
|
120
|
+
}, indent=2)
|
|
121
|
+
return f"CVE {cve_id} nicht gefunden"
|
|
122
|
+
|
|
123
|
+
# Exploit Validation
|
|
124
|
+
@tool
|
|
125
|
+
def validate_exploit(cve_id: str, target: str) -> str:
|
|
126
|
+
"""Validiert ob ein Exploit auf dem Target funktioniert (read-only)"""
|
|
127
|
+
# TODO: Implementiere sichere Validierung
|
|
128
|
+
return f"Exploit-Validierung für {cve_id} auf {target}: Noch nicht implementiert"
|
|
129
|
+
|
|
130
|
+
tools = [scan_ports, scan_vulnerabilities, enumerate_directories,
|
|
131
|
+
lookup_cve, validate_exploit]
|
|
132
|
+
return tools
|
|
133
|
+
|
|
134
|
+
def _build_graph(self) -> StateGraph:
|
|
135
|
+
"""Baut den LangGraph Workflow"""
|
|
136
|
+
|
|
137
|
+
# LLM mit Tool-Binding
|
|
138
|
+
llm_with_tools = self.llm.bind_tools(self.tools)
|
|
139
|
+
|
|
140
|
+
# System Prompt
|
|
141
|
+
system_prompt = """Du bist ein autonomer Pentest-Agent für Zen-AI-Pentest.
|
|
142
|
+
|
|
143
|
+
Deine Aufgabe:
|
|
144
|
+
1. Analysiere das Target und plane den Angriff (Reconnaissance)
|
|
145
|
+
2. Führe Scans durch (Port, Vulnerability, Directory Enumeration)
|
|
146
|
+
3. Analysiere Ergebnisse auf Schwachstellen
|
|
147
|
+
4. Erstelle einen strukturierten Report
|
|
148
|
+
|
|
149
|
+
REGELN:
|
|
150
|
+
- Arbeite Schritt für Schritt (Reasoning)
|
|
151
|
+
- Nutze Tools für externe Aktionen
|
|
152
|
+
- Validiere Ergebnisse (CVSS + EPSS)
|
|
153
|
+
- Never execute destructive exploits without explicit approval
|
|
154
|
+
- Halte max 10 Iterationen ein
|
|
155
|
+
|
|
156
|
+
Wenn du fertig bist, gib eine finale Zusammenfassung aus."""
|
|
157
|
+
|
|
158
|
+
def agent_node(state: AgentState) -> AgentState:
|
|
159
|
+
"""Agent Node: Reason/Plan"""
|
|
160
|
+
# Check iteration limit
|
|
161
|
+
if state["iteration"] >= state["max_iterations"]:
|
|
162
|
+
return {
|
|
163
|
+
**state,
|
|
164
|
+
"messages": state["messages"] + [
|
|
165
|
+
AIMessage(content="Maximale Iterationen erreicht. Beende Scan.")
|
|
166
|
+
],
|
|
167
|
+
"status": "completed"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Prepare messages
|
|
171
|
+
messages = [SystemMessage(content=system_prompt)] + state["messages"]
|
|
172
|
+
|
|
173
|
+
# LLM Call
|
|
174
|
+
response = llm_with_tools.invoke(messages)
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
**state,
|
|
178
|
+
"messages": [response],
|
|
179
|
+
"iteration": state["iteration"] + 1
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
def tools_node(state: AgentState) -> AgentState:
|
|
183
|
+
"""Tools Node: Act/Observe"""
|
|
184
|
+
last_message = state["messages"][-1]
|
|
185
|
+
|
|
186
|
+
if not hasattr(last_message, 'tool_calls') or not last_message.tool_calls:
|
|
187
|
+
return state
|
|
188
|
+
|
|
189
|
+
tool_messages = []
|
|
190
|
+
new_findings = []
|
|
191
|
+
|
|
192
|
+
for tool_call in last_message.tool_calls:
|
|
193
|
+
tool_name = tool_call["name"]
|
|
194
|
+
args = tool_call["args"]
|
|
195
|
+
tool_id = tool_call["id"]
|
|
196
|
+
|
|
197
|
+
logger.info(f"Tool-Aufruf: {tool_name} mit Args: {args}")
|
|
198
|
+
|
|
199
|
+
# Sicherheits-Check
|
|
200
|
+
if self._is_dangerous_tool(tool_name) and self.config.use_human_in_the_loop:
|
|
201
|
+
if not self.config.auto_approve_dangerous:
|
|
202
|
+
result = f"[PENDING APPROVAL] Tool {tool_name} erfordert manuelle Freigabe"
|
|
203
|
+
tool_messages.append(ToolMessage(content=result, tool_call_id=tool_id))
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
# Tool ausführen
|
|
207
|
+
try:
|
|
208
|
+
tool_func = self.tools_by_name.get(tool_name)
|
|
209
|
+
if tool_func:
|
|
210
|
+
result = tool_func.invoke(args)
|
|
211
|
+
|
|
212
|
+
# Finding speichern
|
|
213
|
+
finding = {
|
|
214
|
+
"tool": tool_name,
|
|
215
|
+
"args": args,
|
|
216
|
+
"result": result,
|
|
217
|
+
"iteration": state["iteration"]
|
|
218
|
+
}
|
|
219
|
+
new_findings.append(finding)
|
|
220
|
+
else:
|
|
221
|
+
result = f"Tool {tool_name} nicht gefunden"
|
|
222
|
+
|
|
223
|
+
tool_messages.append(ToolMessage(content=result, tool_call_id=tool_id))
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
error_msg = f"Fehler bei {tool_name}: {str(e)}"
|
|
227
|
+
logger.error(error_msg)
|
|
228
|
+
tool_messages.append(ToolMessage(content=error_msg, tool_call_id=tool_id))
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
**state,
|
|
232
|
+
"messages": tool_messages,
|
|
233
|
+
"findings": state["findings"] + new_findings
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
def should_continue(state: AgentState) -> Literal["tools", "agent", "end"]:
|
|
237
|
+
"""Conditional Edge: Entscheidet über Loop-Fortsetzung"""
|
|
238
|
+
last_message = state["messages"][-1]
|
|
239
|
+
|
|
240
|
+
# Wenn fertig (keine Tool-Calls mehr)
|
|
241
|
+
if state.get("status") == "completed":
|
|
242
|
+
return "end"
|
|
243
|
+
|
|
244
|
+
# Wenn Tools aufgerufen wurden
|
|
245
|
+
if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
|
|
246
|
+
return "tools"
|
|
247
|
+
|
|
248
|
+
# Wenn Tool-Ergebnisse da sind -> zurück zum Agent
|
|
249
|
+
if isinstance(last_message, ToolMessage):
|
|
250
|
+
return "agent"
|
|
251
|
+
|
|
252
|
+
# Sonst Ende
|
|
253
|
+
return "end"
|
|
254
|
+
|
|
255
|
+
# Graph bauen
|
|
256
|
+
workflow = StateGraph(AgentState)
|
|
257
|
+
|
|
258
|
+
workflow.add_node("agent", agent_node)
|
|
259
|
+
workflow.add_node("tools", tools_node)
|
|
260
|
+
|
|
261
|
+
workflow.add_edge(START, "agent")
|
|
262
|
+
|
|
263
|
+
workflow.add_conditional_edges(
|
|
264
|
+
"agent",
|
|
265
|
+
should_continue,
|
|
266
|
+
{
|
|
267
|
+
"tools": "tools",
|
|
268
|
+
"agent": "agent",
|
|
269
|
+
"end": END
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
workflow.add_conditional_edges(
|
|
274
|
+
"tools",
|
|
275
|
+
should_continue,
|
|
276
|
+
{
|
|
277
|
+
"tools": "tools",
|
|
278
|
+
"agent": "agent",
|
|
279
|
+
"end": END
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Mit Memory für Human-in-the-Loop
|
|
284
|
+
checkpointer = MemorySaver()
|
|
285
|
+
return workflow.compile(checkpointer=checkpointer)
|
|
286
|
+
|
|
287
|
+
def _is_dangerous_tool(self, tool_name: str) -> bool:
|
|
288
|
+
"""Prüft ob ein Tool als gefährlich eingestuft wird"""
|
|
289
|
+
dangerous = ["validate_exploit", "exploit", "sqlmap_exploit"]
|
|
290
|
+
return any(d in tool_name.lower() for d in dangerous)
|
|
291
|
+
|
|
292
|
+
def run(self, target: str, objective: str = "comprehensive scan") -> dict:
|
|
293
|
+
"""
|
|
294
|
+
Führt den Agent-Loop aus.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
target: Ziel-URL/IP
|
|
298
|
+
objective: Scan-Ziel (z.B. "port scan", "vulnerability assessment")
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
dict mit findings, messages, status
|
|
302
|
+
"""
|
|
303
|
+
initial_state: AgentState = {
|
|
304
|
+
"messages": [HumanMessage(content=f"{objective} on {target}")],
|
|
305
|
+
"findings": [],
|
|
306
|
+
"target": target,
|
|
307
|
+
"iteration": 0,
|
|
308
|
+
"max_iterations": self.config.max_iterations,
|
|
309
|
+
"status": "running"
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
logger.info(f"Starte ReAct-Agent für {target}")
|
|
313
|
+
|
|
314
|
+
# Graph ausführen
|
|
315
|
+
result = self.graph.invoke(
|
|
316
|
+
initial_state,
|
|
317
|
+
config={"configurable": {"thread_id": f"pentest_{target}"}}
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
logger.info(f"Agent beendet nach {result['iteration']} Iterationen")
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
"findings": result["findings"],
|
|
324
|
+
"final_message": result["messages"][-1].content if result["messages"] else "",
|
|
325
|
+
"iterations": result["iteration"],
|
|
326
|
+
"status": result["status"],
|
|
327
|
+
"target": target
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
def generate_report(self, result: dict) -> str:
|
|
331
|
+
"""Generiert einen strukturierten Report aus den Findings"""
|
|
332
|
+
report = []
|
|
333
|
+
report.append("=" * 70)
|
|
334
|
+
report.append("ZEN-AI-PENTEST REPORT")
|
|
335
|
+
report.append("=" * 70)
|
|
336
|
+
report.append(f"\nTarget: {result['target']}")
|
|
337
|
+
report.append(f"Status: {result['status']}")
|
|
338
|
+
report.append(f"Iterations: {result['iterations']}")
|
|
339
|
+
report.append("")
|
|
340
|
+
|
|
341
|
+
if result['findings']:
|
|
342
|
+
report.append("FINDINGS:")
|
|
343
|
+
report.append("-" * 70)
|
|
344
|
+
for i, finding in enumerate(result['findings'], 1):
|
|
345
|
+
report.append(f"\n{i}. {finding['tool']}")
|
|
346
|
+
report.append(f" Args: {finding['args']}")
|
|
347
|
+
report.append(f" Result: {finding['result'][:200]}...")
|
|
348
|
+
else:
|
|
349
|
+
report.append("No findings detected.")
|
|
350
|
+
|
|
351
|
+
report.append("")
|
|
352
|
+
report.append("=" * 70)
|
|
353
|
+
report.append("FINAL ANALYSIS:")
|
|
354
|
+
report.append("=" * 70)
|
|
355
|
+
report.append(result['final_message'])
|
|
356
|
+
|
|
357
|
+
return "\n".join(report)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# Singleton-Instanz für einfachen Zugriff
|
|
361
|
+
_default_agent = None
|
|
362
|
+
|
|
363
|
+
def get_agent(config: ReActAgentConfig = None) -> ReActAgent:
|
|
364
|
+
"""Gibt die default Agent-Instanz zurück"""
|
|
365
|
+
global _default_agent
|
|
366
|
+
if _default_agent is None or config is not None:
|
|
367
|
+
_default_agent = ReActAgent(config)
|
|
368
|
+
return _default_agent
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
if __name__ == "__main__":
|
|
372
|
+
# Test
|
|
373
|
+
logging.basicConfig(level=logging.INFO)
|
|
374
|
+
|
|
375
|
+
config = ReActAgentConfig(
|
|
376
|
+
max_iterations=5,
|
|
377
|
+
enable_sandbox=True,
|
|
378
|
+
auto_approve_dangerous=False
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
agent = ReActAgent(config)
|
|
382
|
+
result = agent.run("scanme.nmap.org", objective="Port scan and vulnerability assessment")
|
|
383
|
+
|
|
384
|
+
print(agent.generate_report(result))
|