mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-08-15 14:10:22 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5857d00c1 |
@@ -105,6 +105,7 @@ class AgentMode(str, Enum):
|
|||||||
ANALYZE_ONLY = "analyze_only" # Analysis without testing
|
ANALYZE_ONLY = "analyze_only" # Analysis without testing
|
||||||
AUTO_PENTEST = "auto_pentest" # One-click full auto pentest
|
AUTO_PENTEST = "auto_pentest" # One-click full auto pentest
|
||||||
CLI_AGENT = "cli_agent" # AI CLI tool inside Kali sandbox
|
CLI_AGENT = "cli_agent" # AI CLI tool inside Kali sandbox
|
||||||
|
FULL_LLM_PENTEST = "full_llm_pentest" # LLM drives the entire pentest cycle
|
||||||
|
|
||||||
|
|
||||||
class AgentRequest(BaseModel):
|
class AgentRequest(BaseModel):
|
||||||
@@ -251,6 +252,7 @@ async def run_agent(request: AgentRequest, background_tasks: BackgroundTasks):
|
|||||||
"analyze_only": "Analysis only, no active testing",
|
"analyze_only": "Analysis only, no active testing",
|
||||||
"auto_pentest": "One-click auto pentest: Full recon + 100 vuln types + AI report",
|
"auto_pentest": "One-click auto pentest: Full recon + 100 vuln types + AI report",
|
||||||
"cli_agent": "CLI Agent: AI CLI tool (Claude/Gemini/Codex) inside Kali sandbox",
|
"cli_agent": "CLI Agent: AI CLI tool (Claude/Gemini/Codex) inside Kali sandbox",
|
||||||
|
"full_llm_pentest": "Full LLM Pentest: AI drives the entire pentest cycle autonomously",
|
||||||
}
|
}
|
||||||
|
|
||||||
return AgentResponse(
|
return AgentResponse(
|
||||||
@@ -379,6 +381,7 @@ async def _run_agent_task(
|
|||||||
AgentMode.ANALYZE_ONLY: OperationMode.ANALYZE_ONLY,
|
AgentMode.ANALYZE_ONLY: OperationMode.ANALYZE_ONLY,
|
||||||
AgentMode.AUTO_PENTEST: OperationMode.AUTO_PENTEST,
|
AgentMode.AUTO_PENTEST: OperationMode.AUTO_PENTEST,
|
||||||
AgentMode.CLI_AGENT: OperationMode.CLI_AGENT,
|
AgentMode.CLI_AGENT: OperationMode.CLI_AGENT,
|
||||||
|
AgentMode.FULL_LLM_PENTEST: OperationMode.FULL_LLM_PENTEST,
|
||||||
}
|
}
|
||||||
op_mode = mode_map.get(mode, OperationMode.FULL_AUTO)
|
op_mode = mode_map.get(mode, OperationMode.FULL_AUTO)
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ class OperationMode(Enum):
|
|||||||
ANALYZE_ONLY = "analyze_only"
|
ANALYZE_ONLY = "analyze_only"
|
||||||
AUTO_PENTEST = "auto_pentest"
|
AUTO_PENTEST = "auto_pentest"
|
||||||
CLI_AGENT = "cli_agent"
|
CLI_AGENT = "cli_agent"
|
||||||
|
FULL_LLM_PENTEST = "full_llm_pentest"
|
||||||
|
|
||||||
|
|
||||||
class FindingSeverity(Enum):
|
class FindingSeverity(Enum):
|
||||||
@@ -4005,6 +4006,8 @@ NOT_VULNERABLE: <reason>"""
|
|||||||
return await self._run_auto_pentest()
|
return await self._run_auto_pentest()
|
||||||
elif self.mode == OperationMode.CLI_AGENT:
|
elif self.mode == OperationMode.CLI_AGENT:
|
||||||
return await self._run_cli_agent_mode()
|
return await self._run_cli_agent_mode()
|
||||||
|
elif self.mode == OperationMode.FULL_LLM_PENTEST:
|
||||||
|
return await self._run_full_llm_pentest()
|
||||||
else:
|
else:
|
||||||
return await self._run_full_auto()
|
return await self._run_full_auto()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -5008,6 +5011,457 @@ NOT_VULNERABLE: <reason>"""
|
|||||||
await self._update_progress(100, "CLI Agent pentest complete")
|
await self._update_progress(100, "CLI Agent pentest complete")
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# FULL LLM PENTEST MODE — AI drives the entire pentest cycle
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
async def _run_full_llm_pentest(self) -> Dict[str, Any]:
|
||||||
|
"""Full LLM Pentest: the AI drives every step of the pentest.
|
||||||
|
|
||||||
|
The LLM acts as a senior penetration tester. It plans HTTP requests,
|
||||||
|
the system executes them, and the LLM analyzes real responses to
|
||||||
|
identify vulnerabilities. Pure AI-driven, no hardcoded payloads.
|
||||||
|
|
||||||
|
Loop: LLM plans → System executes HTTP → LLM analyzes → repeat
|
||||||
|
"""
|
||||||
|
await self._update_progress(0, "Full LLM Pentest starting")
|
||||||
|
await self.log("info", "=" * 60)
|
||||||
|
await self.log("info", " FULL LLM PENTEST MODE")
|
||||||
|
await self.log("info", " AI drives the entire pentest cycle")
|
||||||
|
await self.log("info", "=" * 60)
|
||||||
|
|
||||||
|
if not self.llm.is_available():
|
||||||
|
await self.log("error", "LLM not available! This mode requires an active LLM provider.")
|
||||||
|
await self.log("error", "Configure ANTHROPIC_API_KEY, OPENAI_API_KEY, or another provider.")
|
||||||
|
return self._generate_error_report("LLM not available for Full LLM Pentest mode")
|
||||||
|
|
||||||
|
# Import prompts
|
||||||
|
from backend.core.vuln_engine.ai_prompts import (
|
||||||
|
get_full_llm_pentest_system_prompt,
|
||||||
|
get_full_llm_pentest_round_prompt,
|
||||||
|
get_full_llm_pentest_report_prompt,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load methodology prompt
|
||||||
|
methodology = self.custom_prompt or ""
|
||||||
|
if not methodology:
|
||||||
|
try:
|
||||||
|
prompt_path = Path("/opt/Prompts-PenTest/pentestcompleto_en.md")
|
||||||
|
if not prompt_path.exists():
|
||||||
|
prompt_path = Path("/opt/Prompts-PenTest/pentestcompleto.md")
|
||||||
|
if prompt_path.exists():
|
||||||
|
methodology = prompt_path.read_text(encoding="utf-8")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Build system prompt
|
||||||
|
system_prompt = get_full_llm_pentest_system_prompt(methodology)
|
||||||
|
await self.log("info", f" System prompt: {len(system_prompt)} chars")
|
||||||
|
await self.log("info", f" Methodology: {'loaded' if methodology else 'none'} ({len(methodology)} chars)")
|
||||||
|
|
||||||
|
# State tracking
|
||||||
|
MAX_ROUNDS = 30
|
||||||
|
MAX_ACTIONS_PER_ROUND = 10
|
||||||
|
total_requests = 0
|
||||||
|
discovered_info_parts: List[str] = []
|
||||||
|
all_round_results: List[str] = [] # accumulates round-by-round results
|
||||||
|
llm_findings: List[Dict] = []
|
||||||
|
|
||||||
|
await self._update_progress(2, "Full LLM Pentest: Round 1")
|
||||||
|
|
||||||
|
for round_num in range(1, MAX_ROUNDS + 1):
|
||||||
|
if self.is_cancelled():
|
||||||
|
await self.log("warning", "[LLM PENTEST] Cancelled by user")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Calculate progress: rounds map to 0-85%
|
||||||
|
progress = min(85, int((round_num / MAX_ROUNDS) * 85))
|
||||||
|
phase_label = (
|
||||||
|
"Recon" if round_num <= 8 else
|
||||||
|
"Testing" if round_num <= 25 else
|
||||||
|
"Post-Exploitation" if round_num <= 28 else
|
||||||
|
"Reporting"
|
||||||
|
)
|
||||||
|
await self._update_progress(progress, f"Full LLM Pentest: {phase_label} (Round {round_num}/{MAX_ROUNDS})")
|
||||||
|
|
||||||
|
# Build round prompt with accumulated context
|
||||||
|
# Keep only recent results to manage token budget (last 5 rounds)
|
||||||
|
recent_results = "\n\n".join(all_round_results[-5:]) if all_round_results else ""
|
||||||
|
discovered_summary = "\n".join(discovered_info_parts[-30:]) if discovered_info_parts else ""
|
||||||
|
|
||||||
|
round_prompt = get_full_llm_pentest_round_prompt(
|
||||||
|
target=self.target,
|
||||||
|
round_num=round_num,
|
||||||
|
max_rounds=MAX_ROUNDS,
|
||||||
|
previous_results=recent_results,
|
||||||
|
discovered_info=discovered_summary,
|
||||||
|
findings_so_far=len(self.findings),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Call LLM
|
||||||
|
await self.log("info", f"[LLM PENTEST] Round {round_num}: Asking AI to plan ({phase_label})")
|
||||||
|
try:
|
||||||
|
llm_response = await self.llm.generate(
|
||||||
|
prompt=round_prompt,
|
||||||
|
system=system_prompt,
|
||||||
|
max_tokens=8192,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
await self.log("error", f"[LLM PENTEST] LLM call failed: {e}")
|
||||||
|
# Try to continue with next round
|
||||||
|
all_round_results.append(f"Round {round_num}: LLM call failed — {str(e)[:100]}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Parse LLM response as JSON
|
||||||
|
parsed = self._parse_llm_json(llm_response)
|
||||||
|
if not parsed:
|
||||||
|
await self.log("warning", f"[LLM PENTEST] Round {round_num}: Failed to parse LLM JSON response")
|
||||||
|
all_round_results.append(f"Round {round_num}: LLM returned invalid JSON")
|
||||||
|
continue
|
||||||
|
|
||||||
|
reasoning = parsed.get("reasoning", "")
|
||||||
|
actions = parsed.get("actions", [])
|
||||||
|
findings = parsed.get("findings", [])
|
||||||
|
phase = parsed.get("phase", "unknown")
|
||||||
|
done = parsed.get("done", False)
|
||||||
|
summary = parsed.get("summary", "")
|
||||||
|
|
||||||
|
if reasoning:
|
||||||
|
await self.log("info", f"[LLM PENTEST] AI reasoning: {reasoning[:200]}")
|
||||||
|
|
||||||
|
# Execute HTTP actions
|
||||||
|
round_result_parts = [f"=== Round {round_num} ({phase}) ==="]
|
||||||
|
if reasoning:
|
||||||
|
round_result_parts.append(f"Reasoning: {reasoning}")
|
||||||
|
|
||||||
|
actions_to_exec = actions[:MAX_ACTIONS_PER_ROUND]
|
||||||
|
await self.log("info", f"[LLM PENTEST] Executing {len(actions_to_exec)} HTTP requests")
|
||||||
|
|
||||||
|
for i, action in enumerate(actions_to_exec):
|
||||||
|
if self.is_cancelled():
|
||||||
|
break
|
||||||
|
|
||||||
|
result = await self._execute_llm_action(action, i + 1)
|
||||||
|
total_requests += 1
|
||||||
|
|
||||||
|
if result:
|
||||||
|
# Add to round results for LLM context
|
||||||
|
result_summary = self._summarize_response(action, result)
|
||||||
|
round_result_parts.append(result_summary)
|
||||||
|
|
||||||
|
# Track discovered info
|
||||||
|
purpose = action.get("purpose", "")
|
||||||
|
url = action.get("url", "")
|
||||||
|
status = result.get("status", 0)
|
||||||
|
if status == 200:
|
||||||
|
discovered_info_parts.append(
|
||||||
|
f"- {action.get('method', 'GET')} {url} → {status} "
|
||||||
|
f"({len(result.get('body', ''))} bytes) — {purpose}"
|
||||||
|
)
|
||||||
|
elif status in (301, 302, 303, 307, 308):
|
||||||
|
location = result.get("headers", {}).get("Location", result.get("headers", {}).get("location", ""))
|
||||||
|
discovered_info_parts.append(f"- {url} → redirect to {location}")
|
||||||
|
elif status == 404:
|
||||||
|
discovered_info_parts.append(f"- {url} → 404 (not found)")
|
||||||
|
elif status == 403:
|
||||||
|
discovered_info_parts.append(f"- {url} → 403 (forbidden)")
|
||||||
|
else:
|
||||||
|
discovered_info_parts.append(f"- {url} → {status}")
|
||||||
|
else:
|
||||||
|
round_result_parts.append(
|
||||||
|
f"Request {i+1}: {action.get('method', 'GET')} {action.get('url', '?')} → FAILED (connection error/timeout)"
|
||||||
|
)
|
||||||
|
|
||||||
|
all_round_results.append("\n".join(round_result_parts))
|
||||||
|
|
||||||
|
# Process findings from this round
|
||||||
|
for finding_data in findings:
|
||||||
|
await self._process_llm_pentest_finding(finding_data, round_num)
|
||||||
|
|
||||||
|
# Check if LLM says we're done
|
||||||
|
if done:
|
||||||
|
await self.log("success", f"[LLM PENTEST] AI completed pentest after {round_num} rounds")
|
||||||
|
if summary:
|
||||||
|
await self.log("info", f"[LLM PENTEST] Summary: {summary[:300]}")
|
||||||
|
break
|
||||||
|
|
||||||
|
await self.log("info", f"[LLM PENTEST] Round {round_num} complete: "
|
||||||
|
f"{len(actions_to_exec)} requests, {len(findings)} findings, "
|
||||||
|
f"total: {total_requests} requests, {len(self.findings)} confirmed findings")
|
||||||
|
|
||||||
|
# ── FINALIZATION ──
|
||||||
|
await self._update_progress(88, "Full LLM Pentest: Generating report")
|
||||||
|
await self.log("info", f"[LLM PENTEST] Testing complete: {total_requests} total requests, "
|
||||||
|
f"{len(self.findings)} confirmed findings")
|
||||||
|
|
||||||
|
# Generate AI-enhanced report
|
||||||
|
report = await self._generate_full_report()
|
||||||
|
|
||||||
|
# Also try to get an AI narrative report
|
||||||
|
if self.llm.is_available() and self.findings:
|
||||||
|
try:
|
||||||
|
findings_json = json.dumps([
|
||||||
|
{
|
||||||
|
"title": f.title,
|
||||||
|
"severity": f.severity,
|
||||||
|
"vulnerability_type": f.vulnerability_type,
|
||||||
|
"affected_endpoint": f.affected_endpoint,
|
||||||
|
"parameter": f.parameter,
|
||||||
|
"payload": f.payload,
|
||||||
|
"evidence": f.evidence[:500] if f.evidence else "",
|
||||||
|
"description": f.description,
|
||||||
|
"impact": f.impact,
|
||||||
|
"cvss_score": f.cvss_score,
|
||||||
|
"cwe_id": f.cwe_id,
|
||||||
|
"poc_code": f.poc_code,
|
||||||
|
"remediation": f.remediation,
|
||||||
|
"confidence_score": f.confidence_score,
|
||||||
|
}
|
||||||
|
for f in self.findings
|
||||||
|
], indent=2)
|
||||||
|
|
||||||
|
report_prompt = get_full_llm_pentest_report_prompt(
|
||||||
|
target=self.target,
|
||||||
|
findings_json=findings_json,
|
||||||
|
total_rounds=min(round_num, MAX_ROUNDS),
|
||||||
|
total_requests=total_requests,
|
||||||
|
)
|
||||||
|
ai_report_text = await self.llm.generate(
|
||||||
|
prompt=report_prompt,
|
||||||
|
system="You are a professional penetration testing report writer.",
|
||||||
|
max_tokens=16384,
|
||||||
|
)
|
||||||
|
if ai_report_text:
|
||||||
|
report["ai_narrative_report"] = ai_report_text
|
||||||
|
await self.log("success", "[LLM PENTEST] AI narrative report generated")
|
||||||
|
except Exception as e:
|
||||||
|
await self.log("debug", f"[LLM PENTEST] Report generation error: {e}")
|
||||||
|
|
||||||
|
await self._update_progress(100, "Full LLM Pentest complete")
|
||||||
|
await self.log("info", "=" * 60)
|
||||||
|
await self.log("info", f" FULL LLM PENTEST COMPLETE: {len(self.findings)} findings")
|
||||||
|
await self.log("info", f" Total HTTP requests: {total_requests}")
|
||||||
|
await self.log("info", "=" * 60)
|
||||||
|
return report
|
||||||
|
|
||||||
|
async def _execute_llm_action(self, action: Dict, action_num: int) -> Optional[Dict]:
|
||||||
|
"""Execute a single HTTP action planned by the LLM.
|
||||||
|
|
||||||
|
The action dict has: method, url, headers, body, content_type, purpose
|
||||||
|
Returns the response dict or None on failure.
|
||||||
|
"""
|
||||||
|
method = (action.get("method") or "GET").upper()
|
||||||
|
url = action.get("url", "")
|
||||||
|
custom_headers = action.get("headers") or {}
|
||||||
|
body = action.get("body")
|
||||||
|
content_type = action.get("content_type", "")
|
||||||
|
purpose = action.get("purpose", "")
|
||||||
|
|
||||||
|
if not url:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ensure URL is absolute
|
||||||
|
if not url.startswith("http"):
|
||||||
|
url = urljoin(self.target, url)
|
||||||
|
|
||||||
|
# Build request headers
|
||||||
|
headers = dict(self.auth_headers) if self.auth_headers else {}
|
||||||
|
headers.update(custom_headers)
|
||||||
|
if content_type and "Content-Type" not in headers and "content-type" not in headers:
|
||||||
|
headers["Content-Type"] = content_type
|
||||||
|
|
||||||
|
# Log the request
|
||||||
|
await self.log("info", f"[LLM PENTEST] → {method} {url[:120]} ({purpose[:60]})")
|
||||||
|
|
||||||
|
try:
|
||||||
|
timeout = aiohttp.ClientTimeout(total=15)
|
||||||
|
|
||||||
|
if self.request_engine:
|
||||||
|
# Use request engine for retry/rate limiting
|
||||||
|
data = None
|
||||||
|
params = None
|
||||||
|
if method == "GET":
|
||||||
|
# Parse params from URL
|
||||||
|
pass # URL already has params
|
||||||
|
else:
|
||||||
|
if body:
|
||||||
|
if content_type and "json" in content_type:
|
||||||
|
try:
|
||||||
|
data = json.loads(body) if isinstance(body, str) else body
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
data = body
|
||||||
|
else:
|
||||||
|
data = body
|
||||||
|
else:
|
||||||
|
data = None
|
||||||
|
|
||||||
|
result = await self.request_engine.request(
|
||||||
|
url, method=method,
|
||||||
|
headers=headers if headers else None,
|
||||||
|
data=data,
|
||||||
|
allow_redirects=True,
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
resp_dict = {
|
||||||
|
"status": result.status,
|
||||||
|
"body": result.body[:50000] if result.body else "",
|
||||||
|
"headers": result.headers,
|
||||||
|
"url": result.url,
|
||||||
|
}
|
||||||
|
status_str = f"{result.status}"
|
||||||
|
body_len = len(result.body) if result.body else 0
|
||||||
|
await self.log("info", f"[LLM PENTEST] ← {status_str} ({body_len} bytes)")
|
||||||
|
return resp_dict
|
||||||
|
else:
|
||||||
|
# Direct session fallback
|
||||||
|
req_kwargs: Dict[str, Any] = {
|
||||||
|
"allow_redirects": True,
|
||||||
|
"timeout": timeout,
|
||||||
|
"headers": headers,
|
||||||
|
}
|
||||||
|
if method != "GET" and body:
|
||||||
|
if content_type and "json" in content_type:
|
||||||
|
try:
|
||||||
|
req_kwargs["json"] = json.loads(body) if isinstance(body, str) else body
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
req_kwargs["data"] = body
|
||||||
|
else:
|
||||||
|
req_kwargs["data"] = body
|
||||||
|
|
||||||
|
async with self.session.request(method, url, **req_kwargs) as resp:
|
||||||
|
resp_body = await resp.text()
|
||||||
|
resp_dict = {
|
||||||
|
"status": resp.status,
|
||||||
|
"body": resp_body[:50000],
|
||||||
|
"headers": dict(resp.headers),
|
||||||
|
"url": str(resp.url),
|
||||||
|
}
|
||||||
|
await self.log("info", f"[LLM PENTEST] ← {resp.status} ({len(resp_body)} bytes)")
|
||||||
|
return resp_dict
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
await self.log("debug", f"[LLM PENTEST] Timeout: {url[:80]}")
|
||||||
|
except Exception as e:
|
||||||
|
await self.log("debug", f"[LLM PENTEST] Request error: {str(e)[:80]}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _summarize_response(self, action: Dict, result: Dict) -> str:
|
||||||
|
"""Create a compact summary of an HTTP response for the LLM context."""
|
||||||
|
method = action.get("method", "GET")
|
||||||
|
url = action.get("url", "?")
|
||||||
|
purpose = action.get("purpose", "")
|
||||||
|
status = result.get("status", 0)
|
||||||
|
headers = result.get("headers", {})
|
||||||
|
body = result.get("body", "")
|
||||||
|
|
||||||
|
# Extract key headers
|
||||||
|
key_headers = {}
|
||||||
|
for h in ["Server", "server", "Content-Type", "content-type",
|
||||||
|
"X-Powered-By", "x-powered-by", "Set-Cookie", "set-cookie",
|
||||||
|
"Location", "location", "X-Frame-Options", "x-frame-options",
|
||||||
|
"Content-Security-Policy", "content-security-policy",
|
||||||
|
"WWW-Authenticate", "www-authenticate"]:
|
||||||
|
val = headers.get(h)
|
||||||
|
if val:
|
||||||
|
key_headers[h] = val[:200]
|
||||||
|
|
||||||
|
# Truncate body for context (keep meaningful content)
|
||||||
|
body_preview = body[:3000] if body else ""
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"Request: {method} {url}",
|
||||||
|
f"Purpose: {purpose}",
|
||||||
|
f"Status: {status}",
|
||||||
|
f"Headers: {json.dumps(key_headers, default=str)}",
|
||||||
|
f"Body ({len(body)} bytes):",
|
||||||
|
body_preview,
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _parse_llm_json(self, text: str) -> Optional[Dict]:
|
||||||
|
"""Parse JSON from LLM response, handling markdown code blocks."""
|
||||||
|
if not text:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Try direct parse
|
||||||
|
text_stripped = text.strip()
|
||||||
|
try:
|
||||||
|
return json.loads(text_stripped)
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Try extracting from markdown code block
|
||||||
|
import re
|
||||||
|
patterns = [
|
||||||
|
r'```json\s*\n(.*?)\n\s*```',
|
||||||
|
r'```\s*\n(.*?)\n\s*```',
|
||||||
|
r'\{[\s\S]*\}',
|
||||||
|
]
|
||||||
|
for pattern in patterns[:2]:
|
||||||
|
match = re.search(pattern, text, re.DOTALL)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
return json.loads(match.group(1))
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Try finding the outermost JSON object
|
||||||
|
# Find first { and last }
|
||||||
|
first_brace = text.find('{')
|
||||||
|
last_brace = text.rfind('}')
|
||||||
|
if first_brace >= 0 and last_brace > first_brace:
|
||||||
|
try:
|
||||||
|
return json.loads(text[first_brace:last_brace + 1])
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _process_llm_pentest_finding(self, finding_data: Dict, round_num: int):
|
||||||
|
"""Process a finding reported by the LLM in Full LLM Pentest mode.
|
||||||
|
|
||||||
|
Creates a Finding object and routes it through the validation pipeline.
|
||||||
|
"""
|
||||||
|
title = finding_data.get("title", "LLM Finding")
|
||||||
|
severity = finding_data.get("severity", "medium").lower()
|
||||||
|
if severity not in ("critical", "high", "medium", "low", "info"):
|
||||||
|
severity = "medium"
|
||||||
|
|
||||||
|
vuln_type = finding_data.get("vulnerability_type", "unknown")
|
||||||
|
evidence = finding_data.get("evidence", "")
|
||||||
|
|
||||||
|
# Skip findings without evidence (anti-hallucination)
|
||||||
|
if not evidence or len(evidence) < 10:
|
||||||
|
await self.log("debug", f"[LLM PENTEST] Skipping finding without evidence: {title}")
|
||||||
|
return
|
||||||
|
|
||||||
|
finding = Finding(
|
||||||
|
id=hashlib.md5(
|
||||||
|
f"{title}|{finding_data.get('affected_endpoint', '')}|{finding_data.get('payload', '')}|{round_num}".encode()
|
||||||
|
).hexdigest()[:12],
|
||||||
|
title=title,
|
||||||
|
severity=severity,
|
||||||
|
vulnerability_type=vuln_type,
|
||||||
|
cvss_score=finding_data.get("cvss_score", 0.0),
|
||||||
|
cwe_id=finding_data.get("cwe_id", ""),
|
||||||
|
description=finding_data.get("description", ""),
|
||||||
|
affected_endpoint=finding_data.get("affected_endpoint", self.target),
|
||||||
|
parameter=finding_data.get("parameter", ""),
|
||||||
|
payload=finding_data.get("payload", ""),
|
||||||
|
evidence=evidence,
|
||||||
|
impact=finding_data.get("impact", ""),
|
||||||
|
poc_code=finding_data.get("poc_code", ""),
|
||||||
|
remediation=finding_data.get("remediation", ""),
|
||||||
|
ai_verified=True,
|
||||||
|
confidence_score=70, # Initial score, ValidationJudge will refine
|
||||||
|
ai_status="confirmed",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Route through validation pipeline (_judge_finding handles
|
||||||
|
# negative controls, proof of execution, confidence scoring)
|
||||||
|
await self._add_finding(finding)
|
||||||
|
await self.log("success", f"[LLM PENTEST] Finding: {severity.upper()} — {title}")
|
||||||
|
|
||||||
# ── Pre-Stream AI Master Plan ──
|
# ── Pre-Stream AI Master Plan ──
|
||||||
|
|
||||||
async def _ai_master_plan(self) -> Dict:
|
async def _ai_master_plan(self) -> Dict:
|
||||||
|
|||||||
@@ -2097,3 +2097,222 @@ RULES:
|
|||||||
- Prioritize by LIKELIHOOD of exploitation, not theoretical severity.
|
- Prioritize by LIKELIHOOD of exploitation, not theoretical severity.
|
||||||
- Consider technology-specific vulnerabilities (e.g., Spring → actuator, WordPress → wp-admin).
|
- Consider technology-specific vulnerabilities (e.g., Spring → actuator, WordPress → wp-admin).
|
||||||
- Flag parameters like 'url', 'file', 'path', 'redirect', 'callback', 'template' as high-risk."""
|
- Flag parameters like 'url', 'file', 'path', 'redirect', 'callback', 'template' as high-risk."""
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# FULL LLM PENTEST PROMPTS — AI drives the entire pentest cycle
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def get_full_llm_pentest_system_prompt(methodology: str = "") -> str:
|
||||||
|
"""System prompt for the Full LLM Pentest mode.
|
||||||
|
|
||||||
|
The LLM acts as a senior penetration tester and plans HTTP requests
|
||||||
|
for the system to execute. The LLM sees real responses and adapts.
|
||||||
|
"""
|
||||||
|
methodology_section = ""
|
||||||
|
if methodology:
|
||||||
|
# Truncate very large methodology to fit context
|
||||||
|
max_len = 60000
|
||||||
|
if len(methodology) > max_len:
|
||||||
|
methodology = methodology[:max_len] + "\n\n[... methodology truncated for context ...]"
|
||||||
|
methodology_section = f"""
|
||||||
|
## PENTEST METHODOLOGY
|
||||||
|
|
||||||
|
Follow this methodology throughout your testing:
|
||||||
|
|
||||||
|
{methodology}
|
||||||
|
"""
|
||||||
|
|
||||||
|
return f"""You are a Senior Offensive Security Consultant performing an authorized penetration test in a controlled lab environment.
|
||||||
|
|
||||||
|
ALL ACTIVITIES ARE AUTHORIZED. The target is a deliberately vulnerable lab environment.
|
||||||
|
|
||||||
|
## YOUR ROLE
|
||||||
|
|
||||||
|
You are the testing engine. You think like a human pentester using Burp Suite / curl.
|
||||||
|
You PLAN what HTTP requests to make, and the system EXECUTES them for you.
|
||||||
|
You then ANALYZE the real responses and ADAPT your strategy.
|
||||||
|
|
||||||
|
## HOW THIS WORKS
|
||||||
|
|
||||||
|
Each round you output a JSON object with:
|
||||||
|
1. **reasoning**: What you observed, what you learned, what to try next
|
||||||
|
2. **actions**: HTTP requests you want the system to execute (max 10 per round)
|
||||||
|
3. **findings**: Vulnerabilities you confirmed based on REAL response evidence
|
||||||
|
4. **phase**: Current phase (recon, testing, post_exploitation, reporting)
|
||||||
|
5. **done**: true when you've completed the full pentest cycle
|
||||||
|
|
||||||
|
The system executes your HTTP requests and returns the actual responses.
|
||||||
|
You then analyze those responses and plan your next actions.
|
||||||
|
|
||||||
|
## PHASES
|
||||||
|
|
||||||
|
### Phase 1: RECON (rounds 1-8)
|
||||||
|
- Fingerprint technologies (server headers, cookies, response patterns)
|
||||||
|
- Discover endpoints (crawl links, check robots.txt, sitemap.xml)
|
||||||
|
- Map input vectors (forms, parameters, headers, cookies)
|
||||||
|
- Identify authentication mechanisms
|
||||||
|
- Check for common files (.env, .git, admin panels)
|
||||||
|
|
||||||
|
### Phase 2: TESTING (rounds 9-25)
|
||||||
|
Test each discovered endpoint for:
|
||||||
|
- SQL Injection (error-based, boolean-based, time-based, UNION-based)
|
||||||
|
- Cross-Site Scripting (reflected, stored, DOM-based)
|
||||||
|
- Local/Remote File Inclusion (LFI/RFI)
|
||||||
|
- Command Injection (OS command injection via various delimiters)
|
||||||
|
- Authentication bypass
|
||||||
|
- SSRF, CSRF, IDOR, XXE
|
||||||
|
- Security misconfigurations
|
||||||
|
- Sensitive data exposure
|
||||||
|
- Directory traversal
|
||||||
|
|
||||||
|
### Phase 3: POST-EXPLOITATION (rounds 26-28)
|
||||||
|
- Extract data from confirmed vulnerabilities
|
||||||
|
- Chain vulnerabilities for maximum impact
|
||||||
|
- Test privilege escalation paths
|
||||||
|
- Verify data exposure scope
|
||||||
|
|
||||||
|
### Phase 4: REPORTING (round 29-30)
|
||||||
|
- Compile all findings with evidence
|
||||||
|
- Set done=true
|
||||||
|
|
||||||
|
{methodology_section}
|
||||||
|
|
||||||
|
## CRITICAL RULES
|
||||||
|
|
||||||
|
1. **REAL EVIDENCE ONLY**: Never claim a vulnerability without evidence from an actual response.
|
||||||
|
- SQLi: Show the SQL error message or extracted data from the response body
|
||||||
|
- XSS: Show the reflected payload in the response body unescaped
|
||||||
|
- LFI: Show file contents (e.g., /etc/passwd content) in the response
|
||||||
|
- Command Injection: Show command output in the response
|
||||||
|
|
||||||
|
2. **NO HALLUCINATION**: If a test fails (payload is filtered, no error), say so honestly.
|
||||||
|
Do NOT fabricate evidence. The system will verify your claims.
|
||||||
|
|
||||||
|
3. **ADAPT**: If WAF blocks payloads, try encoding, case variation, alternative syntax.
|
||||||
|
If an endpoint 404s, move to the next one. Don't repeat failed tests.
|
||||||
|
|
||||||
|
4. **BE SPECIFIC**: Include exact URLs, parameters, payloads, and expected vs actual behavior.
|
||||||
|
|
||||||
|
5. **PROGRESS**: Don't repeat the same tests. Track what you've already tested.
|
||||||
|
|
||||||
|
## OUTPUT FORMAT (strict JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{{
|
||||||
|
"phase": "recon|testing|post_exploitation|reporting",
|
||||||
|
"reasoning": "Detailed explanation of what you observed and why you're taking these actions",
|
||||||
|
"actions": [
|
||||||
|
{{
|
||||||
|
"method": "GET|POST|PUT|DELETE|OPTIONS|HEAD|PATCH",
|
||||||
|
"url": "https://target.com/path?param=value",
|
||||||
|
"headers": {{"Header-Name": "value"}},
|
||||||
|
"body": "form or raw body data (for POST/PUT)",
|
||||||
|
"content_type": "application/x-www-form-urlencoded|application/json|multipart/form-data",
|
||||||
|
"purpose": "What this request tests"
|
||||||
|
}}
|
||||||
|
],
|
||||||
|
"findings": [
|
||||||
|
{{
|
||||||
|
"title": "SQL Injection in /login username parameter",
|
||||||
|
"severity": "critical|high|medium|low|info",
|
||||||
|
"vulnerability_type": "sql_injection|xss_reflected|xss_stored|lfi|rfi|command_injection|ssrf|csrf|idor|xxe|auth_bypass|open_redirect|directory_listing|info_disclosure|security_misconfiguration",
|
||||||
|
"affected_endpoint": "/login",
|
||||||
|
"parameter": "username",
|
||||||
|
"payload": "' OR 1=1--",
|
||||||
|
"evidence": "Response contained: You have an error in your SQL syntax...",
|
||||||
|
"description": "The username parameter is vulnerable to SQL injection...",
|
||||||
|
"impact": "An attacker could bypass authentication and extract all database contents",
|
||||||
|
"cvss_score": 9.8,
|
||||||
|
"cwe_id": "CWE-89",
|
||||||
|
"poc_code": "curl -X POST 'https://target/login' -d 'username=%27+OR+1%3D1--&password=test'",
|
||||||
|
"remediation": "Use parameterized queries / prepared statements"
|
||||||
|
}}
|
||||||
|
],
|
||||||
|
"done": false,
|
||||||
|
"summary": "Only set when done=true. Full executive summary of the pentest."
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
|
IMPORTANT: Output ONLY valid JSON. No markdown, no text before or after the JSON object."""
|
||||||
|
|
||||||
|
|
||||||
|
def get_full_llm_pentest_round_prompt(
|
||||||
|
target: str,
|
||||||
|
round_num: int,
|
||||||
|
max_rounds: int,
|
||||||
|
previous_results: str,
|
||||||
|
discovered_info: str,
|
||||||
|
findings_so_far: int,
|
||||||
|
) -> str:
|
||||||
|
"""Build the round prompt for each iteration of the Full LLM Pentest loop."""
|
||||||
|
|
||||||
|
phase_hint = ""
|
||||||
|
if round_num <= 8:
|
||||||
|
phase_hint = "You should be in the RECON phase. Focus on discovering endpoints, technologies, and input vectors."
|
||||||
|
elif round_num <= 25:
|
||||||
|
phase_hint = "You should be in the TESTING phase. Test discovered endpoints for vulnerabilities."
|
||||||
|
elif round_num <= 28:
|
||||||
|
phase_hint = "You should be in the POST-EXPLOITATION phase. Chain vulnerabilities and extract data."
|
||||||
|
else:
|
||||||
|
phase_hint = "You should be in the REPORTING phase. Compile final findings and set done=true."
|
||||||
|
|
||||||
|
return f"""## ROUND {round_num}/{max_rounds}
|
||||||
|
|
||||||
|
Target: {target}
|
||||||
|
Findings so far: {findings_so_far}
|
||||||
|
{phase_hint}
|
||||||
|
|
||||||
|
{"WARNING: This is your LAST round. Set done=true and include your final summary." if round_num >= max_rounds else ""}
|
||||||
|
|
||||||
|
## WHAT YOU KNOW SO FAR
|
||||||
|
|
||||||
|
{discovered_info if discovered_info else "Nothing discovered yet. Start with basic recon."}
|
||||||
|
|
||||||
|
## PREVIOUS ROUND RESULTS
|
||||||
|
|
||||||
|
{previous_results if previous_results else "This is the first round. No previous results."}
|
||||||
|
|
||||||
|
Plan your next actions. Remember:
|
||||||
|
- Max 10 HTTP requests per round
|
||||||
|
- Be strategic — don't waste requests on unlikely paths
|
||||||
|
- Build on what you've learned from previous responses
|
||||||
|
- Report findings as soon as you have REAL evidence
|
||||||
|
|
||||||
|
Output your response as a single JSON object."""
|
||||||
|
|
||||||
|
|
||||||
|
def get_full_llm_pentest_report_prompt(
|
||||||
|
target: str,
|
||||||
|
findings_json: str,
|
||||||
|
total_rounds: int,
|
||||||
|
total_requests: int,
|
||||||
|
) -> str:
|
||||||
|
"""Prompt for the LLM to generate the final pentest report."""
|
||||||
|
return f"""Generate a professional penetration test report for the following engagement.
|
||||||
|
|
||||||
|
## Engagement Details
|
||||||
|
- Target: {target}
|
||||||
|
- Testing Rounds: {total_rounds}
|
||||||
|
- Total HTTP Requests: {total_requests}
|
||||||
|
- Methodology: AI-Driven Full Pentest (LLM as Testing Engine)
|
||||||
|
|
||||||
|
## Confirmed Findings
|
||||||
|
|
||||||
|
{findings_json}
|
||||||
|
|
||||||
|
## Report Structure
|
||||||
|
|
||||||
|
Generate a comprehensive report with:
|
||||||
|
|
||||||
|
1. **Executive Summary** — Business impact (non-technical language), overall risk rating, key findings
|
||||||
|
2. **Scope and Methodology** — What was tested, approach taken, standards followed (OWASP, PTES)
|
||||||
|
3. **Detailed Findings** — For each vulnerability: title, severity, description, evidence, impact, remediation, OWASP/CWE references
|
||||||
|
4. **Risk Prioritization Table** — All findings sorted by severity with CVSS scores
|
||||||
|
5. **Remediation Roadmap** — Short-term fixes, medium-term improvements, long-term recommendations
|
||||||
|
6. **Conclusion**
|
||||||
|
|
||||||
|
Write in professional English suitable for C-level stakeholders and technical teams.
|
||||||
|
Be precise, structured, and security-focused.
|
||||||
|
|
||||||
|
Output the report as a markdown document."""
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
Crosshair, Shield, ChevronDown, ChevronUp, Loader2,
|
Crosshair, Shield, ChevronDown, ChevronUp, Loader2,
|
||||||
AlertTriangle, CheckCircle2, Globe, Lock, Bug,
|
AlertTriangle, CheckCircle2, Globe, Lock, Bug,
|
||||||
FileText, ScrollText, X, ExternalLink, Download, Sparkles,
|
FileText, ScrollText, X, ExternalLink, Download, Sparkles,
|
||||||
Brain, Wrench, Layers, Trash2, Clock, Search,
|
Brain, Trash2, Clock, Search,
|
||||||
Activity, Terminal
|
Activity, Terminal
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { PieChart, Pie, Cell, Tooltip as RechartsTooltip, ResponsiveContainer } from 'recharts'
|
import { PieChart, Pie, Cell, Tooltip as RechartsTooltip, ResponsiveContainer } from 'recharts'
|
||||||
@@ -14,22 +14,12 @@ import type { AgentStatus, AgentFinding, AgentLog, ToolExecution, ContainerStatu
|
|||||||
// ─── Constants ────────────────────────────────────────────────────────────────
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
const PHASES = [
|
const PHASES = [
|
||||||
{ key: 'parallel', label: 'Parallel Streams', icon: Layers, range: [0, 50] as const },
|
{ key: 'recon', label: 'AI Recon', icon: Globe, range: [0, 25] as const },
|
||||||
{ key: 'deep', label: 'Deep Analysis', icon: Brain, range: [50, 75] as const },
|
{ key: 'testing', label: 'AI Testing', icon: Bug, range: [25, 70] as const },
|
||||||
{ key: 'final', label: 'Finalization', icon: Shield, range: [75, 100] as const },
|
{ key: 'postexploit', label: 'Post-Exploitation', icon: Brain, range: [70, 85] as const },
|
||||||
|
{ key: 'report', label: 'Report', icon: Shield, range: [85, 100] as const },
|
||||||
]
|
]
|
||||||
|
|
||||||
const STREAMS = [
|
|
||||||
{ key: 'recon', label: 'Recon', icon: Globe, color: 'blue', activeUntil: 25 },
|
|
||||||
{ key: 'junior', label: 'Junior AI', icon: Brain, color: 'purple', activeUntil: 35 },
|
|
||||||
{ key: 'tools', label: 'Tools', icon: Wrench, color: 'orange', activeUntil: 50 },
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const STREAM_COLORS: Record<string, { bg: string; text: string; border: string; pulse: string }> = {
|
|
||||||
blue: { bg: 'bg-blue-500/20', text: 'text-blue-400', border: 'border-blue-500/40', pulse: 'bg-blue-400' },
|
|
||||||
purple: { bg: 'bg-purple-500/20', text: 'text-purple-400', border: 'border-purple-500/40', pulse: 'bg-purple-400' },
|
|
||||||
orange: { bg: 'bg-orange-500/20', text: 'text-orange-400', border: 'border-orange-500/40', pulse: 'bg-orange-400' },
|
|
||||||
}
|
|
||||||
|
|
||||||
const SEVERITY_COLORS: Record<string, string> = {
|
const SEVERITY_COLORS: Record<string, string> = {
|
||||||
critical: 'bg-red-500', high: 'bg-orange-500', medium: 'bg-yellow-500',
|
critical: 'bg-red-500', high: 'bg-orange-500', medium: 'bg-yellow-500',
|
||||||
@@ -53,11 +43,8 @@ const CONFIDENCE_STYLES: Record<string, string> = {
|
|||||||
|
|
||||||
const LOG_FILTERS = [
|
const LOG_FILTERS = [
|
||||||
{ key: 'all', label: 'All', color: '' },
|
{ key: 'all', label: 'All', color: '' },
|
||||||
{ key: 'stream1', label: 'Recon', color: 'text-blue-400' },
|
{ key: 'llm', label: 'LLM Pentest', color: 'text-red-400' },
|
||||||
{ key: 'stream2', label: 'Junior', color: 'text-purple-400' },
|
{ key: 'ai', label: 'AI Decisions', color: 'text-purple-400' },
|
||||||
{ key: 'stream3', label: 'Tools', color: 'text-orange-400' },
|
|
||||||
{ key: 'deep', label: 'Deep', color: 'text-cyan-400' },
|
|
||||||
{ key: 'container', label: 'Container', color: 'text-cyan-300' },
|
|
||||||
{ key: 'error', label: 'Errors', color: 'text-red-400' },
|
{ key: 'error', label: 'Errors', color: 'text-red-400' },
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -70,9 +57,10 @@ const MAX_TOASTS = 5
|
|||||||
// ─── Utility Functions ────────────────────────────────────────────────────────
|
// ─── Utility Functions ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function phaseFromProgress(progress: number): number {
|
function phaseFromProgress(progress: number): number {
|
||||||
if (progress < 50) return 0
|
if (progress < 25) return 0
|
||||||
if (progress < 75) return 1
|
if (progress < 70) return 1
|
||||||
return 2
|
if (progress < 85) return 2
|
||||||
|
return 3
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatElapsed(totalSeconds: number): string {
|
function formatElapsed(totalSeconds: number): string {
|
||||||
@@ -83,6 +71,7 @@ function formatElapsed(totalSeconds: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function logMessageColor(message: string): string {
|
function logMessageColor(message: string): string {
|
||||||
|
if (message.startsWith('[LLM PENTEST]')) return 'text-red-400'
|
||||||
if (message.startsWith('[STREAM 1]')) return 'text-blue-400'
|
if (message.startsWith('[STREAM 1]')) return 'text-blue-400'
|
||||||
if (message.startsWith('[STREAM 2]')) return 'text-purple-400'
|
if (message.startsWith('[STREAM 2]')) return 'text-purple-400'
|
||||||
if (message.startsWith('[STREAM 3]')) return 'text-orange-400'
|
if (message.startsWith('[STREAM 3]')) return 'text-orange-400'
|
||||||
@@ -101,11 +90,8 @@ function logMessageColor(message: string): string {
|
|||||||
|
|
||||||
function matchLogFilter(log: AgentLog, filter: string): boolean {
|
function matchLogFilter(log: AgentLog, filter: string): boolean {
|
||||||
if (filter === 'all') return true
|
if (filter === 'all') return true
|
||||||
if (filter === 'stream1') return log.message.startsWith('[STREAM 1]')
|
if (filter === 'llm') return log.message.startsWith('[LLM PENTEST]')
|
||||||
if (filter === 'stream2') return log.message.startsWith('[STREAM 2]')
|
if (filter === 'ai') return log.source === 'llm' || log.message.includes('[AI]') || log.message.includes('[LLM]')
|
||||||
if (filter === 'stream3') return log.message.startsWith('[STREAM 3]')
|
|
||||||
if (filter === 'deep') return log.message.startsWith('[DEEP]')
|
|
||||||
if (filter === 'container') return log.message.startsWith('[CONTAINER]')
|
|
||||||
if (filter === 'error') return log.level === 'error' || log.level === 'warning'
|
if (filter === 'error') return log.level === 'error' || log.level === 'warning'
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -139,33 +125,6 @@ interface Toast {
|
|||||||
|
|
||||||
// ─── Sub-Components ───────────────────────────────────────────────────────────
|
// ─── Sub-Components ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function StreamBadge({ stream, progress, isRunning }: {
|
|
||||||
stream: typeof STREAMS[number]; progress: number; isRunning: boolean
|
|
||||||
}) {
|
|
||||||
const active = isRunning && progress < stream.activeUntil
|
|
||||||
const done = progress >= stream.activeUntil
|
|
||||||
const colors = STREAM_COLORS[stream.color]
|
|
||||||
const Icon = stream.icon
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-xs font-medium transition-all duration-300 ${
|
|
||||||
active ? `${colors.bg} ${colors.text} ${colors.border}` :
|
|
||||||
done ? 'bg-dark-700/50 text-dark-400 border-dark-600' :
|
|
||||||
'bg-dark-900 text-dark-500 border-dark-700'
|
|
||||||
}`}>
|
|
||||||
{active && (
|
|
||||||
<span className="relative flex h-2 w-2">
|
|
||||||
<span className={`animate-ping absolute inline-flex h-full w-full rounded-full ${colors.pulse} opacity-75`} />
|
|
||||||
<span className={`relative inline-flex rounded-full h-2 w-2 ${colors.pulse}`} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{done && <CheckCircle2 className="w-3 h-3 text-green-500" />}
|
|
||||||
{!active && !done && <Icon className="w-3 h-3" />}
|
|
||||||
<span>{stream.label}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LiveStatsDashboard({ status, elapsedSeconds, toolExecutions }: {
|
function LiveStatsDashboard({ status, elapsedSeconds, toolExecutions }: {
|
||||||
status: AgentStatus; elapsedSeconds: number; toolExecutions: ToolExecution[]
|
status: AgentStatus; elapsedSeconds: number; toolExecutions: ToolExecution[]
|
||||||
}) {
|
}) {
|
||||||
@@ -487,13 +446,20 @@ export default function FullIATestingPage() {
|
|||||||
// ─── Elapsed Time Ticker ──────────────────────────────────────────────────
|
// ─── Elapsed Time Ticker ──────────────────────────────────────────────────
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isRunning || !status?.started_at) return
|
if (!status?.started_at) return
|
||||||
const startTime = new Date(status.started_at).getTime()
|
const startTime = new Date(status.started_at).getTime()
|
||||||
|
if (isRunning) {
|
||||||
const tick = () => setElapsedSeconds(Math.floor((Date.now() - startTime) / 1000))
|
const tick = () => setElapsedSeconds(Math.floor((Date.now() - startTime) / 1000))
|
||||||
tick()
|
tick()
|
||||||
const id = setInterval(tick, 1000)
|
const id = setInterval(tick, 1000)
|
||||||
return () => clearInterval(id)
|
return () => clearInterval(id)
|
||||||
}, [isRunning, status?.started_at])
|
} else {
|
||||||
|
const endTime = status.completed_at
|
||||||
|
? new Date(status.completed_at).getTime()
|
||||||
|
: Date.now()
|
||||||
|
setElapsedSeconds(Math.max(0, Math.floor((endTime - startTime) / 1000)))
|
||||||
|
}
|
||||||
|
}, [isRunning, status?.started_at, status?.completed_at])
|
||||||
|
|
||||||
// ─── Polling ──────────────────────────────────────────────────────────────
|
// ─── Polling ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -593,8 +559,9 @@ export default function FullIATestingPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await agentApi.autoPentest(primaryTarget, {
|
const resp = await agentApi.autoPentest(primaryTarget, {
|
||||||
|
mode: 'full_llm_pentest',
|
||||||
prompt: promptContent,
|
prompt: promptContent,
|
||||||
enable_kali_sandbox: true,
|
enable_kali_sandbox: false,
|
||||||
auth_type: authType || undefined,
|
auth_type: authType || undefined,
|
||||||
auth_value: authValue || undefined,
|
auth_value: authValue || undefined,
|
||||||
preferred_provider: selectedProvider || undefined,
|
preferred_provider: selectedProvider || undefined,
|
||||||
@@ -603,7 +570,7 @@ export default function FullIATestingPage() {
|
|||||||
|
|
||||||
setAgentId(resp.agent_id)
|
setAgentId(resp.agent_id)
|
||||||
setIsRunning(true)
|
setIsRunning(true)
|
||||||
addToast('FULL AI pentest started', 'info')
|
addToast('Full LLM Pentest started', 'info')
|
||||||
localStorage.setItem(SESSION_KEY, JSON.stringify({
|
localStorage.setItem(SESSION_KEY, JSON.stringify({
|
||||||
agentId: resp.agent_id,
|
agentId: resp.agent_id,
|
||||||
target: primaryTarget,
|
target: primaryTarget,
|
||||||
@@ -705,9 +672,9 @@ export default function FullIATestingPage() {
|
|||||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-red-500/20 rounded-2xl mb-4">
|
<div className="inline-flex items-center justify-center w-16 h-16 bg-red-500/20 rounded-2xl mb-4">
|
||||||
<Crosshair className="w-8 h-8 text-red-400" />
|
<Crosshair className="w-8 h-8 text-red-400" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-3xl font-bold text-white mb-2">FULL AI TESTING</h1>
|
<h1 className="text-3xl font-bold text-white mb-2">FULL LLM PENTEST</h1>
|
||||||
<p className="text-dark-400 max-w-md mx-auto text-sm">
|
<p className="text-dark-400 max-w-md mx-auto text-sm">
|
||||||
Complete AI-driven penetration test. Recon, exploitation, post-exploitation with Kali sandbox.
|
The LLM drives the entire pentest cycle. AI plans HTTP requests, system executes, AI analyzes and adapts.
|
||||||
</p>
|
</p>
|
||||||
{promptContent && (
|
{promptContent && (
|
||||||
<button
|
<button
|
||||||
@@ -766,13 +733,13 @@ export default function FullIATestingPage() {
|
|||||||
|
|
||||||
<div className="flex flex-wrap gap-2 mb-6">
|
<div className="flex flex-wrap gap-2 mb-6">
|
||||||
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-red-500/10 border border-red-500/20 rounded-lg text-xs text-red-400">
|
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-red-500/10 border border-red-500/20 rounded-lg text-xs text-red-400">
|
||||||
<Crosshair className="w-3 h-3" /> Full Pentest Cycle
|
<Brain className="w-3 h-3" /> LLM-Driven Pentest
|
||||||
</span>
|
|
||||||
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-orange-500/10 border border-orange-500/20 rounded-lg text-xs text-orange-400">
|
|
||||||
<Wrench className="w-3 h-3" /> Kali Sandbox
|
|
||||||
</span>
|
</span>
|
||||||
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-purple-500/10 border border-purple-500/20 rounded-lg text-xs text-purple-400">
|
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-purple-500/10 border border-purple-500/20 rounded-lg text-xs text-purple-400">
|
||||||
<Brain className="w-3 h-3" /> AI Researcher
|
<Crosshair className="w-3 h-3" /> AI Plans & Executes HTTP
|
||||||
|
</span>
|
||||||
|
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-orange-500/10 border border-orange-500/20 rounded-lg text-xs text-orange-400">
|
||||||
|
<Shield className="w-3 h-3" /> Full Validation Pipeline
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -865,8 +832,8 @@ export default function FullIATestingPage() {
|
|||||||
disabled={!target.trim() || !promptContent || promptLoading}
|
disabled={!target.trim() || !promptContent || promptLoading}
|
||||||
className="w-full py-4 bg-red-500 hover:bg-red-600 disabled:bg-dark-600 disabled:text-dark-400 text-white font-bold text-lg rounded-xl transition-colors flex items-center justify-center gap-3"
|
className="w-full py-4 bg-red-500 hover:bg-red-600 disabled:bg-dark-600 disabled:text-dark-400 text-white font-bold text-lg rounded-xl transition-colors flex items-center justify-center gap-3"
|
||||||
>
|
>
|
||||||
<Crosshair className="w-6 h-6" />
|
<Brain className="w-6 h-6" />
|
||||||
START FULL AI PENTEST
|
START FULL LLM PENTEST
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -885,9 +852,9 @@ export default function FullIATestingPage() {
|
|||||||
status?.status === 'error' ? 'bg-red-500' : 'bg-gray-500'
|
status?.status === 'error' ? 'bg-red-500' : 'bg-gray-500'
|
||||||
}`} />
|
}`} />
|
||||||
<h3 className="text-white font-semibold truncate">
|
<h3 className="text-white font-semibold truncate">
|
||||||
{isRunning ? 'FULL AI Pentest Running' :
|
{isRunning ? 'Full LLM Pentest Running' :
|
||||||
status?.status === 'completed' ? 'Pentest Complete' :
|
status?.status === 'completed' ? 'LLM Pentest Complete' :
|
||||||
status?.status === 'error' ? 'Pentest Failed' : 'Pentest Stopped'}
|
status?.status === 'error' ? 'LLM Pentest Failed' : 'LLM Pentest Stopped'}
|
||||||
</h3>
|
</h3>
|
||||||
<span className="text-dark-400 text-sm truncate max-w-[200px] sm:max-w-[300px] hidden sm:inline">{target}</span>
|
<span className="text-dark-400 text-sm truncate max-w-[200px] sm:max-w-[300px] hidden sm:inline">{target}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -946,7 +913,7 @@ export default function FullIATestingPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Phase Indicators */}
|
{/* Phase Indicators */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||||
{PHASES.map((phase, idx) => {
|
{PHASES.map((phase, idx) => {
|
||||||
const Icon = phase.icon
|
const Icon = phase.icon
|
||||||
const isActive = idx === currentPhaseIdx && isRunning
|
const isActive = idx === currentPhaseIdx && isRunning
|
||||||
@@ -971,13 +938,6 @@ export default function FullIATestingPage() {
|
|||||||
{phase.range[0]}-{phase.range[1]}%
|
{phase.range[0]}-{phase.range[1]}%
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{idx === 0 && (
|
|
||||||
<div className="flex flex-wrap gap-1.5 mt-2">
|
|
||||||
{STREAMS.map(stream => (
|
|
||||||
<StreamBadge key={stream.key} stream={stream} progress={status.progress} isRunning={isRunning} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -1272,7 +1232,7 @@ export default function FullIATestingPage() {
|
|||||||
{isRunning ? (
|
{isRunning ? (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
FULL AI pentest in progress... Findings will appear as discovered.
|
Full LLM Pentest in progress... AI is planning and executing tests.
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
'No findings'
|
'No findings'
|
||||||
@@ -1304,7 +1264,7 @@ export default function FullIATestingPage() {
|
|||||||
<AlertTriangle className="w-6 h-6 text-yellow-500" />
|
<AlertTriangle className="w-6 h-6 text-yellow-500" />
|
||||||
)}
|
)}
|
||||||
<h3 className={`${status.status === 'completed' ? 'text-green-400' : 'text-yellow-400'} font-semibold text-lg`}>
|
<h3 className={`${status.status === 'completed' ? 'text-green-400' : 'text-yellow-400'} font-semibold text-lg`}>
|
||||||
{status.status === 'completed' ? 'FULL AI Pentest Complete' : 'Pentest Stopped'}
|
{status.status === 'completed' ? 'Full LLM Pentest Complete' : 'LLM Pentest Stopped'}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user