Merge pull request #17 from giveen/hexstrike-ai-integration
MCP: Add Metasploit integration, HexStrike parity, auto-start flags and SSETransport improvements
67
.env.example
@@ -1,4 +1,69 @@
|
||||
# PentestAgent Configuration
|
||||
# PentestAgent example configuration (copy to .env and edit as needed)
|
||||
# Copy this file to `.env` and set values appropriate for your environment.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core settings
|
||||
LLM_PROVIDER=ollama
|
||||
#OLLAMA_BASE_URL=http://127.0.0.1:11434
|
||||
PENTESTAGENT_MODEL="ollama/qwen2.5:7b-instruct"
|
||||
OLLAMA_API_BASE=http://127.0.0.1:11434
|
||||
|
||||
# Debugging
|
||||
PENTESTAGENT_DEBUG=true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# MCP / Vendored tools (grouped)
|
||||
# - Use the LAUNCH_* flags to allow the setup script or manager to auto-start
|
||||
# vendored MCP servers and helper daemons. Set to `true` to enable auto-start.
|
||||
# - Defaults are `false` to avoid automatically running networked services.
|
||||
|
||||
# Vendored HexStrike MCP adapter (legacy name support: LAUNCH_HEXSTRIKE)
|
||||
LAUNCH_HEXTRIKE=false
|
||||
#LAUNCH_HEXSTRIKE=false # alternate spelling (kept for compatibility)
|
||||
|
||||
# Metasploit MCP (MetasploitMCP)
|
||||
# When `LAUNCH_METASPLOIT_MCP=true` the setup script may attempt to start
|
||||
# `msfrpcd` (Metasploit RPC daemon) and then start the vendored MetasploitMCP
|
||||
# HTTP/SSE server. Provide `MSF_PASSWORD` if you want the setup script to
|
||||
# auto-launch `msfrpcd` (it will never invoke sudo).
|
||||
LAUNCH_METASPLOIT_MCP=false
|
||||
|
||||
# When set to `true`, the subtree helper scripts (e.g. scripts/add_metasploit_subtree.sh)
|
||||
# will force a pull/update of vendored subtrees. Useful when you want to refresh
|
||||
# the third_party trees during setup. Set to `true` to enable.
|
||||
FORCE_SUBTREE_PULL=true
|
||||
|
||||
# Metasploit RPC (msfrpcd) connection settings
|
||||
# - `MSF_USER`/`MSF_PASSWORD`: msfrpcd credentials (keep password secret)
|
||||
# - `MSF_SERVER`/`MSF_PORT`: host/port where msfrpcd listens (typically 127.0.0.1)
|
||||
# - `MSF_SSL`: set to `true` if msfrpcd is configured with TLS
|
||||
MSF_USER=msf
|
||||
# set a non-empty password if you want setup to auto-start msfrpcd
|
||||
MSF_PASSWORD=
|
||||
MSF_SERVER=127.0.0.1
|
||||
MSF_PORT=55553
|
||||
MSF_SSL=false
|
||||
|
||||
# Where to save any payloads generated by vendored MCP servers (optional)
|
||||
#PAYLOAD_SAVE_DIR=$HOME/payloads
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional provider API keys and overrides
|
||||
#OPENAI_API_KEY=
|
||||
#ANTHROPIC_API_KEY=
|
||||
#GEMINI_API_KEY=
|
||||
|
||||
# Embeddings (for RAG knowledge base): options include 'openai' or 'local'
|
||||
PENTESTAGENT_EMBEDDINGS=local
|
||||
|
||||
# Optional daily token budgeting (examples)
|
||||
#DAILY_TOKEN_LIMIT=1000000
|
||||
#PENTESTAGENT_DAILY_TOKEN_BUDGET=500000
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Notes:
|
||||
# - Never commit your real API keys or passwords. Keep `.env` out of version control.
|
||||
# - Use `LAUNCH_METASPLOIT_MCP=true` only in trusted test environments.# PentestAgent Configuration
|
||||
|
||||
# API Keys (set at least one for chat model)
|
||||
OPENAI_API_KEY=
|
||||
|
||||
11
README.md
@@ -210,3 +210,14 @@ Only use against systems you have explicit authorization to test. Unauthorized a
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## HexStrike Integration & Thanks
|
||||
|
||||
This branch vendors an optional integration with HexStrike (a powerful MCP-enabled scoring and tooling framework). HexStrike acts as a force-multiplier for PentestAgent by exposing a rich set of automated pentesting tools and workflows that the agent can call via MCP — greatly expanding available capabilities with minimal setup.
|
||||
|
||||
Special thanks and credit to the HexStrike project and its author: https://github.com/0x4m4/hexstrike-ai
|
||||
|
||||
Notes:
|
||||
- HexStrike is vendored under `third_party/hexstrike` and is opt-in; follow `scripts/install_hexstrike_deps.sh` to install its Python dependencies.
|
||||
- Auto-start of the vendored HexStrike adapter is controlled via the `.env` flag `LAUNCH_HEXTRIKE` and can be enabled per-user.
|
||||
- This update also includes several TUI fixes (improved background worker handling and safer task cancellation) to stabilize the terminal UI while using long-running MCP tools.
|
||||
|
||||
@@ -111,6 +111,18 @@ Examples:
|
||||
mcp_remove = mcp_subparsers.add_parser("remove", help="Remove an MCP server")
|
||||
mcp_remove.add_argument("name", help="Server name to remove")
|
||||
|
||||
# mcp disable
|
||||
mcp_disable = mcp_subparsers.add_parser(
|
||||
"disable", help="Disable an MCP server (update config)"
|
||||
)
|
||||
mcp_disable.add_argument("name", help="Server name to disable")
|
||||
|
||||
# mcp enable
|
||||
mcp_enable = mcp_subparsers.add_parser(
|
||||
"enable", help="Enable an MCP server (update config)"
|
||||
)
|
||||
mcp_enable.add_argument("name", help="Server name to enable")
|
||||
|
||||
# mcp test
|
||||
mcp_test = mcp_subparsers.add_parser("test", help="Test MCP server connection")
|
||||
mcp_test.add_argument("name", help="Server name to test")
|
||||
@@ -259,6 +271,18 @@ def handle_mcp_command(args: argparse.Namespace):
|
||||
else:
|
||||
console.print(f"[red]Server not found: {args.name}[/]")
|
||||
|
||||
elif args.mcp_command == "disable":
|
||||
if manager.set_enabled(args.name, False):
|
||||
console.print(f"[yellow]Disabled MCP server in config: {args.name}[/]")
|
||||
else:
|
||||
console.print(f"[red]Server not found: {args.name}[/]")
|
||||
|
||||
elif args.mcp_command == "enable":
|
||||
if manager.set_enabled(args.name, True):
|
||||
console.print(f"[green]Enabled MCP server in config: {args.name}[/]")
|
||||
else:
|
||||
console.print(f"[red]Server not found: {args.name}[/]")
|
||||
|
||||
elif args.mcp_command == "test":
|
||||
console.print(f"[bold]Testing MCP server: {args.name}[/]\n")
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import re
|
||||
import textwrap
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast
|
||||
|
||||
from rich.text import Text
|
||||
from textual import on, work
|
||||
@@ -195,6 +195,95 @@ class HelpScreen(ModalScreen):
|
||||
self.app.pop_screen()
|
||||
|
||||
|
||||
class ToolsScreen(ModalScreen):
|
||||
"""Interactive tools browser — split-pane layout.
|
||||
|
||||
Left pane: tree of tools. Right pane: full description (scrollable).
|
||||
Selecting another tool replaces the right-pane content. Close returns
|
||||
to the main screen.
|
||||
"""
|
||||
|
||||
BINDINGS = [Binding("escape", "dismiss", "Close"), Binding("q", "dismiss", "Close")]
|
||||
|
||||
CSS = """
|
||||
ToolsScreen { align: center middle; }
|
||||
"""
|
||||
|
||||
def __init__(self, tools: List[Any]) -> None:
|
||||
super().__init__()
|
||||
self.tools = tools
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
# Build a split view: left tree, right description
|
||||
with Container(id="tools-container"):
|
||||
with Horizontal(id="tools-split"):
|
||||
with Vertical(id="tools-left"):
|
||||
yield Static("Tools", id="tools-title")
|
||||
yield Tree("TOOLS", id="tools-tree")
|
||||
|
||||
with Vertical(id="tools-right"):
|
||||
yield Static("Description", id="tools-desc-title")
|
||||
yield ScrollableContainer(Static("Select a tool to view details.", id="tools-desc"), id="tools-desc-scroll")
|
||||
|
||||
yield Center(Button("Close", id="tools-close"))
|
||||
|
||||
def on_mount(self) -> None:
|
||||
try:
|
||||
tree = self.query_one("#tools-tree", Tree)
|
||||
except Exception:
|
||||
return
|
||||
|
||||
root = tree.root
|
||||
root.allow_expand = True
|
||||
root.show_root = False
|
||||
|
||||
# Populate tool nodes
|
||||
for t in self.tools:
|
||||
name = getattr(t, "name", str(t))
|
||||
root.add(name, data={"tool": t})
|
||||
|
||||
try:
|
||||
tree.focus()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@on(Tree.NodeSelected, "#tools-tree")
|
||||
def on_tool_selected(self, event: Tree.NodeSelected) -> None:
|
||||
node = event.node
|
||||
try:
|
||||
tool = node.data.get("tool") if node.data else None
|
||||
name = node.label or (getattr(tool, "name", str(tool)) if tool else "Unknown")
|
||||
|
||||
# Prefer Tool.description (registered tools use this), then fall back
|
||||
desc = None
|
||||
if tool is not None:
|
||||
desc = getattr(tool, "description", None)
|
||||
if not desc:
|
||||
desc = (
|
||||
getattr(tool, "summary", None)
|
||||
or getattr(tool, "help_text", None)
|
||||
or getattr(tool, "__doc__", None)
|
||||
)
|
||||
if not desc:
|
||||
desc = "No description available."
|
||||
|
||||
# Update right-hand description pane
|
||||
try:
|
||||
desc_widget = self.query_one("#tools-desc", Static)
|
||||
text = Text()
|
||||
text.append(f"{name}\n", style="bold #d4d4d4")
|
||||
text.append(str(desc), style="#d4d4d4")
|
||||
desc_widget.update(text)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@on(Button.Pressed, "#tools-close")
|
||||
def close_tools(self) -> None:
|
||||
self.app.pop_screen()
|
||||
|
||||
|
||||
# ----- Main Chat Message Widgets -----
|
||||
|
||||
|
||||
@@ -903,7 +992,7 @@ class PentestAgentTUI(App):
|
||||
def __init__(
|
||||
self,
|
||||
target: Optional[str] = None,
|
||||
model: str = None,
|
||||
model: Optional[str] = None,
|
||||
use_docker: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -924,7 +1013,8 @@ class PentestAgentTUI(App):
|
||||
self._is_running = False
|
||||
self._is_initializing = True # Block input during init
|
||||
self._should_stop = False
|
||||
self._current_worker = None # Track running worker for cancellation
|
||||
# Worker handle returned by `@work` or an `asyncio.Task` (keep generic)
|
||||
self._current_worker: Optional[Any] = None # Track running worker for cancellation
|
||||
self._current_crew = None # Track crew orchestrator for cancellation
|
||||
|
||||
# Crew mode state
|
||||
@@ -973,7 +1063,8 @@ class PentestAgentTUI(App):
|
||||
|
||||
async def on_mount(self) -> None:
|
||||
"""Initialize on mount"""
|
||||
self._initialize_agent()
|
||||
# Call the textual worker - decorator returns a Worker, not a coroutine
|
||||
_ = cast(Any, self._initialize_agent())
|
||||
|
||||
@work(thread=False)
|
||||
async def _initialize_agent(self) -> None:
|
||||
@@ -1048,6 +1139,9 @@ class PentestAgentTUI(App):
|
||||
await self.runtime.start()
|
||||
|
||||
# LLM
|
||||
# Ensure types for static analysis: runtime and model are set
|
||||
assert self.model is not None
|
||||
assert self.runtime is not None
|
||||
llm = LLM(
|
||||
model=self.model,
|
||||
config=ModelConfig(temperature=0.7),
|
||||
@@ -1055,7 +1149,7 @@ class PentestAgentTUI(App):
|
||||
)
|
||||
|
||||
# Tools
|
||||
self.all_tools = get_all_tools()
|
||||
self.all_tools = get_all_tools() # Ensure tools are loaded
|
||||
|
||||
# Agent
|
||||
self.agent = PentestAgentAgent(
|
||||
@@ -1201,7 +1295,7 @@ class PentestAgentTUI(App):
|
||||
for key, value in notes.items():
|
||||
# Show full value, indent multi-line content
|
||||
if "\n" in value:
|
||||
indented = value.replace("\n", "\n ")
|
||||
indented = str(value).replace("\n", "\n ")
|
||||
lines.append(f"\n[{key}]\n {indented}")
|
||||
else:
|
||||
lines.append(f"[{key}] {value}")
|
||||
@@ -1458,7 +1552,8 @@ Be concise. Use the actual data from notes."""
|
||||
|
||||
# Use assist mode by default
|
||||
if self.agent and not self._is_running:
|
||||
self._current_worker = self._run_assist(message)
|
||||
# Schedule assist run and keep task handle
|
||||
self._current_worker = asyncio.create_task(self._run_assist(message))
|
||||
|
||||
async def _handle_command(self, cmd: str) -> None:
|
||||
"""Handle slash commands"""
|
||||
@@ -1476,31 +1571,36 @@ Be concise. Use the actual data from notes."""
|
||||
self.agent.conversation_history.clear()
|
||||
self._add_system("Chat cleared")
|
||||
elif cmd_lower == "/tools":
|
||||
from ..runtime.runtime import detect_environment
|
||||
# Open the interactive tools browser (split-pane).
|
||||
try:
|
||||
await self.push_screen(ToolsScreen(tools=self.all_tools))
|
||||
except Exception:
|
||||
# Fallback: list tools in the system area if UI push fails
|
||||
from ..runtime.runtime import detect_environment
|
||||
|
||||
names = [t.name for t in self.all_tools]
|
||||
msg = f"Tools ({len(names)}): " + ", ".join(names)
|
||||
names = [t.name for t in self.all_tools]
|
||||
msg = f"Tools ({len(names)}): " + ", ".join(names)
|
||||
|
||||
# Add detected CLI tools
|
||||
env = detect_environment()
|
||||
if env.available_tools:
|
||||
# Group by category
|
||||
by_category = {}
|
||||
for tool_info in env.available_tools:
|
||||
if tool_info.category not in by_category:
|
||||
by_category[tool_info.category] = []
|
||||
by_category[tool_info.category].append(tool_info.name)
|
||||
# Add detected CLI tools
|
||||
env = detect_environment()
|
||||
if env.available_tools:
|
||||
# Group by category
|
||||
by_category = {}
|
||||
for tool_info in env.available_tools:
|
||||
if tool_info.category not in by_category:
|
||||
by_category[tool_info.category] = []
|
||||
by_category[tool_info.category].append(tool_info.name)
|
||||
|
||||
cli_sections = []
|
||||
for category in sorted(by_category.keys()):
|
||||
tools_list = ", ".join(sorted(by_category[category]))
|
||||
cli_sections.append(f"{category}: {tools_list}")
|
||||
cli_sections = []
|
||||
for category in sorted(by_category.keys()):
|
||||
tools_list = ", ".join(sorted(by_category[category]))
|
||||
cli_sections.append(f"{category}: {tools_list}")
|
||||
|
||||
msg += f"\n\nCLI Tools ({len(env.available_tools)}):\n" + "\n".join(
|
||||
cli_sections
|
||||
)
|
||||
msg += f"\n\nCLI Tools ({len(env.available_tools)}):\n" + "\n".join(
|
||||
cli_sections
|
||||
)
|
||||
|
||||
self._add_system(msg)
|
||||
self._add_system(msg)
|
||||
elif cmd_lower in ["/quit", "/exit", "/q"]:
|
||||
self.exit()
|
||||
elif cmd_lower == "/prompt":
|
||||
@@ -1512,7 +1612,8 @@ Be concise. Use the actual data from notes."""
|
||||
elif cmd_lower == "/notes":
|
||||
await self._show_notes()
|
||||
elif cmd_lower == "/report":
|
||||
self._run_report_generation()
|
||||
# Call the textual worker - decorator returns a Worker
|
||||
_ = cast(Any, self._run_report_generation())
|
||||
elif cmd_original.startswith("/target"):
|
||||
self._set_target(cmd_original)
|
||||
elif cmd_original.startswith("/agent"):
|
||||
@@ -1549,7 +1650,8 @@ Be concise. Use the actual data from notes."""
|
||||
self._hide_sidebar()
|
||||
|
||||
if self.agent and not self._is_running:
|
||||
self._current_worker = self._run_agent_mode(task)
|
||||
# Schedule agent mode and keep task handle
|
||||
self._current_worker = asyncio.create_task(self._run_agent_mode(task))
|
||||
|
||||
async def _parse_crew_command(self, cmd: str) -> None:
|
||||
"""Parse and execute /crew command"""
|
||||
@@ -1577,7 +1679,8 @@ Be concise. Use the actual data from notes."""
|
||||
if not self._is_running:
|
||||
self._add_user(f"/crew {target}")
|
||||
self._show_sidebar()
|
||||
self._current_worker = self._run_crew_mode(target)
|
||||
# Schedule crew mode and keep handle
|
||||
self._current_worker = asyncio.create_task(self._run_crew_mode(target))
|
||||
|
||||
def _show_sidebar(self) -> None:
|
||||
"""Show the sidebar for crew mode."""
|
||||
@@ -1620,8 +1723,12 @@ Be concise. Use the actual data from notes."""
|
||||
self._crew_orchestrator_node = tree.root.add(
|
||||
"CREW", data={"type": "crew", "id": "crew"}
|
||||
)
|
||||
self._crew_orchestrator_node.expand()
|
||||
tree.select_node(self._crew_orchestrator_node)
|
||||
if self._crew_orchestrator_node:
|
||||
try:
|
||||
self._crew_orchestrator_node.expand()
|
||||
tree.select_node(self._crew_orchestrator_node)
|
||||
except Exception:
|
||||
pass
|
||||
self._viewing_worker_id = None
|
||||
|
||||
# Update stats
|
||||
@@ -1723,11 +1830,15 @@ Be concise. Use the actual data from notes."""
|
||||
|
||||
try:
|
||||
label = self._format_worker_label(worker_id)
|
||||
node = self._crew_orchestrator_node.add(
|
||||
label, data={"type": "worker", "id": worker_id}
|
||||
)
|
||||
self._crew_worker_nodes[worker_id] = node
|
||||
self._crew_orchestrator_node.expand()
|
||||
if self._crew_orchestrator_node:
|
||||
node = self._crew_orchestrator_node.add(
|
||||
label, data={"type": "worker", "id": worker_id}
|
||||
)
|
||||
self._crew_worker_nodes[worker_id] = node
|
||||
try:
|
||||
self._crew_orchestrator_node.expand()
|
||||
except Exception:
|
||||
pass
|
||||
self._update_crew_stats()
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1874,6 +1985,10 @@ Be concise. Use the actual data from notes."""
|
||||
# Build prior context from assist/agent conversation history
|
||||
prior_context = self._build_prior_context()
|
||||
|
||||
# Ensure model/runtime are available for static analysis
|
||||
assert self.model is not None
|
||||
assert self.runtime is not None
|
||||
|
||||
llm = LLM(model=self.model, config=ModelConfig(temperature=0.7))
|
||||
|
||||
crew = CrewOrchestrator(
|
||||
@@ -1882,7 +1997,7 @@ Be concise. Use the actual data from notes."""
|
||||
runtime=self.runtime,
|
||||
on_worker_event=self._handle_worker_event,
|
||||
rag_engine=self.rag_engine,
|
||||
target=self.target,
|
||||
target=target,
|
||||
prior_context=prior_context,
|
||||
)
|
||||
self._current_crew = crew # Track for cancellation
|
||||
@@ -2148,8 +2263,10 @@ Be concise. Use the actual data from notes."""
|
||||
# Stop any running tasks first
|
||||
if self._is_running:
|
||||
self._should_stop = True
|
||||
if self._current_worker and not self._current_worker.is_finished:
|
||||
self._current_worker.cancel()
|
||||
if self._current_worker and not getattr(self._current_worker, "done", lambda: False)():
|
||||
cancel = getattr(self._current_worker, "cancel", None)
|
||||
if cancel:
|
||||
cancel()
|
||||
if self._current_crew:
|
||||
# Schedule cancel but don't wait - we're exiting
|
||||
asyncio.create_task(self._cancel_crew())
|
||||
@@ -2161,8 +2278,10 @@ Be concise. Use the actual data from notes."""
|
||||
self._add_system("[!] Stopping...")
|
||||
|
||||
# Cancel the running worker to interrupt blocking awaits
|
||||
if self._current_worker and not self._current_worker.is_finished:
|
||||
self._current_worker.cancel()
|
||||
if self._current_worker and not getattr(self._current_worker, "done", lambda: False)():
|
||||
cancel = getattr(self._current_worker, "cancel", None)
|
||||
if cancel:
|
||||
cancel()
|
||||
|
||||
# Cancel crew orchestrator if running
|
||||
if self._current_crew:
|
||||
@@ -2193,7 +2312,8 @@ Be concise. Use the actual data from notes."""
|
||||
"""Reconnect MCP servers after cancellation to restore clean state."""
|
||||
await asyncio.sleep(0.5) # Brief delay for cancellation to propagate
|
||||
try:
|
||||
await self.mcp_manager.reconnect_all()
|
||||
if self.mcp_manager:
|
||||
await self.mcp_manager.reconnect_all()
|
||||
except Exception:
|
||||
pass # Best effort - don't crash if reconnect fails
|
||||
|
||||
@@ -2257,7 +2377,7 @@ Be concise. Use the actual data from notes."""
|
||||
|
||||
def run_tui(
|
||||
target: Optional[str] = None,
|
||||
model: str = None,
|
||||
model: Optional[str] = None,
|
||||
use_docker: bool = False,
|
||||
):
|
||||
"""Run the PentestAgent TUI"""
|
||||
|
||||
244
pentestagent/mcp/hexstrike_adapter.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""Adapter to manage a vendored HexStrike MCP server.
|
||||
|
||||
This adapter provides a simple programmatic API to start/stop the vendored
|
||||
HexStrike server (expected under ``third_party/hexstrike``) and to perform a
|
||||
health check before returning control to the caller.
|
||||
|
||||
The adapter is intentionally lightweight (no Docker) and uses an async
|
||||
subprocess so the server can run in the background while the TUI/runtime
|
||||
operates.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import signal
|
||||
import time
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
except Exception:
|
||||
aiohttp = None
|
||||
|
||||
|
||||
LOOT_DIR = Path("loot/artifacts")
|
||||
LOOT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
LOG_FILE = LOOT_DIR / "hexstrike.log"
|
||||
|
||||
|
||||
class HexstrikeAdapter:
|
||||
"""Manage a vendored HexStrike server under `third_party/hexstrike`.
|
||||
|
||||
Usage:
|
||||
adapter = HexstrikeAdapter()
|
||||
await adapter.start()
|
||||
# ... use MCPManager to connect to the server
|
||||
await adapter.stop()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = "127.0.0.1",
|
||||
port: int = 8888,
|
||||
python_cmd: str = "python3",
|
||||
server_script: Optional[Path] = None,
|
||||
cwd: Optional[Path] = None,
|
||||
env: Optional[dict] = None,
|
||||
) -> None:
|
||||
self.host = host
|
||||
self.port = int(port)
|
||||
self.python_cmd = python_cmd
|
||||
self.server_script = (
|
||||
server_script
|
||||
or Path("third_party/hexstrike/hexstrike_server.py")
|
||||
)
|
||||
self.cwd = cwd or Path.cwd()
|
||||
self.env = {**os.environ, **(env or {})}
|
||||
|
||||
self._process: Optional[asyncio.subprocess.Process] = None
|
||||
self._reader_task: Optional[asyncio.Task] = None
|
||||
|
||||
def _build_command(self):
|
||||
return [self.python_cmd, str(self.server_script), "--port", str(self.port)]
|
||||
|
||||
async def start(self, background: bool = True, timeout: int = 30) -> bool:
|
||||
"""Start the vendored HexStrike server.
|
||||
|
||||
Returns True if the server started and passed health check within
|
||||
`timeout` seconds.
|
||||
"""
|
||||
if not self.server_script.exists():
|
||||
raise FileNotFoundError(
|
||||
f"HexStrike server script not found at {self.server_script}."
|
||||
)
|
||||
|
||||
if self._process and self._process.returncode is None:
|
||||
return await self.health_check(timeout=1)
|
||||
|
||||
cmd = self._build_command()
|
||||
|
||||
# Resolve python command if possible
|
||||
resolved = shutil.which(self.python_cmd) or self.python_cmd
|
||||
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
resolved,
|
||||
*cmd[1:],
|
||||
cwd=str(self.cwd),
|
||||
env=self.env,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
start_new_session=True,
|
||||
)
|
||||
|
||||
# Log PID for debugging and management
|
||||
try:
|
||||
pid = getattr(self._process, "pid", None)
|
||||
if pid:
|
||||
with LOG_FILE.open("a") as fh:
|
||||
fh.write(f"[HexstrikeAdapter] started pid={pid}\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Start a background reader task to capture logs
|
||||
loop = asyncio.get_running_loop()
|
||||
self._reader_task = loop.create_task(self._capture_output())
|
||||
|
||||
# Wait for health check
|
||||
try:
|
||||
return await self.health_check(timeout=timeout)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def _capture_output(self) -> None:
|
||||
"""Capture stdout/stderr from the server and append to the log file."""
|
||||
if not self._process or not self._process.stdout:
|
||||
return
|
||||
|
||||
try:
|
||||
with LOG_FILE.open("ab") as fh:
|
||||
while True:
|
||||
line = await self._process.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
# Prefix timestamps for easier debugging
|
||||
fh.write(line)
|
||||
fh.flush()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def stop(self, timeout: int = 5) -> None:
|
||||
"""Stop the server process gracefully."""
|
||||
proc = self._process
|
||||
if not proc:
|
||||
return
|
||||
|
||||
try:
|
||||
proc.terminate()
|
||||
await asyncio.wait_for(proc.wait(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._process = None
|
||||
|
||||
if self._reader_task and not self._reader_task.done():
|
||||
self._reader_task.cancel()
|
||||
try:
|
||||
await self._reader_task
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def stop_sync(self, timeout: int = 5) -> None:
|
||||
"""Synchronous stop helper for use during process-exit cleanup.
|
||||
|
||||
This forcefully terminates the underlying subprocess PID if the
|
||||
async event loop is no longer available.
|
||||
"""
|
||||
proc = self._process
|
||||
if not proc:
|
||||
return
|
||||
|
||||
# Try to terminate gracefully first
|
||||
try:
|
||||
pid = getattr(proc, "pid", None)
|
||||
if pid:
|
||||
# Kill the whole process group if possible (handles children)
|
||||
try:
|
||||
pgid = os.getpgid(pid)
|
||||
os.killpg(pgid, signal.SIGTERM)
|
||||
except Exception:
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# wait briefly for process to exit
|
||||
end = time.time() + float(timeout)
|
||||
while time.time() < end:
|
||||
ret = getattr(proc, "returncode", None)
|
||||
if ret is not None:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
# If still running, force kill the process group
|
||||
try:
|
||||
pgid = os.getpgid(pid)
|
||||
os.killpg(pgid, signal.SIGKILL)
|
||||
except Exception:
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.stop_sync()
|
||||
except Exception:
|
||||
pass
|
||||
# Clear references
|
||||
try:
|
||||
self._process = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def health_check(self, timeout: int = 5) -> bool:
|
||||
"""Check the server health endpoint. Returns True if healthy."""
|
||||
url = f"http://{self.host}:{self.port}/health"
|
||||
|
||||
if aiohttp:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, timeout=timeout) as resp:
|
||||
return resp.status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# Fallback: synchronous urllib in thread
|
||||
import urllib.request
|
||||
|
||||
def _check():
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=timeout) as r:
|
||||
return r.status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
return await loop.run_in_executor(None, _check)
|
||||
|
||||
def is_running(self) -> bool:
|
||||
return self._process is not None and self._process.returncode is None
|
||||
|
||||
|
||||
__all__ = ["HexstrikeAdapter"]
|
||||
@@ -15,6 +15,8 @@ Uses standard MCP configuration format:
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import atexit
|
||||
import signal
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
@@ -33,6 +35,8 @@ class MCPServerConfig:
|
||||
env: Dict[str, str] = field(default_factory=dict)
|
||||
enabled: bool = True
|
||||
description: str = ""
|
||||
# Whether to auto-start this server when `connect_all()` is called.
|
||||
start_on_launch: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -68,7 +72,14 @@ class MCPManager:
|
||||
def __init__(self, config_path: Optional[Path] = None):
|
||||
self.config_path = config_path or self._find_config()
|
||||
self.servers: Dict[str, MCPServer] = {}
|
||||
# Track adapters we auto-started so we can stop them later
|
||||
self._started_adapters: Dict[str, object] = {}
|
||||
self._message_id = 0
|
||||
# Ensure we attempt to clean up vendored servers on process exit
|
||||
try:
|
||||
atexit.register(self._atexit_cleanup)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _find_config(self) -> Path:
|
||||
for path in self.DEFAULT_CONFIG_PATHS:
|
||||
@@ -96,8 +107,54 @@ class MCPManager:
|
||||
args=config.get("args", []),
|
||||
env=config.get("env", {}),
|
||||
enabled=config.get("enabled", True),
|
||||
start_on_launch=config.get("start_on_launch", False),
|
||||
description=config.get("description", ""),
|
||||
)
|
||||
# Allow override via environment variables for vendored MCP servers.
|
||||
# Per-adapter overrides supported:
|
||||
# - Hexstrike: LAUNCH_HEXTRIKE or LAUNCH_HEXSTRIKE
|
||||
# - Metasploit: LAUNCH_METASPLOIT_MCP
|
||||
# If set to a truthy value (1,true,y), force-enable auto-start for matching vendored server.
|
||||
# If set to a falsy value (0,false,n), force-disable auto-start for matching vendored server.
|
||||
def _apply_launch_override(env_names, match_fn):
|
||||
launch_env = None
|
||||
for e in env_names:
|
||||
launch_env = os.environ.get(e)
|
||||
if launch_env is not None:
|
||||
break
|
||||
if launch_env is None:
|
||||
return
|
||||
v = str(launch_env).strip().lower()
|
||||
enable = v in ("1", "true", "yes", "y")
|
||||
disable = v in ("0", "false", "no", "n")
|
||||
|
||||
for name, cfg in servers.items():
|
||||
try:
|
||||
if not match_fn(name, cfg):
|
||||
continue
|
||||
if enable:
|
||||
cfg.start_on_launch = True
|
||||
elif disable:
|
||||
cfg.start_on_launch = False
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Hexstrike override
|
||||
_apply_launch_override(["LAUNCH_HEXTRIKE", "LAUNCH_HEXSTRIKE"],
|
||||
lambda name, cfg: (
|
||||
(name or "").lower().find("hexstrike") != -1
|
||||
or (cfg.command and "third_party/hexstrike" in str(cfg.command))
|
||||
or any("third_party/hexstrike" in str(a) for a in (cfg.args or []))
|
||||
))
|
||||
|
||||
# Metasploit override
|
||||
_apply_launch_override(["LAUNCH_METASPLOIT_MCP"],
|
||||
lambda name, cfg: (
|
||||
(name or "").lower().find("metasploit") != -1
|
||||
or (cfg.command and "third_party/MetasploitMCP" in str(cfg.command))
|
||||
or any("third_party/MetasploitMCP" in str(a) for a in (cfg.args or []))
|
||||
))
|
||||
|
||||
return servers
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"[MCP] Error loading config: {e}")
|
||||
@@ -117,6 +174,75 @@ class MCPManager:
|
||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.config_path.write_text(json.dumps(config, indent=2), encoding="utf-8")
|
||||
|
||||
def _atexit_cleanup(self):
|
||||
"""Synchronous atexit cleanup that attempts to stop adapters and disconnect servers."""
|
||||
try:
|
||||
# Try to run async shutdown; if an event loop is already running this may fail,
|
||||
# but it's best-effort to avoid orphaned vendored servers.
|
||||
asyncio.run(self._stop_started_adapters_and_disconnect())
|
||||
except Exception:
|
||||
# Last-ditch: attempt to stop adapters synchronously.
|
||||
# If the adapter exposes a blocking `stop()` call, call it. Otherwise, try
|
||||
# to kill the underlying process by PID to avoid asyncio subprocess
|
||||
# destructors running after the loop is closed.
|
||||
for adapter in list(self._started_adapters.values()):
|
||||
try:
|
||||
# Prefer adapter-provided synchronous stop hook
|
||||
stop_sync = getattr(adapter, "stop_sync", None)
|
||||
if stop_sync:
|
||||
try:
|
||||
stop_sync()
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: try blocking stop() if present
|
||||
stop = getattr(adapter, "stop", None)
|
||||
if stop and not asyncio.iscoroutinefunction(stop):
|
||||
try:
|
||||
stop()
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Final fallback: kill underlying PID if available
|
||||
pid = None
|
||||
proc = getattr(adapter, "_process", None)
|
||||
if proc is not None:
|
||||
pid = getattr(proc, "pid", None)
|
||||
if pid:
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except Exception:
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def _stop_started_adapters_and_disconnect(self) -> None:
|
||||
# Stop any adapters we started
|
||||
for name, adapter in list(self._started_adapters.items()):
|
||||
try:
|
||||
stop = getattr(adapter, "stop", None)
|
||||
if stop:
|
||||
if asyncio.iscoroutinefunction(stop):
|
||||
await stop()
|
||||
else:
|
||||
# run blocking stop in executor
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, stop)
|
||||
except Exception:
|
||||
pass
|
||||
self._started_adapters.clear()
|
||||
|
||||
# Disconnect any active MCP server connections
|
||||
try:
|
||||
await self.disconnect_all()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def add_server(
|
||||
self,
|
||||
name: str,
|
||||
@@ -144,6 +270,18 @@ class MCPManager:
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_enabled(self, name: str, enabled: bool) -> bool:
|
||||
"""Enable or disable a configured MCP server in the config file.
|
||||
|
||||
Returns True if the server existed and was updated, False otherwise.
|
||||
"""
|
||||
servers = self._load_config()
|
||||
if name not in servers:
|
||||
return False
|
||||
servers[name].enabled = bool(enabled)
|
||||
self._save_config(servers)
|
||||
return True
|
||||
|
||||
def list_configured_servers(self) -> List[dict]:
|
||||
servers = self._load_config()
|
||||
return [
|
||||
@@ -161,10 +299,81 @@ class MCPManager:
|
||||
|
||||
async def connect_all(self) -> List[Any]:
|
||||
servers_config = self._load_config()
|
||||
# Respect explicit LAUNCH_* env overrides for vendored MCP servers.
|
||||
# If set to a falsy value (0/false/no/n) we will skip connecting to matching vendored servers.
|
||||
launch_hex_env = os.environ.get("LAUNCH_HEXTRIKE") or os.environ.get("LAUNCH_HEXSTRIKE")
|
||||
launch_hex_disabled = False
|
||||
if launch_hex_env is not None:
|
||||
v = str(launch_hex_env).strip().lower()
|
||||
if v in ("0", "false", "no", "n"):
|
||||
launch_hex_disabled = True
|
||||
|
||||
launch_msf_env = os.environ.get("LAUNCH_METASPLOIT_MCP")
|
||||
launch_msf_disabled = False
|
||||
if launch_msf_env is not None:
|
||||
v = str(launch_msf_env).strip().lower()
|
||||
if v in ("0", "false", "no", "n"):
|
||||
launch_msf_disabled = True
|
||||
|
||||
all_tools = []
|
||||
for name, config in servers_config.items():
|
||||
if not config.enabled:
|
||||
continue
|
||||
# If the user explicitly disabled launching HexStrike, skip hexstrike entries entirely
|
||||
lowered = name.lower() if name else ""
|
||||
is_hex = (
|
||||
"hexstrike" in lowered
|
||||
or (config.command and "third_party/hexstrike" in str(config.command))
|
||||
or any("third_party/hexstrike" in str(a) for a in (config.args or []))
|
||||
)
|
||||
if launch_hex_disabled and is_hex:
|
||||
print(f"[MCP] Skipping auto-connection for {name} due to LAUNCH_HEXTRIKE={launch_hex_env}")
|
||||
continue
|
||||
# Optionally auto-start vendored servers (e.g., HexStrike subtree or MetasploitMCP)
|
||||
if getattr(config, "start_on_launch", False):
|
||||
try:
|
||||
args_joined = " ".join(config.args or [])
|
||||
cmd_str = config.command or ""
|
||||
|
||||
# Hexstrike auto-start
|
||||
if "third_party/hexstrike" in args_joined or (cmd_str and "third_party/hexstrike" in cmd_str):
|
||||
if not launch_hex_disabled:
|
||||
try:
|
||||
from .hexstrike_adapter import HexstrikeAdapter
|
||||
|
||||
adapter = HexstrikeAdapter()
|
||||
started = await adapter.start()
|
||||
if started:
|
||||
try:
|
||||
self._started_adapters[name] = adapter
|
||||
except Exception:
|
||||
pass
|
||||
print(f"[MCP] Auto-started vendored server for {name}")
|
||||
except Exception as e:
|
||||
print(f"[MCP] Failed to auto-start vendored server {name}: {e}")
|
||||
else:
|
||||
print(f"[MCP] Skipping auto-start for {name} due to LAUNCH_HEXTRIKE override")
|
||||
|
||||
# Metasploit auto-start
|
||||
if "third_party/MetasploitMCP" in args_joined or (cmd_str and "third_party/MetasploitMCP" in cmd_str) or (name and "metasploit" in name.lower()):
|
||||
if not launch_msf_disabled:
|
||||
try:
|
||||
from .metasploit_adapter import MetasploitAdapter
|
||||
|
||||
adapter = MetasploitAdapter()
|
||||
started = await adapter.start()
|
||||
if started:
|
||||
try:
|
||||
self._started_adapters[name] = adapter
|
||||
except Exception:
|
||||
pass
|
||||
print(f"[MCP] Auto-started vendored server for {name}")
|
||||
except Exception as e:
|
||||
print(f"[MCP] Failed to auto-start vendored server {name}: {e}")
|
||||
else:
|
||||
print(f"[MCP] Skipping auto-start for {name} due to LAUNCH_METASPLOIT_MCP override")
|
||||
except Exception:
|
||||
pass
|
||||
server = await self._connect_server(config)
|
||||
if server:
|
||||
self.servers[name] = server
|
||||
@@ -179,6 +388,40 @@ class MCPManager:
|
||||
if name not in servers_config:
|
||||
return None
|
||||
config = servers_config[name]
|
||||
# If this appears to be a vendored Metasploit MCP entry, attempt to auto-start
|
||||
# the vendored adapter so `pentestagent mcp test metasploit-local` works
|
||||
try:
|
||||
args_joined = " ".join(config.args or [])
|
||||
cmd_str = config.command or ""
|
||||
is_msf = (
|
||||
(name and "metasploit" in name.lower())
|
||||
or ("third_party/MetasploitMCP" in cmd_str)
|
||||
or ("third_party/MetasploitMCP" in args_joined)
|
||||
)
|
||||
if is_msf:
|
||||
launch_msf_env = os.environ.get("LAUNCH_METASPLOIT_MCP")
|
||||
launch_disabled = False
|
||||
if launch_msf_env is not None:
|
||||
v = str(launch_msf_env).strip().lower()
|
||||
if v in ("0", "false", "no", "n"):
|
||||
launch_disabled = True
|
||||
if not launch_disabled:
|
||||
try:
|
||||
from .metasploit_adapter import MetasploitAdapter
|
||||
|
||||
adapter = MetasploitAdapter()
|
||||
started = await adapter.start()
|
||||
if started:
|
||||
try:
|
||||
self._started_adapters[name] = adapter
|
||||
except Exception:
|
||||
pass
|
||||
print(f"[MCP] Auto-started vendored server for {name}")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
server = await self._connect_server(config)
|
||||
if server:
|
||||
self.servers[name] = server
|
||||
@@ -188,10 +431,56 @@ class MCPManager:
|
||||
transport = None
|
||||
try:
|
||||
env = {**os.environ, **config.env}
|
||||
transport = StdioTransport(
|
||||
command=config.command, args=config.args, env=env
|
||||
)
|
||||
await transport.connect()
|
||||
|
||||
# Decide transport type:
|
||||
# - If args contain a http/sse transport or a --server http:// URL, use SSETransport
|
||||
# - Otherwise default to StdioTransport (spawn process and use stdio JSON-RPC)
|
||||
use_http = False
|
||||
http_url = None
|
||||
args_joined = " ".join(config.args or [])
|
||||
if "--transport http" in args_joined or "--transport sse" in args_joined:
|
||||
# Try to extract host/port from args
|
||||
try:
|
||||
# naive parsing: look for --host <host> and --port <port>
|
||||
host = None
|
||||
port = None
|
||||
for i, a in enumerate(config.args or []):
|
||||
if a == "--host" and i + 1 < len(config.args):
|
||||
host = config.args[i + 1]
|
||||
if a == "--port" and i + 1 < len(config.args):
|
||||
port = config.args[i + 1]
|
||||
if host and port:
|
||||
http_url = f"http://{host}:{port}/sse"
|
||||
except Exception:
|
||||
http_url = None
|
||||
use_http = True
|
||||
# If args specify a --server URL, prefer that
|
||||
if not http_url:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
for i, a in enumerate(config.args or []):
|
||||
if a == "--server" and i + 1 < len(config.args):
|
||||
candidate = config.args[i + 1]
|
||||
if isinstance(candidate, str) and candidate.startswith("http"):
|
||||
# If the provided server URL doesn't include a path, default to the MCP SSE path
|
||||
p = urlparse(candidate)
|
||||
if p.path and p.path != "/":
|
||||
http_url = candidate
|
||||
else:
|
||||
http_url = candidate.rstrip("/") + "/sse"
|
||||
use_http = True
|
||||
break
|
||||
|
||||
if use_http and http_url:
|
||||
from .transport import SSETransport
|
||||
|
||||
transport = SSETransport(url=http_url)
|
||||
await transport.connect()
|
||||
else:
|
||||
transport = StdioTransport(
|
||||
command=config.command, args=config.args, env=env
|
||||
)
|
||||
await transport.connect()
|
||||
|
||||
await transport.send(
|
||||
{
|
||||
@@ -260,6 +549,19 @@ class MCPManager:
|
||||
if server:
|
||||
await server.disconnect()
|
||||
del self.servers[name]
|
||||
# If we started an adapter for this server, stop it as well
|
||||
adapter = self._started_adapters.pop(name, None)
|
||||
if adapter:
|
||||
try:
|
||||
stop = getattr(adapter, "stop", None)
|
||||
if stop:
|
||||
if asyncio.iscoroutinefunction(stop):
|
||||
await stop()
|
||||
else:
|
||||
loop = asyncio.get_running_loop()
|
||||
await loop.run_in_executor(None, stop)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def disconnect_all(self):
|
||||
for server in list(self.servers.values()):
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
{
|
||||
"mcpServers": {}
|
||||
"mcpServers": {
|
||||
"hexstrike-local": {
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"third_party/hexstrike/hexstrike_mcp.py",
|
||||
"--server",
|
||||
"http://127.0.0.1:8888"
|
||||
],
|
||||
"description": "HexStrike AI (vendored) - local server",
|
||||
"timeout": 300,
|
||||
"enabled": true,
|
||||
"start_on_launch": false
|
||||
}
|
||||
,
|
||||
"metasploit-local": {
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"third_party/MetasploitMCP/MetasploitMCP.py",
|
||||
"--server",
|
||||
"http://127.0.0.1:7777"
|
||||
],
|
||||
"description": "Metasploit MCP (vendored) - local server",
|
||||
"timeout": 300,
|
||||
"enabled": true,
|
||||
"start_on_launch": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
341
pentestagent/mcp/metasploit_adapter.py
Normal file
@@ -0,0 +1,341 @@
|
||||
"""Adapter to manage a vendored Metasploit MCP server.
|
||||
|
||||
This follows the same lightweight pattern as the Hexstrike adapter: it
|
||||
expects the MetasploitMCP repository to be vendored under
|
||||
``third_party/MetasploitMCP`` (or a custom path provided by the caller).
|
||||
The adapter starts the server as a background subprocess and performs a
|
||||
health check on a configurable port.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import time
|
||||
import signal
|
||||
|
||||
try:
|
||||
import aiohttp
|
||||
except Exception:
|
||||
aiohttp = None
|
||||
|
||||
|
||||
LOOT_DIR = Path("loot/artifacts")
|
||||
LOOT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
LOG_FILE = LOOT_DIR / "metasploit_mcp.log"
|
||||
|
||||
|
||||
class MetasploitAdapter:
|
||||
"""Manage a vendored Metasploit MCP server under `third_party/MetasploitMCP`.
|
||||
|
||||
Usage:
|
||||
adapter = MetasploitAdapter()
|
||||
await adapter.start()
|
||||
# ... use MCPManager to connect to the server
|
||||
await adapter.stop()
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = "127.0.0.1",
|
||||
port: int = 7777,
|
||||
python_cmd: str = "python3",
|
||||
server_script: Optional[Path] = None,
|
||||
cwd: Optional[Path] = None,
|
||||
env: Optional[dict] = None,
|
||||
transport: str = "http",
|
||||
) -> None:
|
||||
self.host = host
|
||||
self.port = int(port)
|
||||
self.python_cmd = python_cmd
|
||||
# Vendored project uses 'MetasploitMCP.py' as the main entrypoint
|
||||
self.server_script = (
|
||||
server_script or Path("third_party/MetasploitMCP/MetasploitMCP.py")
|
||||
)
|
||||
self.cwd = cwd or Path.cwd()
|
||||
self.env = {**os.environ, **(env or {})}
|
||||
self.transport = transport
|
||||
|
||||
self._process: Optional[asyncio.subprocess.Process] = None
|
||||
self._reader_task: Optional[asyncio.Task] = None
|
||||
self._msfrpcd_proc: Optional[asyncio.subprocess.Process] = None
|
||||
|
||||
def _build_command(self):
|
||||
cmd = [self.python_cmd, str(self.server_script)]
|
||||
# Prefer explicit transport when starting vendored server from adapter
|
||||
if self.transport:
|
||||
cmd += ["--transport", str(self.transport)]
|
||||
# When running HTTP, ensure host/port are provided
|
||||
if str(self.transport).lower() in ("http", "sse"):
|
||||
cmd += ["--host", str(self.host), "--port", str(self.port)]
|
||||
else:
|
||||
# For other transports, allow default args
|
||||
cmd += ["--port", str(self.port)]
|
||||
return cmd
|
||||
|
||||
async def _start_msfrpcd_if_needed(self) -> None:
|
||||
"""Start `msfrpcd` if it's not already reachable at MSF_SERVER:MSF_PORT.
|
||||
|
||||
This starts `msfrpcd` as a child process (no sudo) using MSF_* env
|
||||
values if available. It's intentionally conservative: if the RPC
|
||||
endpoint is already listening we won't try to start a new daemon.
|
||||
"""
|
||||
try:
|
||||
msf_server = str(self.env.get("MSF_SERVER", "127.0.0.1"))
|
||||
msf_port = int(self.env.get("MSF_PORT", 55553))
|
||||
except Exception:
|
||||
msf_server = "127.0.0.1"
|
||||
msf_port = 55553
|
||||
|
||||
# Quick socket check to see if msfrpcd is already listening
|
||||
import socket
|
||||
|
||||
try:
|
||||
with socket.create_connection((msf_server, msf_port), timeout=1):
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# If msfrpcd not available on path, skip starting
|
||||
if not shutil.which("msfrpcd"):
|
||||
return
|
||||
|
||||
msf_user = str(self.env.get("MSF_USER", "msf"))
|
||||
msf_password = str(self.env.get("MSF_PASSWORD", ""))
|
||||
msf_ssl = str(self.env.get("MSF_SSL", "false")).lower() in ("1", "true", "yes", "y")
|
||||
|
||||
# Build args for msfrpcd (no sudo). Use -S (SSL optional) flag only if requested.
|
||||
args = ["msfrpcd", "-U", msf_user, "-P", msf_password, "-a", msf_server, "-p", str(msf_port)]
|
||||
if msf_ssl:
|
||||
args.append("-S")
|
||||
|
||||
try:
|
||||
resolved = shutil.which("msfrpcd") or "msfrpcd"
|
||||
self._msfrpcd_proc = await asyncio.create_subprocess_exec(
|
||||
resolved,
|
||||
*args[1:],
|
||||
cwd=str(self.cwd),
|
||||
env=self.env,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
start_new_session=True,
|
||||
)
|
||||
# Start reader to capture msfrpcd logs
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.create_task(self._capture_msfrpcd_output())
|
||||
|
||||
# Poll the msfrpcd TCP socket until it's accepting connections or timeout
|
||||
import socket
|
||||
deadline = asyncio.get_event_loop().time() + 10.0
|
||||
while asyncio.get_event_loop().time() < deadline:
|
||||
try:
|
||||
with socket.create_connection((msf_server, msf_port), timeout=1):
|
||||
return
|
||||
except Exception:
|
||||
await asyncio.sleep(0.5)
|
||||
# If we fallthrough, msfrpcd didn't become ready in time
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
async def _capture_msfrpcd_output(self) -> None:
|
||||
if not self._msfrpcd_proc or not self._msfrpcd_proc.stdout:
|
||||
return
|
||||
try:
|
||||
with LOG_FILE.open("ab") as fh:
|
||||
while True:
|
||||
line = await self._msfrpcd_proc.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
fh.write(b"[msfrpcd] " + line)
|
||||
fh.flush()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def start(self, background: bool = True, timeout: int = 30) -> bool:
|
||||
"""Start the vendored Metasploit MCP server.
|
||||
|
||||
Returns True if the server started and passed health check within
|
||||
`timeout` seconds.
|
||||
"""
|
||||
if not self.server_script.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Metasploit MCP server script not found at {self.server_script}."
|
||||
)
|
||||
|
||||
if self._process and self._process.returncode is None:
|
||||
return await self.health_check(timeout=1)
|
||||
|
||||
# If running in HTTP/SSE mode, ensure msfrpcd is started and reachable
|
||||
if str(self.transport).lower() in ("http", "sse"):
|
||||
try:
|
||||
await self._start_msfrpcd_if_needed()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
cmd = self._build_command()
|
||||
resolved = shutil.which(self.python_cmd) or self.python_cmd
|
||||
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
resolved,
|
||||
*cmd[1:],
|
||||
cwd=str(self.cwd),
|
||||
env=self.env,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
start_new_session=True,
|
||||
)
|
||||
|
||||
# Log PID
|
||||
try:
|
||||
pid = getattr(self._process, "pid", None)
|
||||
if pid:
|
||||
with LOG_FILE.open("a") as fh:
|
||||
fh.write(f"[MetasploitAdapter] started pid={pid}\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Start background reader
|
||||
loop = asyncio.get_running_loop()
|
||||
self._reader_task = loop.create_task(self._capture_output())
|
||||
|
||||
try:
|
||||
return await self.health_check(timeout=timeout)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def _capture_output(self) -> None:
|
||||
if not self._process or not self._process.stdout:
|
||||
return
|
||||
|
||||
try:
|
||||
with LOG_FILE.open("ab") as fh:
|
||||
while True:
|
||||
line = await self._process.stdout.readline()
|
||||
if not line:
|
||||
break
|
||||
fh.write(line)
|
||||
fh.flush()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def stop(self, timeout: int = 5) -> None:
|
||||
proc = self._process
|
||||
if not proc:
|
||||
return
|
||||
|
||||
try:
|
||||
proc.terminate()
|
||||
await asyncio.wait_for(proc.wait(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._process = None
|
||||
|
||||
if self._reader_task and not self._reader_task.done():
|
||||
self._reader_task.cancel()
|
||||
try:
|
||||
await self._reader_task
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Stop msfrpcd if we started it
|
||||
try:
|
||||
msf_proc = self._msfrpcd_proc
|
||||
if msf_proc:
|
||||
try:
|
||||
msf_proc.terminate()
|
||||
await asyncio.wait_for(msf_proc.wait(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
try:
|
||||
msf_proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self._msfrpcd_proc = None
|
||||
|
||||
def stop_sync(self, timeout: int = 5) -> None:
|
||||
proc = self._process
|
||||
if not proc:
|
||||
return
|
||||
|
||||
try:
|
||||
pid = getattr(proc, "pid", None)
|
||||
if pid:
|
||||
try:
|
||||
pgid = os.getpgid(pid)
|
||||
os.killpg(pgid, signal.SIGTERM)
|
||||
except Exception:
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
end = time.time() + float(timeout)
|
||||
while time.time() < end:
|
||||
ret = getattr(proc, "returncode", None)
|
||||
if ret is not None:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
|
||||
try:
|
||||
pgid = os.getpgid(pid)
|
||||
os.killpg(pgid, signal.SIGKILL)
|
||||
except Exception:
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.stop_sync()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self._process = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def health_check(self, timeout: int = 5) -> bool:
|
||||
url = f"http://{self.host}:{self.port}/health"
|
||||
|
||||
if aiohttp:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, timeout=timeout) as resp:
|
||||
return resp.status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
import urllib.request
|
||||
|
||||
def _check():
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=timeout) as r:
|
||||
return r.status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
return await loop.run_in_executor(None, _check)
|
||||
|
||||
def is_running(self) -> bool:
|
||||
return self._process is not None and self._process.returncode is None
|
||||
|
||||
|
||||
__all__ = ["MetasploitAdapter"]
|
||||
@@ -174,6 +174,12 @@ class SSETransport(MCPTransport):
|
||||
self.url = url
|
||||
self.session: Optional[Any] = None # aiohttp.ClientSession
|
||||
self._connected = False
|
||||
self._post_url: Optional[str] = None
|
||||
self._sse_response: Optional[Any] = None
|
||||
self._sse_task: Optional[asyncio.Task] = None
|
||||
self._pending: dict[str, asyncio.Future] = {}
|
||||
self._pending_lock = asyncio.Lock()
|
||||
self._endpoint_ready: Optional[asyncio.Event] = None
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
@@ -186,6 +192,31 @@ class SSETransport(MCPTransport):
|
||||
import aiohttp
|
||||
|
||||
self.session = aiohttp.ClientSession()
|
||||
|
||||
# Open a persistent SSE connection so we can receive async
|
||||
# responses delivered over the event stream. Keep the response
|
||||
# object alive and run a background task to parse events.
|
||||
try:
|
||||
# Do not use a short timeout; keep the connection open.
|
||||
resp = await self.session.get(self.url, timeout=None)
|
||||
# Store response and start background reader
|
||||
self._sse_response = resp
|
||||
# event used to signal when endpoint announced
|
||||
self._endpoint_ready = asyncio.Event()
|
||||
self._sse_task = asyncio.create_task(self._sse_listener(resp))
|
||||
# Wait a short time for the endpoint to be discovered to avoid races
|
||||
try:
|
||||
await asyncio.wait_for(self._endpoint_ready.wait(), timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
# If endpoint not discovered, continue; send() will try discovery
|
||||
pass
|
||||
except Exception:
|
||||
# If opening the SSE stream fails, still mark connected so
|
||||
# send() can attempt POST discovery and report meaningful errors.
|
||||
self._sse_response = None
|
||||
self._sse_task = None
|
||||
self._endpoint_ready = None
|
||||
|
||||
self._connected = True
|
||||
except ImportError as e:
|
||||
raise RuntimeError(
|
||||
@@ -205,23 +236,197 @@ class SSETransport(MCPTransport):
|
||||
if not self.session:
|
||||
raise RuntimeError("Transport not connected")
|
||||
|
||||
if not self.session:
|
||||
raise RuntimeError("Transport not connected")
|
||||
|
||||
# Ensure we have a POST endpoint. If discovery hasn't completed yet,
|
||||
# try a quick synchronous discovery attempt before posting so we don't
|
||||
# accidentally POST to the SSE listen endpoint which returns 405.
|
||||
if not self._post_url:
|
||||
try:
|
||||
await self._discover_post_url(timeout=2.0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
post_target = self._post_url or self.url
|
||||
|
||||
try:
|
||||
async with self.session.post(
|
||||
self.url, json=message, headers={"Content-Type": "application/json"}
|
||||
post_target, json=message, headers={"Content-Type": "application/json"}
|
||||
) as response:
|
||||
if response.status != 200:
|
||||
raise RuntimeError(f"HTTP error: {response.status}")
|
||||
|
||||
return await response.json()
|
||||
status = response.status
|
||||
if status == 200:
|
||||
return await response.json()
|
||||
if status == 202:
|
||||
# Asynchronous response: wait for matching SSE event with the same id
|
||||
if "id" not in message:
|
||||
return {}
|
||||
msg_id = str(message["id"])
|
||||
fut = asyncio.get_running_loop().create_future()
|
||||
async with self._pending_lock:
|
||||
self._pending[msg_id] = fut
|
||||
try:
|
||||
result = await asyncio.wait_for(fut, timeout=15.0)
|
||||
return result
|
||||
finally:
|
||||
async with self._pending_lock:
|
||||
self._pending.pop(msg_id, None)
|
||||
# Other statuses are errors
|
||||
raise RuntimeError(f"HTTP error: {status}")
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"SSE request failed: {e}") from e
|
||||
|
||||
async def _discover_post_url(self, timeout: float = 2.0) -> None:
|
||||
"""Attempt a short GET to the SSE endpoint to find the advertised POST URL.
|
||||
|
||||
This is a fallback used when the background listener hasn't yet
|
||||
extracted the `endpoint` event. It reads a few lines with a short
|
||||
timeout and sets `self._post_url` if found.
|
||||
"""
|
||||
if not self.session:
|
||||
return
|
||||
|
||||
try:
|
||||
async with self.session.get(self.url, timeout=timeout) as resp:
|
||||
if resp.status != 200:
|
||||
return
|
||||
# Read up to a few lines looking for `data:`
|
||||
for _ in range(20):
|
||||
line = await resp.content.readline()
|
||||
if not line:
|
||||
break
|
||||
try:
|
||||
text = line.decode(errors="ignore").strip()
|
||||
except Exception:
|
||||
continue
|
||||
if text.startswith("data:"):
|
||||
endpoint = text.split("data:", 1)[1].strip()
|
||||
from urllib.parse import urlparse
|
||||
|
||||
p = urlparse(self.url)
|
||||
if endpoint.startswith("http"):
|
||||
self._post_url = endpoint
|
||||
elif endpoint.startswith("/"):
|
||||
self._post_url = f"{p.scheme}://{p.netloc}{endpoint}"
|
||||
else:
|
||||
self._post_url = f"{p.scheme}://{p.netloc}/{endpoint.lstrip('/')}"
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
async def disconnect(self):
|
||||
"""Close the HTTP session."""
|
||||
# Cancel listener and close SSE response
|
||||
try:
|
||||
if self._sse_task:
|
||||
self._sse_task.cancel()
|
||||
try:
|
||||
await self._sse_task
|
||||
except Exception:
|
||||
pass
|
||||
self._sse_task = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if self._sse_response:
|
||||
try:
|
||||
await self._sse_response.release()
|
||||
except Exception:
|
||||
pass
|
||||
self._sse_response = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fail any pending requests
|
||||
async with self._pending_lock:
|
||||
for fut in list(self._pending.values()):
|
||||
if not fut.done():
|
||||
fut.set_exception(RuntimeError("Transport disconnected"))
|
||||
self._pending.clear()
|
||||
|
||||
if self.session:
|
||||
await self.session.close()
|
||||
self.session = None
|
||||
self._connected = False
|
||||
|
||||
async def _sse_listener(self, resp: Any):
|
||||
"""Background task that reads SSE events and resolves pending futures.
|
||||
|
||||
The listener expects SSE-formatted events where `data:` lines may
|
||||
contain JSON payloads. If a JSON object contains an `id` field that
|
||||
matches a pending request, the corresponding future is completed with
|
||||
that JSON value.
|
||||
"""
|
||||
try:
|
||||
# Read the stream line-by-line, accumulating event blocks
|
||||
event_lines: list[str] = []
|
||||
async for raw in resp.content:
|
||||
try:
|
||||
line = raw.decode(errors="ignore").rstrip("\r\n")
|
||||
except Exception:
|
||||
continue
|
||||
if line == "":
|
||||
# End of event; process accumulated lines
|
||||
event_name = None
|
||||
data_lines: list[str] = []
|
||||
for l in event_lines:
|
||||
if l.startswith("event:"):
|
||||
event_name = l.split(":", 1)[1].strip()
|
||||
elif l.startswith("data:"):
|
||||
data_lines.append(l.split(":", 1)[1].lstrip())
|
||||
|
||||
if data_lines:
|
||||
data_text = "\n".join(data_lines)
|
||||
# If this is an endpoint announcement, record POST URL
|
||||
if event_name == "endpoint":
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
p = urlparse(self.url)
|
||||
endpoint = data_text.strip()
|
||||
if endpoint.startswith("http"):
|
||||
self._post_url = endpoint
|
||||
elif endpoint.startswith("/"):
|
||||
self._post_url = f"{p.scheme}://{p.netloc}{endpoint}"
|
||||
else:
|
||||
self._post_url = f"{p.scheme}://{p.netloc}/{endpoint.lstrip('/')}"
|
||||
except Exception:
|
||||
pass
|
||||
# Notify connect() that endpoint is ready
|
||||
try:
|
||||
if self._endpoint_ready and not self._endpoint_ready.is_set():
|
||||
self._endpoint_ready.set()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
# Try to parse as JSON and resolve pending futures
|
||||
try:
|
||||
obj = json.loads(data_text)
|
||||
if isinstance(obj, dict) and "id" in obj:
|
||||
msg_id = str(obj.get("id"))
|
||||
async with self._pending_lock:
|
||||
fut = self._pending.get(msg_id)
|
||||
if fut and not fut.done():
|
||||
fut.set_result(obj)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
event_lines = []
|
||||
else:
|
||||
event_lines.append(line)
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
except Exception:
|
||||
# On error, fail pending futures
|
||||
async with self._pending_lock:
|
||||
for fut in list(self._pending.values()):
|
||||
if not fut.done():
|
||||
fut.set_exception(RuntimeError("SSE listener error"))
|
||||
self._pending.clear()
|
||||
finally:
|
||||
# Ensure we mark disconnected state
|
||||
self._connected = False
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,20 @@ rag = [
|
||||
all = [
|
||||
"pentestagent[dev,rag]",
|
||||
]
|
||||
hexstrike = [
|
||||
"flask>=2.3.0,<4.0.0",
|
||||
"requests>=2.31.0,<3.0.0",
|
||||
"psutil>=5.9.0,<6.0.0",
|
||||
"fastmcp>=0.2.0,<1.0.0",
|
||||
"beautifulsoup4>=4.12.0,<5.0.0",
|
||||
"selenium>=4.15.0,<5.0.0",
|
||||
"webdriver-manager>=4.0.0,<5.0.0",
|
||||
"aiohttp>=3.8.0,<4.0.0",
|
||||
"mitmproxy>=9.0.0,<11.0.0",
|
||||
"pwntools>=4.10.0,<5.0.0",
|
||||
"angr>=9.2.0,<10.0.0",
|
||||
"bcrypt==4.0.1",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/GH05TCREW/pentestagent"
|
||||
|
||||
4
pyrightconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"typeCheckingMode": "basic",
|
||||
"reportMissingImports": true
|
||||
}
|
||||
3
requirements-hexstrike.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# Wrapper requirements file for vendored HexStrike dependencies
|
||||
# This delegates to the vendored requirements in third_party/hexstrike.
|
||||
-r third_party/hexstrike/requirements.txt
|
||||
32
scripts/add_hexstrike_subtree.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Helper script to vendor HexStrike into this repo using git subtree.
|
||||
# Run from repository root.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="https://github.com/0x4m4/hexstrike-ai.git"
|
||||
PREFIX="third_party/hexstrike"
|
||||
BRANCH="main"
|
||||
|
||||
echo "This will add HexStrike as a git subtree under ${PREFIX}."
|
||||
echo "If the subtree already exists, the script will pull and rebase the subtree instead.\n"
|
||||
|
||||
if [ -d "${PREFIX}" ]; then
|
||||
echo "Detected existing subtree at ${PREFIX}."
|
||||
if [ "${FORCE_SUBTREE_PULL:-false}" = "true" ]; then
|
||||
echo "FORCE_SUBTREE_PULL=true: pulling latest changes into existing subtree..."
|
||||
git subtree pull --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash || {
|
||||
echo "git subtree pull failed; attempting without --squash..."
|
||||
git subtree pull --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" || exit 1
|
||||
}
|
||||
echo "Subtree at ${PREFIX} updated."
|
||||
else
|
||||
echo "To update the existing subtree run:"
|
||||
echo " FORCE_SUBTREE_PULL=true bash scripts/add_hexstrike_subtree.sh"
|
||||
echo "Or run manually: git subtree pull --prefix=\"${PREFIX}\" ${REPO_URL} ${BRANCH} --squash"
|
||||
fi
|
||||
else
|
||||
echo "Adding subtree for the first time..."
|
||||
git subtree add --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash
|
||||
echo "HexStrike subtree added under ${PREFIX}."
|
||||
fi
|
||||
84
scripts/add_metasploit_subtree.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
# Helper script to vendor MetasploitMCP into this repo using git subtree.
|
||||
# Run from repository root.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="${METASPLOIT_SUBTREE_REPO:-https://github.com/GH05TCREW/MetasploitMCP.git}"
|
||||
PREFIX="third_party/MetasploitMCP"
|
||||
BRANCH="main"
|
||||
|
||||
echo "This will add MetasploitMCP as a git subtree under ${PREFIX}."
|
||||
echo "You can override the upstream repo with: METASPLOIT_SUBTREE_REPO=...\n"
|
||||
echo "If the subtree already exists, the script will pull and rebase the subtree instead.\n"
|
||||
|
||||
if [ -d "${PREFIX}" ]; then
|
||||
# If directory exists but is empty (left by manual mkdir or previous failed import),
|
||||
# treat it as if the subtree is not yet added so we can perform the add operation.
|
||||
if [ -z "$(ls -A "${PREFIX}" 2>/dev/null)" ]; then
|
||||
echo "Detected empty directory at ${PREFIX}; adding subtree into it..."
|
||||
mkdir -p "$(dirname "${PREFIX}")"
|
||||
if git subtree add --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash; then
|
||||
echo "MetasploitMCP subtree added under ${PREFIX}."
|
||||
else
|
||||
echo "Failed to add subtree from ${REPO_URL}." >&2
|
||||
echo "Check that the URL is correct or override with METASPLOIT_SUBTREE_REPO." >&2
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
# Directory exists; check whether the path is tracked in git.
|
||||
if git ls-files --error-unmatch "${PREFIX}" >/dev/null 2>&1; then
|
||||
echo "Detected existing subtree at ${PREFIX}."
|
||||
if [ "${FORCE_SUBTREE_PULL:-false}" = "true" ]; then
|
||||
echo "FORCE_SUBTREE_PULL=true: pulling latest changes into existing subtree..."
|
||||
git subtree pull --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash || {
|
||||
echo "git subtree pull failed; attempting without --squash..."
|
||||
git subtree pull --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" || exit 1
|
||||
}
|
||||
echo "Subtree at ${PREFIX} updated."
|
||||
else
|
||||
echo "To update the existing subtree run:"
|
||||
echo " FORCE_SUBTREE_PULL=true bash scripts/add_metasploit_subtree.sh"
|
||||
echo "Or run manually: git subtree pull --prefix=\"${PREFIX}\" ${REPO_URL} ${BRANCH} --squash"
|
||||
fi
|
||||
else
|
||||
# Directory exists but not tracked by git.
|
||||
echo "Directory ${PREFIX} exists but is not tracked in git."
|
||||
if [ "${FORCE_SUBTREE_PULL:-false}" = "true" ]; then
|
||||
echo "FORCE_SUBTREE_PULL=true: backing up existing directory and attempting to add subtree..."
|
||||
BACKUP="${PREFIX}.backup.$(date +%s)"
|
||||
mv "${PREFIX}" "${BACKUP}" || { echo "Failed to move ${PREFIX} to ${BACKUP}" >&2; exit 1; }
|
||||
# Ensure parent exists after move
|
||||
mkdir -p "$(dirname "${PREFIX}")"
|
||||
if git subtree add --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash; then
|
||||
echo "MetasploitMCP subtree added under ${PREFIX}."
|
||||
echo "Removing backup ${BACKUP}."
|
||||
rm -rf "${BACKUP}"
|
||||
else
|
||||
echo "Failed to add subtree from ${REPO_URL}. Restoring backup." >&2
|
||||
rm -rf "${PREFIX}" || true
|
||||
mv "${BACKUP}" "${PREFIX}" || { echo "Failed to restore ${BACKUP} to ${PREFIX}" >&2; exit 1; }
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "To add the subtree into the existing directory, either remove/rename ${PREFIX} and retry,"
|
||||
echo "or run with FORCE_SUBTREE_PULL=true to back up and add:"
|
||||
echo " FORCE_SUBTREE_PULL=true bash scripts/add_metasploit_subtree.sh"
|
||||
echo "Or override the repo with METASPLOIT_SUBTREE_REPO to use a different source."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Adding subtree for the first time..."
|
||||
# Ensure parent dir exists for clearer errors
|
||||
mkdir -p "$(dirname "${PREFIX}")"
|
||||
|
||||
if git subtree add --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash; then
|
||||
echo "MetasploitMCP subtree added under ${PREFIX}."
|
||||
else
|
||||
echo "Failed to add subtree from ${REPO_URL}." >&2
|
||||
echo "Check that the URL is correct or override with METASPLOIT_SUBTREE_REPO." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
45
scripts/install_hexstrike_deps.ps1
Normal file
@@ -0,0 +1,45 @@
|
||||
<#
|
||||
Install vendored HexStrike Python dependencies (Windows/PowerShell).
|
||||
|
||||
This mirrors `scripts/install_hexstrike_deps.sh` for Windows users.
|
||||
#>
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
Write-Host "Installing vendored HexStrike dependencies (Windows)..."
|
||||
|
||||
# Load .env if present (simple parser: ignore comments/blank lines)
|
||||
if (Test-Path -Path ".env") {
|
||||
Write-Host "Sourcing .env"
|
||||
Get-Content .env | ForEach-Object {
|
||||
$line = $_.Trim()
|
||||
if ($line -and -not $line.StartsWith("#") -and $line.Contains("=")) {
|
||||
$parts = $line -split "=", 2
|
||||
$name = $parts[0].Trim()
|
||||
$value = $parts[1].Trim()
|
||||
# Only set if not empty
|
||||
if ($name) { $env:$name = $value }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$req = Join-Path -Path (Get-Location) -ChildPath "third_party/hexstrike/requirements.txt"
|
||||
|
||||
if (-not (Test-Path -Path $req)) {
|
||||
Write-Host "Cannot find $req. Is the HexStrike subtree present?" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Prefer venv python if present
|
||||
$python = "python"
|
||||
if (Test-Path -Path ".\venv\Scripts\python.exe") {
|
||||
$python = Join-Path -Path (Get-Location) -ChildPath ".\venv\Scripts\python.exe"
|
||||
}
|
||||
|
||||
Write-Host "Using Python: $python"
|
||||
|
||||
& $python -m pip install --upgrade pip
|
||||
& $python -m pip install -r $req
|
||||
|
||||
Write-Host "HexStrike dependencies installed. Note: many external tools are not included and must be installed separately as described in third_party/hexstrike/requirements.txt." -ForegroundColor Green
|
||||
|
||||
exit 0
|
||||
42
scripts/install_hexstrike_deps.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Install vendored HexStrike Python dependencies.
|
||||
# This script will source a local .env if present so any environment
|
||||
# variables (proxies/indices/LLM keys) are respected during installation.
|
||||
|
||||
HERE=$(dirname "${BASH_SOURCE[0]}")
|
||||
ROOT=$(cd "$HERE/.." && pwd)
|
||||
|
||||
cd "$ROOT"
|
||||
|
||||
if [ -f ".env" ]; then
|
||||
echo "Sourcing .env"
|
||||
# export all vars from .env (ignore comments and blank lines)
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
set +a
|
||||
fi
|
||||
|
||||
REQ=third_party/hexstrike/requirements.txt
|
||||
|
||||
if [ ! -f "$REQ" ]; then
|
||||
echo "Cannot find $REQ. Is the HexStrike subtree present?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Installing HexStrike requirements from $REQ"
|
||||
|
||||
# Prefer using the active venv python if present
|
||||
PY=$(which python || true)
|
||||
if [ -n "${VIRTUAL_ENV:-}" ]; then
|
||||
PY="$VIRTUAL_ENV/bin/python"
|
||||
fi
|
||||
|
||||
"$PY" -m pip install --upgrade pip
|
||||
"$PY" -m pip install -r "$REQ"
|
||||
|
||||
echo "HexStrike dependencies installed. Note: many external tools are not included and must be installed separately as described in third_party/hexstrike/requirements.txt."
|
||||
|
||||
exit 0
|
||||
40
scripts/install_metasploit_deps.sh
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Install vendored MetasploitMCP Python dependencies.
|
||||
# This script will source a local .env if present so any environment
|
||||
# variables (proxies/indices/LLM keys) are respected during installation.
|
||||
|
||||
HERE=$(dirname "${BASH_SOURCE[0]}")
|
||||
ROOT=$(cd "$HERE/.." && pwd)
|
||||
|
||||
cd "$ROOT"
|
||||
|
||||
if [ -f ".env" ]; then
|
||||
echo "Sourcing .env"
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
set +a
|
||||
fi
|
||||
|
||||
REQ=third_party/MetasploitMCP/requirements.txt
|
||||
|
||||
if [ ! -f "$REQ" ]; then
|
||||
echo "Cannot find $REQ. Is the MetasploitMCP subtree present?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Installing MetasploitMCP requirements from $REQ"
|
||||
|
||||
PY=$(which python || true)
|
||||
if [ -n "${VIRTUAL_ENV:-}" ]; then
|
||||
PY="$VIRTUAL_ENV/bin/python"
|
||||
fi
|
||||
|
||||
"$PY" -m pip install --upgrade pip
|
||||
"$PY" -m pip install -r "$REQ"
|
||||
|
||||
echo "MetasploitMCP dependencies installed. Note: external components may still be required."
|
||||
|
||||
exit 0
|
||||
@@ -81,6 +81,28 @@ PENTESTAGENT_MODEL=gpt-5
|
||||
# Settings
|
||||
PENTESTAGENT_DEBUG=false
|
||||
|
||||
# Auto-launch vendored HexStrike on connect (true/false)
|
||||
# If true, the MCP manager will attempt to start vendored HexStrike servers
|
||||
# that are configured or detected under `third_party/hexstrike`.
|
||||
LAUNCH_HEXTRIKE=false
|
||||
# Auto-launch vendored Metasploit MCP on connect (true/false)
|
||||
# If true, the MCP manager will attempt to start vendored MetasploitMCP
|
||||
# servers that are configured or detected under `third_party/MetasploitMCP`.
|
||||
LAUNCH_METASPLOIT_MCP=false
|
||||
|
||||
# Metasploit RPC (msfrpcd) settings — used when LAUNCH_METASPLOIT_MCP=true
|
||||
# Set MSF_PASSWORD to enable automatic msfrpcd startup. Example:
|
||||
# MSF_USER=msf
|
||||
# MSF_PASSWORD=change_me
|
||||
# MSF_SERVER=127.0.0.1
|
||||
# MSF_PORT=55553
|
||||
# MSF_SSL=false
|
||||
MSF_USER=msf
|
||||
MSF_PASSWORD=
|
||||
MSF_SERVER=127.0.0.1
|
||||
MSF_PORT=55553
|
||||
MSF_SSL=false
|
||||
|
||||
# Agent max iterations (regular agent + crew workers, default: 30)
|
||||
# PENTESTAGENT_AGENT_MAX_ITERATIONS=30
|
||||
|
||||
@@ -91,10 +113,88 @@ PENTESTAGENT_DEBUG=false
|
||||
Write-Host "[!] Please edit .env and add your API keys"
|
||||
}
|
||||
|
||||
# Load .env into process environment variables (so the script can use them)
|
||||
if (Test-Path -Path ".env") {
|
||||
Get-Content .env | ForEach-Object {
|
||||
if ($_ -match '^(?:\s*#)|(?:\s*$)') { return }
|
||||
if ($_ -match '^(\s*([^=]+)?)=(.*)$') {
|
||||
$name = $Matches[2].Trim()
|
||||
$value = $Matches[3].Trim()
|
||||
if ($name) { [Environment]::SetEnvironmentVariable($name, $value, 'Process') }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create loot directory for reports
|
||||
New-Item -ItemType Directory -Force -Path "loot" | Out-Null
|
||||
Write-Host "[OK] Loot directory created"
|
||||
|
||||
# Install vendored HexStrike dependencies automatically if present
|
||||
$hexReq = Join-Path -Path (Get-Location) -ChildPath "third_party/hexstrike/requirements.txt"
|
||||
if (Test-Path -Path $hexReq) {
|
||||
Write-Host "Installing vendored HexStrike dependencies..."
|
||||
try {
|
||||
& .\scripts\install_hexstrike_deps.ps1
|
||||
} catch {
|
||||
Write-Host "Warning: Failed to install HexStrike deps: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Attempt to vendor MetasploitMCP via bundled script if not already present
|
||||
$msDir = Join-Path -Path (Get-Location) -ChildPath "third_party/MetasploitMCP"
|
||||
$addScript = Join-Path -Path (Get-Location) -ChildPath "scripts/add_metasploit_subtree.sh"
|
||||
if (-not (Test-Path -Path $msDir) -and (Test-Path -Path $addScript)) {
|
||||
Write-Host "Vendoring MetasploitMCP into third_party (requires bash)..."
|
||||
if (Get-Command bash -ErrorAction SilentlyContinue) {
|
||||
try {
|
||||
& bash -c "scripts/add_metasploit_subtree.sh"
|
||||
} catch {
|
||||
Write-Host "Warning: Failed to vendor MetasploitMCP via bash: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "Warning: 'bash' not available; please run scripts/add_metasploit_subtree.sh manually." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Install vendored MetasploitMCP dependencies automatically if present
|
||||
$msReq = Join-Path -Path (Get-Location) -ChildPath "third_party/MetasploitMCP/requirements.txt"
|
||||
$installMsScript = Join-Path -Path (Get-Location) -ChildPath "scripts/install_metasploit_deps.sh"
|
||||
if (Test-Path -Path $msReq) {
|
||||
Write-Host "Installing vendored MetasploitMCP dependencies..."
|
||||
if (Test-Path -Path $installMsScript -and (Get-Command bash -ErrorAction SilentlyContinue)) {
|
||||
try {
|
||||
& bash -c "scripts/install_metasploit_deps.sh"
|
||||
} catch {
|
||||
Write-Host "Warning: Failed to install MetasploitMCP deps via bash: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "Warning: Could not run install script automatically; run scripts/install_metasploit_deps.sh manually." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Optionally auto-start msfrpcd if configured in .env
|
||||
if (($env:LAUNCH_METASPLOIT_MCP -eq 'true') -and ($env:MSF_PASSWORD)) {
|
||||
$msfUser = if ($env:MSF_USER) { $env:MSF_USER } else { 'msf' }
|
||||
$msfServer = if ($env:MSF_SERVER) { $env:MSF_SERVER } else { '127.0.0.1' }
|
||||
$msfPort = if ($env:MSF_PORT) { $env:MSF_PORT } else { '55553' }
|
||||
Write-Host "Starting msfrpcd (user=$msfUser, host=$msfServer, port=$msfPort) without sudo (background)..."
|
||||
# Start msfrpcd without sudo; if it's already running the cmd will fail harmlessly.
|
||||
if (Get-Command msfrpcd -ErrorAction SilentlyContinue) {
|
||||
try {
|
||||
if ($env:MSF_SSL -eq 'true' -or $env:MSF_SSL -eq '1') {
|
||||
Start-Process -FilePath msfrpcd -ArgumentList "-U", $msfUser, "-P", $env:MSF_PASSWORD, "-a", $msfServer, "-p", $msfPort, "-S" -NoNewWindow -WindowStyle Hidden
|
||||
} else {
|
||||
Start-Process -FilePath msfrpcd -ArgumentList "-U", $msfUser, "-P", $env:MSF_PASSWORD, "-a", $msfServer, "-p", $msfPort -NoNewWindow -WindowStyle Hidden
|
||||
}
|
||||
Write-Host "msfrpcd start requested; check with: netstat -an | Select-String $msfPort"
|
||||
} catch {
|
||||
Write-Host "Warning: Failed to start msfrpcd: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "msfrpcd not found; please install Metasploit Framework to enable Metasploit RPC." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Setup complete!"
|
||||
Write-Host ""
|
||||
|
||||
@@ -75,6 +75,28 @@ PENTESTAGENT_MODEL=gpt-5
|
||||
# Settings
|
||||
PENTESTAGENT_DEBUG=false
|
||||
|
||||
# Auto-launch vendored HexStrike on connect (true/false)
|
||||
# If true, the MCP manager will attempt to start vendored HexStrike servers
|
||||
# that are configured or detected under `third_party/hexstrike`.
|
||||
LAUNCH_HEXTRIKE=false
|
||||
# Auto-launch vendored Metasploit MCP on connect (true/false)
|
||||
# If true, the MCP manager will attempt to start vendored MetasploitMCP
|
||||
# servers that are configured or detected under `third_party/MetasploitMCP`.
|
||||
LAUNCH_METASPLOIT_MCP=false
|
||||
|
||||
# Metasploit RPC (msfrpcd) settings — used when LAUNCH_METASPLOIT_MCP=true
|
||||
# Set MSF_PASSWORD to enable automatic msfrpcd startup. Example:
|
||||
# MSF_USER=msf
|
||||
# MSF_PASSWORD=change_me
|
||||
# MSF_SERVER=127.0.0.1
|
||||
# MSF_PORT=55553
|
||||
# MSF_SSL=false
|
||||
MSF_USER=msf
|
||||
MSF_PASSWORD=
|
||||
MSF_SERVER=127.0.0.1
|
||||
MSF_PORT=55553
|
||||
MSF_SSL=false
|
||||
|
||||
# Agent max iterations (regular agent + crew workers, default: 30)
|
||||
# PENTESTAGENT_AGENT_MAX_ITERATIONS=30
|
||||
|
||||
@@ -85,10 +107,67 @@ EOF
|
||||
echo "[!] Please edit .env and add your API keys"
|
||||
fi
|
||||
|
||||
# Load .env into environment if present
|
||||
if [ -f ".env" ]; then
|
||||
# Export variables defined in .env for the duration of this script
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
. .env
|
||||
set +a
|
||||
fi
|
||||
|
||||
# Create loot directory for reports
|
||||
mkdir -p loot
|
||||
echo "[OK] Loot directory created"
|
||||
|
||||
# Install vendored HexStrike dependencies automatically if present
|
||||
if [ -f "third_party/hexstrike/requirements.txt" ]; then
|
||||
echo "Installing vendored HexStrike dependencies..."
|
||||
bash scripts/install_hexstrike_deps.sh
|
||||
fi
|
||||
|
||||
# Vendor MetasploitMCP via git-subtree if not already vendored
|
||||
if [ ! -d "third_party/MetasploitMCP" ] && [ -f "scripts/add_metasploit_subtree.sh" ]; then
|
||||
echo "Vendoring MetasploitMCP into third_party..."
|
||||
bash scripts/add_metasploit_subtree.sh || echo "Warning: failed to vendor MetasploitMCP; you can run scripts/add_metasploit_subtree.sh manually."
|
||||
fi
|
||||
|
||||
# Install vendored MetasploitMCP dependencies automatically if present
|
||||
if [ -f "third_party/MetasploitMCP/requirements.txt" ]; then
|
||||
echo "Installing vendored MetasploitMCP dependencies..."
|
||||
bash scripts/install_metasploit_deps.sh || echo "Warning: failed to install MetasploitMCP dependencies."
|
||||
fi
|
||||
|
||||
# Optionally auto-start Metasploit RPC daemon if configured
|
||||
# Start `msfrpcd` without sudo if LAUNCH_METASPLOIT_MCP=true and MSF_PASSWORD is set.
|
||||
if [ "${LAUNCH_METASPLOIT_MCP,,}" = "true" ] && [ -n "${MSF_PASSWORD:-}" ]; then
|
||||
if command -v msfrpcd >/dev/null 2>&1; then
|
||||
MSF_USER="${MSF_USER:-msf}"
|
||||
MSF_SERVER="${MSF_SERVER:-127.0.0.1}"
|
||||
MSF_PORT="${MSF_PORT:-55553}"
|
||||
MSF_SSL="${MSF_SSL:-false}"
|
||||
echo "Starting msfrpcd (user=${MSF_USER}, host=${MSF_SERVER}, port=${MSF_PORT})..."
|
||||
# Start msfrpcd as a background process without sudo. The daemon will bind to the loopback
|
||||
# interface and does not require root privileges on modern systems for ephemeral ports.
|
||||
msfrpcd_cmd=$(command -v msfrpcd || true)
|
||||
if [ -n "$msfrpcd_cmd" ]; then
|
||||
LOG_DIR="loot/artifacts"
|
||||
mkdir -p "$LOG_DIR"
|
||||
MSF_LOG="$LOG_DIR/metasploit_msfrpcd.log"
|
||||
if [ "${MSF_SSL,,}" = "true" ] || [ "${MSF_SSL}" = "1" ]; then
|
||||
"$msfrpcd_cmd" -U "$MSF_USER" -P "$MSF_PASSWORD" -a "$MSF_SERVER" -p "$MSF_PORT" -S >"$MSF_LOG" 2>&1 &
|
||||
else
|
||||
"$msfrpcd_cmd" -U "$MSF_USER" -P "$MSF_PASSWORD" -a "$MSF_SERVER" -p "$MSF_PORT" >"$MSF_LOG" 2>&1 &
|
||||
fi
|
||||
echo "msfrpcd started (logs: $MSF_LOG)"
|
||||
else
|
||||
echo "msfrpcd not found; please install Metasploit Framework to enable Metasploit RPC."
|
||||
fi
|
||||
else
|
||||
echo "msfrpcd not found; please install Metasploit Framework to enable Metasploit RPC."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================================================="
|
||||
echo "Setup complete!"
|
||||
|
||||
757
third_party/hexstrike/README.md
vendored
Normal file
@@ -0,0 +1,757 @@
|
||||
<div align="center">
|
||||
|
||||
<img src="assets/hexstrike-logo.png" alt="HexStrike AI Logo" width="220" style="margin-bottom: 20px;"/>
|
||||
|
||||
# HexStrike AI MCP Agents v6.0
|
||||
### AI-Powered MCP Cybersecurity Automation Platform
|
||||
|
||||
[](https://www.python.org/)
|
||||
[](LICENSE)
|
||||
[](https://github.com/0x4m4/hexstrike-ai)
|
||||
[](https://github.com/0x4m4/hexstrike-ai)
|
||||
[](https://github.com/0x4m4/hexstrike-ai/releases)
|
||||
[](https://github.com/0x4m4/hexstrike-ai)
|
||||
[](https://github.com/0x4m4/hexstrike-ai)
|
||||
[](https://github.com/0x4m4/hexstrike-ai)
|
||||
|
||||
**Advanced AI-powered penetration testing MCP framework with 150+ security tools and 12+ autonomous AI agents**
|
||||
|
||||
[📋 What's New](#whats-new-in-v60) • [🏗️ Architecture](#architecture-overview) • [🚀 Installation](#installation) • [🛠️ Features](#features) • [🤖 AI Agents](#ai-agents) • [📡 API Reference](#api-reference)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## Follow Our Social Accounts
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/BWnmrrSHbA">
|
||||
<img src="https://img.shields.io/badge/Discord-Join-7289DA?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord" />
|
||||
</a>
|
||||
|
||||
<a href="https://www.linkedin.com/company/hexstrike-ai">
|
||||
<img src="https://img.shields.io/badge/LinkedIn-Follow%20us-0A66C2?logo=linkedin&logoColor=white&style=for-the-badge" alt="Follow us on LinkedIn" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
HexStrike AI MCP v6.0 features a multi-agent architecture with autonomous AI agents, intelligent decision-making, and vulnerability intelligence.
|
||||
|
||||
```mermaid
|
||||
%%{init: {"themeVariables": {
|
||||
"primaryColor": "#b71c1c",
|
||||
"secondaryColor": "#ff5252",
|
||||
"tertiaryColor": "#ff8a80",
|
||||
"background": "#2d0000",
|
||||
"edgeLabelBackground":"#b71c1c",
|
||||
"fontFamily": "monospace",
|
||||
"fontSize": "16px",
|
||||
"fontColor": "#fffde7",
|
||||
"nodeTextColor": "#fffde7"
|
||||
}}}%%
|
||||
graph TD
|
||||
A[AI Agent - Claude/GPT/Copilot] -->|MCP Protocol| B[HexStrike MCP Server v6.0]
|
||||
|
||||
B --> C[Intelligent Decision Engine]
|
||||
B --> D[12+ Autonomous AI Agents]
|
||||
B --> E[Modern Visual Engine]
|
||||
|
||||
C --> F[Tool Selection AI]
|
||||
C --> G[Parameter Optimization]
|
||||
C --> H[Attack Chain Discovery]
|
||||
|
||||
D --> I[BugBounty Agent]
|
||||
D --> J[CTF Solver Agent]
|
||||
D --> K[CVE Intelligence Agent]
|
||||
D --> L[Exploit Generator Agent]
|
||||
|
||||
E --> M[Real-time Dashboards]
|
||||
E --> N[Progress Visualization]
|
||||
E --> O[Vulnerability Cards]
|
||||
|
||||
B --> P[150+ Security Tools]
|
||||
P --> Q[Network Tools - 25+]
|
||||
P --> R[Web App Tools - 40+]
|
||||
P --> S[Cloud Tools - 20+]
|
||||
P --> T[Binary Tools - 25+]
|
||||
P --> U[CTF Tools - 20+]
|
||||
P --> V[OSINT Tools - 20+]
|
||||
|
||||
B --> W[Advanced Process Management]
|
||||
W --> X[Smart Caching]
|
||||
W --> Y[Resource Optimization]
|
||||
W --> Z[Error Recovery]
|
||||
|
||||
style A fill:#b71c1c,stroke:#ff5252,stroke-width:3px,color:#fffde7
|
||||
style B fill:#ff5252,stroke:#b71c1c,stroke-width:4px,color:#fffde7
|
||||
style C fill:#ff8a80,stroke:#b71c1c,stroke-width:2px,color:#fffde7
|
||||
style D fill:#ff8a80,stroke:#b71c1c,stroke-width:2px,color:#fffde7
|
||||
style E fill:#ff8a80,stroke:#b71c1c,stroke-width:2px,color:#fffde7
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **AI Agent Connection** - Claude, GPT, or other MCP-compatible agents connect via FastMCP protocol
|
||||
2. **Intelligent Analysis** - Decision engine analyzes targets and selects optimal testing strategies
|
||||
3. **Autonomous Execution** - AI agents execute comprehensive security assessments
|
||||
4. **Real-time Adaptation** - System adapts based on results and discovered vulnerabilities
|
||||
5. **Advanced Reporting** - Visual output with vulnerability cards and risk analysis
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
### Quick Setup to Run the hexstrike MCPs Server
|
||||
|
||||
```bash
|
||||
# 1. Clone the repository
|
||||
git clone https://github.com/0x4m4/hexstrike-ai.git
|
||||
cd hexstrike-ai
|
||||
|
||||
# 2. Create virtual environment
|
||||
python3 -m venv hexstrike-env
|
||||
source hexstrike-env/bin/activate # Linux/Mac
|
||||
# hexstrike-env\Scripts\activate # Windows
|
||||
|
||||
# 3. Install Python dependencies
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
```
|
||||
|
||||
### Installation and Setting Up Guide for various AI Clients:
|
||||
|
||||
#### Installation & Demo Video
|
||||
|
||||
Watch the full installation and setup walkthrough here: [YouTube - HexStrike AI Installation & Demo](https://www.youtube.com/watch?v=pSoftCagCm8)
|
||||
|
||||
#### Supported AI Clients for Running & Integration
|
||||
|
||||
You can install and run HexStrike AI MCPs with various AI clients, including:
|
||||
|
||||
- **5ire (Latest version v0.14.0 not supported for now)**
|
||||
- **VS Code Copilot**
|
||||
- **Roo Code**
|
||||
- **Cursor**
|
||||
- **Claude Desktop**
|
||||
- **Any MCP-compatible agent**
|
||||
|
||||
Refer to the video above for step-by-step instructions and integration examples for these platforms.
|
||||
|
||||
|
||||
|
||||
### Install Security Tools
|
||||
|
||||
**Core Tools (Essential):**
|
||||
```bash
|
||||
# Network & Reconnaissance
|
||||
nmap masscan rustscan amass subfinder nuclei fierce dnsenum
|
||||
autorecon theharvester responder netexec enum4linux-ng
|
||||
|
||||
# Web Application Security
|
||||
gobuster feroxbuster dirsearch ffuf dirb httpx katana
|
||||
nikto sqlmap wpscan arjun paramspider dalfox wafw00f
|
||||
|
||||
# Password & Authentication
|
||||
hydra john hashcat medusa patator crackmapexec
|
||||
evil-winrm hash-identifier ophcrack
|
||||
|
||||
# Binary Analysis & Reverse Engineering
|
||||
gdb radare2 binwalk ghidra checksec strings objdump
|
||||
volatility3 foremost steghide exiftool
|
||||
```
|
||||
|
||||
**Cloud Security Tools:**
|
||||
```bash
|
||||
prowler scout-suite trivy
|
||||
kube-hunter kube-bench docker-bench-security
|
||||
```
|
||||
|
||||
**Browser Agent Requirements:**
|
||||
```bash
|
||||
# Chrome/Chromium for Browser Agent
|
||||
sudo apt install chromium-browser chromium-chromedriver
|
||||
# OR install Google Chrome
|
||||
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
||||
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
|
||||
sudo apt update && sudo apt install google-chrome-stable
|
||||
```
|
||||
|
||||
### Start the Server
|
||||
|
||||
```bash
|
||||
# Start the MCP server
|
||||
python3 hexstrike_server.py
|
||||
|
||||
# Optional: Start with debug mode
|
||||
python3 hexstrike_server.py --debug
|
||||
|
||||
# Optional: Custom port configuration
|
||||
python3 hexstrike_server.py --port 8888
|
||||
```
|
||||
|
||||
### Verify Installation
|
||||
|
||||
```bash
|
||||
# Test server health
|
||||
curl http://localhost:8888/health
|
||||
|
||||
# Test AI agent capabilities
|
||||
curl -X POST http://localhost:8888/api/intelligence/analyze-target \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"target": "example.com", "analysis_type": "comprehensive"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AI Client Integration Setup
|
||||
|
||||
### Claude Desktop Integration or Cursor
|
||||
|
||||
Edit `~/.config/Claude/claude_desktop_config.json`:
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"hexstrike-ai": {
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"/path/to/hexstrike-ai/hexstrike_mcp.py",
|
||||
"--server",
|
||||
"http://localhost:8888"
|
||||
],
|
||||
"description": "HexStrike AI v6.0 - Advanced Cybersecurity Automation Platform",
|
||||
"timeout": 300,
|
||||
"disabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### VS Code Copilot Integration
|
||||
|
||||
Configure VS Code settings in `.vscode/settings.json`:
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"hexstrike": {
|
||||
"type": "stdio",
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"/path/to/hexstrike-ai/hexstrike_mcp.py",
|
||||
"--server",
|
||||
"http://localhost:8888"
|
||||
]
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Security Tools Arsenal
|
||||
|
||||
**150+ Professional Security Tools:**
|
||||
|
||||
<details>
|
||||
<summary><b>🔍 Network Reconnaissance & Scanning (25+ Tools)</b></summary>
|
||||
|
||||
- **Nmap** - Advanced port scanning with custom NSE scripts and service detection
|
||||
- **Rustscan** - Ultra-fast port scanner with intelligent rate limiting
|
||||
- **Masscan** - High-speed Internet-scale port scanning with banner grabbing
|
||||
- **AutoRecon** - Comprehensive automated reconnaissance with 35+ parameters
|
||||
- **Amass** - Advanced subdomain enumeration and OSINT gathering
|
||||
- **Subfinder** - Fast passive subdomain discovery with multiple sources
|
||||
- **Fierce** - DNS reconnaissance and zone transfer testing
|
||||
- **DNSEnum** - DNS information gathering and subdomain brute forcing
|
||||
- **TheHarvester** - Email and subdomain harvesting from multiple sources
|
||||
- **ARP-Scan** - Network discovery using ARP requests
|
||||
- **NBTScan** - NetBIOS name scanning and enumeration
|
||||
- **RPCClient** - RPC enumeration and null session testing
|
||||
- **Enum4linux** - SMB enumeration with user, group, and share discovery
|
||||
- **Enum4linux-ng** - Advanced SMB enumeration with enhanced logging
|
||||
- **SMBMap** - SMB share enumeration and exploitation
|
||||
- **Responder** - LLMNR, NBT-NS and MDNS poisoner for credential harvesting
|
||||
- **NetExec** - Network service exploitation framework (formerly CrackMapExec)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🌐 Web Application Security Testing (40+ Tools)</b></summary>
|
||||
|
||||
- **Gobuster** - Directory, file, and DNS enumeration with intelligent wordlists
|
||||
- **Dirsearch** - Advanced directory and file discovery with enhanced logging
|
||||
- **Feroxbuster** - Recursive content discovery with intelligent filtering
|
||||
- **FFuf** - Fast web fuzzer with advanced filtering and parameter discovery
|
||||
- **Dirb** - Comprehensive web content scanner with recursive scanning
|
||||
- **HTTPx** - Fast HTTP probing and technology detection
|
||||
- **Katana** - Next-generation crawling and spidering with JavaScript support
|
||||
- **Hakrawler** - Fast web endpoint discovery and crawling
|
||||
- **Gau** - Get All URLs from multiple sources (Wayback, Common Crawl, etc.)
|
||||
- **Waybackurls** - Historical URL discovery from Wayback Machine
|
||||
- **Nuclei** - Fast vulnerability scanner with 4000+ templates
|
||||
- **Nikto** - Web server vulnerability scanner with comprehensive checks
|
||||
- **SQLMap** - Advanced automatic SQL injection testing with tamper scripts
|
||||
- **WPScan** - WordPress security scanner with vulnerability database
|
||||
- **Arjun** - HTTP parameter discovery with intelligent fuzzing
|
||||
- **ParamSpider** - Parameter mining from web archives
|
||||
- **X8** - Hidden parameter discovery with advanced techniques
|
||||
- **Jaeles** - Advanced vulnerability scanning with custom signatures
|
||||
- **Dalfox** - Advanced XSS vulnerability scanning with DOM analysis
|
||||
- **Wafw00f** - Web application firewall fingerprinting
|
||||
- **TestSSL** - SSL/TLS configuration testing and vulnerability assessment
|
||||
- **SSLScan** - SSL/TLS cipher suite enumeration
|
||||
- **SSLyze** - Fast and comprehensive SSL/TLS configuration analyzer
|
||||
- **Anew** - Append new lines to files for efficient data processing
|
||||
- **QSReplace** - Query string parameter replacement for systematic testing
|
||||
- **Uro** - URL filtering and deduplication for efficient testing
|
||||
- **Whatweb** - Web technology identification with fingerprinting
|
||||
- **JWT-Tool** - JSON Web Token testing with algorithm confusion
|
||||
- **GraphQL-Voyager** - GraphQL schema exploration and introspection testing
|
||||
- **Burp Suite Extensions** - Custom extensions for advanced web testing
|
||||
- **ZAP Proxy** - OWASP ZAP integration for automated security scanning
|
||||
- **Wfuzz** - Web application fuzzer with advanced payload generation
|
||||
- **Commix** - Command injection exploitation tool with automated detection
|
||||
- **NoSQLMap** - NoSQL injection testing for MongoDB, CouchDB, etc.
|
||||
- **Tplmap** - Server-side template injection exploitation tool
|
||||
|
||||
**🌐 Advanced Browser Agent:**
|
||||
- **Headless Chrome Automation** - Full Chrome browser automation with Selenium
|
||||
- **Screenshot Capture** - Automated screenshot generation for visual inspection
|
||||
- **DOM Analysis** - Deep DOM tree analysis and JavaScript execution monitoring
|
||||
- **Network Traffic Monitoring** - Real-time network request/response logging
|
||||
- **Security Header Analysis** - Comprehensive security header validation
|
||||
- **Form Detection & Analysis** - Automatic form discovery and input field analysis
|
||||
- **JavaScript Execution** - Dynamic content analysis with full JavaScript support
|
||||
- **Proxy Integration** - Seamless integration with Burp Suite and other proxies
|
||||
- **Multi-page Crawling** - Intelligent web application spidering and mapping
|
||||
- **Performance Metrics** - Page load times, resource usage, and optimization insights
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🔐 Authentication & Password Security (12+ Tools)</b></summary>
|
||||
|
||||
- **Hydra** - Network login cracker supporting 50+ protocols
|
||||
- **John the Ripper** - Advanced password hash cracking with custom rules
|
||||
- **Hashcat** - World's fastest password recovery tool with GPU acceleration
|
||||
- **Medusa** - Speedy, parallel, modular login brute-forcer
|
||||
- **Patator** - Multi-purpose brute-forcer with advanced modules
|
||||
- **NetExec** - Swiss army knife for pentesting networks
|
||||
- **SMBMap** - SMB share enumeration and exploitation tool
|
||||
- **Evil-WinRM** - Windows Remote Management shell with PowerShell integration
|
||||
- **Hash-Identifier** - Hash type identification tool
|
||||
- **HashID** - Advanced hash algorithm identifier with confidence scoring
|
||||
- **CrackStation** - Online hash lookup integration
|
||||
- **Ophcrack** - Windows password cracker using rainbow tables
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🔬 Binary Analysis & Reverse Engineering (25+ Tools)</b></summary>
|
||||
|
||||
- **GDB** - GNU Debugger with Python scripting and exploit development support
|
||||
- **GDB-PEDA** - Python Exploit Development Assistance for GDB
|
||||
- **GDB-GEF** - GDB Enhanced Features for exploit development
|
||||
- **Radare2** - Advanced reverse engineering framework with comprehensive analysis
|
||||
- **Ghidra** - NSA's software reverse engineering suite with headless analysis
|
||||
- **IDA Free** - Interactive disassembler with advanced analysis capabilities
|
||||
- **Binary Ninja** - Commercial reverse engineering platform
|
||||
- **Binwalk** - Firmware analysis and extraction tool with recursive extraction
|
||||
- **ROPgadget** - ROP/JOP gadget finder with advanced search capabilities
|
||||
- **Ropper** - ROP gadget finder and exploit development tool
|
||||
- **One-Gadget** - Find one-shot RCE gadgets in libc
|
||||
- **Checksec** - Binary security property checker with comprehensive analysis
|
||||
- **Strings** - Extract printable strings from binaries with filtering
|
||||
- **Objdump** - Display object file information with Intel syntax
|
||||
- **Readelf** - ELF file analyzer with detailed header information
|
||||
- **XXD** - Hex dump utility with advanced formatting
|
||||
- **Hexdump** - Hex viewer and editor with customizable output
|
||||
- **Pwntools** - CTF framework and exploit development library
|
||||
- **Angr** - Binary analysis platform with symbolic execution
|
||||
- **Libc-Database** - Libc identification and offset lookup tool
|
||||
- **Pwninit** - Automate binary exploitation setup
|
||||
- **Volatility** - Advanced memory forensics framework
|
||||
- **MSFVenom** - Metasploit payload generator with advanced encoding
|
||||
- **UPX** - Executable packer/unpacker for binary analysis
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>☁️ Cloud & Container Security (20+ Tools)</b></summary>
|
||||
|
||||
- **Prowler** - AWS/Azure/GCP security assessment with compliance checks
|
||||
- **Scout Suite** - Multi-cloud security auditing for AWS, Azure, GCP, Alibaba Cloud
|
||||
- **CloudMapper** - AWS network visualization and security analysis
|
||||
- **Pacu** - AWS exploitation framework with comprehensive modules
|
||||
- **Trivy** - Comprehensive vulnerability scanner for containers and IaC
|
||||
- **Clair** - Container vulnerability analysis with detailed CVE reporting
|
||||
- **Kube-Hunter** - Kubernetes penetration testing with active/passive modes
|
||||
- **Kube-Bench** - CIS Kubernetes benchmark checker with remediation
|
||||
- **Docker Bench Security** - Docker security assessment following CIS benchmarks
|
||||
- **Falco** - Runtime security monitoring for containers and Kubernetes
|
||||
- **Checkov** - Infrastructure as code security scanning
|
||||
- **Terrascan** - Infrastructure security scanner with policy-as-code
|
||||
- **CloudSploit** - Cloud security scanning and monitoring
|
||||
- **AWS CLI** - Amazon Web Services command line with security operations
|
||||
- **Azure CLI** - Microsoft Azure command line with security assessment
|
||||
- **GCloud** - Google Cloud Platform command line with security tools
|
||||
- **Kubectl** - Kubernetes command line with security context analysis
|
||||
- **Helm** - Kubernetes package manager with security scanning
|
||||
- **Istio** - Service mesh security analysis and configuration assessment
|
||||
- **OPA** - Policy engine for cloud-native security and compliance
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🏆 CTF & Forensics Tools (20+ Tools)</b></summary>
|
||||
|
||||
- **Volatility** - Advanced memory forensics framework with comprehensive plugins
|
||||
- **Volatility3** - Next-generation memory forensics with enhanced analysis
|
||||
- **Foremost** - File carving and data recovery with signature-based detection
|
||||
- **PhotoRec** - File recovery software with advanced carving capabilities
|
||||
- **TestDisk** - Disk partition recovery and repair tool
|
||||
- **Steghide** - Steganography detection and extraction with password support
|
||||
- **Stegsolve** - Steganography analysis tool with visual inspection
|
||||
- **Zsteg** - PNG/BMP steganography detection tool
|
||||
- **Outguess** - Universal steganographic tool for JPEG images
|
||||
- **ExifTool** - Metadata reader/writer for various file formats
|
||||
- **Binwalk** - Firmware analysis and reverse engineering with extraction
|
||||
- **Scalpel** - File carving tool with configurable headers and footers
|
||||
- **Bulk Extractor** - Digital forensics tool for extracting features
|
||||
- **Autopsy** - Digital forensics platform with timeline analysis
|
||||
- **Sleuth Kit** - Collection of command-line digital forensics tools
|
||||
|
||||
**Cryptography & Hash Analysis:**
|
||||
- **John the Ripper** - Password cracker with custom rules and advanced modes
|
||||
- **Hashcat** - GPU-accelerated password recovery with 300+ hash types
|
||||
- **Hash-Identifier** - Hash type identification with confidence scoring
|
||||
- **CyberChef** - Web-based analysis toolkit for encoding and encryption
|
||||
- **Cipher-Identifier** - Automatic cipher type detection and analysis
|
||||
- **Frequency-Analysis** - Statistical cryptanalysis for substitution ciphers
|
||||
- **RSATool** - RSA key analysis and common attack implementations
|
||||
- **FactorDB** - Integer factorization database for cryptographic challenges
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>🔥 Bug Bounty & OSINT Arsenal (20+ Tools)</b></summary>
|
||||
|
||||
- **Amass** - Advanced subdomain enumeration and OSINT gathering
|
||||
- **Subfinder** - Fast passive subdomain discovery with API integration
|
||||
- **Hakrawler** - Fast web endpoint discovery and crawling
|
||||
- **HTTPx** - Fast and multi-purpose HTTP toolkit with technology detection
|
||||
- **ParamSpider** - Mining parameters from web archives
|
||||
- **Aquatone** - Visual inspection of websites across hosts
|
||||
- **Subjack** - Subdomain takeover vulnerability checker
|
||||
- **DNSEnum** - DNS enumeration script with zone transfer capabilities
|
||||
- **Fierce** - Domain scanner for locating targets with DNS analysis
|
||||
- **TheHarvester** - Email and subdomain harvesting from multiple sources
|
||||
- **Sherlock** - Username investigation across 400+ social networks
|
||||
- **Social-Analyzer** - Social media analysis and OSINT gathering
|
||||
- **Recon-ng** - Web reconnaissance framework with modular architecture
|
||||
- **Maltego** - Link analysis and data mining for OSINT investigations
|
||||
- **SpiderFoot** - OSINT automation with 200+ modules
|
||||
- **Shodan** - Internet-connected device search with advanced filtering
|
||||
- **Censys** - Internet asset discovery with certificate analysis
|
||||
- **Have I Been Pwned** - Breach data analysis and credential exposure
|
||||
- **Pipl** - People search engine integration for identity investigation
|
||||
- **TruffleHog** - Git repository secret scanning with entropy analysis
|
||||
|
||||
</details>
|
||||
|
||||
### AI Agents
|
||||
|
||||
**12+ Specialized AI Agents:**
|
||||
|
||||
- **IntelligentDecisionEngine** - Tool selection and parameter optimization
|
||||
- **BugBountyWorkflowManager** - Bug bounty hunting workflows
|
||||
- **CTFWorkflowManager** - CTF challenge solving
|
||||
- **CVEIntelligenceManager** - Vulnerability intelligence
|
||||
- **AIExploitGenerator** - Automated exploit development
|
||||
- **VulnerabilityCorrelator** - Attack chain discovery
|
||||
- **TechnologyDetector** - Technology stack identification
|
||||
- **RateLimitDetector** - Rate limiting detection
|
||||
- **FailureRecoverySystem** - Error handling and recovery
|
||||
- **PerformanceMonitor** - System optimization
|
||||
- **ParameterOptimizer** - Context-aware optimization
|
||||
- **GracefulDegradation** - Fault-tolerant operation
|
||||
|
||||
### Advanced Features
|
||||
|
||||
- **Smart Caching System** - Intelligent result caching with LRU eviction
|
||||
- **Real-time Process Management** - Live command control and monitoring
|
||||
- **Vulnerability Intelligence** - CVE monitoring and exploit analysis
|
||||
- **Browser Agent** - Headless Chrome automation for web testing
|
||||
- **API Security Testing** - GraphQL, JWT, REST API security assessment
|
||||
- **Modern Visual Engine** - Real-time dashboards and progress tracking
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Core System Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/health` | GET | Server health check with tool availability |
|
||||
| `/api/command` | POST | Execute arbitrary commands with caching |
|
||||
| `/api/telemetry` | GET | System performance metrics |
|
||||
| `/api/cache/stats` | GET | Cache performance statistics |
|
||||
| `/api/intelligence/analyze-target` | POST | AI-powered target analysis |
|
||||
| `/api/intelligence/select-tools` | POST | Intelligent tool selection |
|
||||
| `/api/intelligence/optimize-parameters` | POST | Parameter optimization |
|
||||
|
||||
### Common MCP Tools
|
||||
|
||||
**Network Security Tools:**
|
||||
- `nmap_scan()` - Advanced Nmap scanning with optimization
|
||||
- `rustscan_scan()` - Ultra-fast port scanning
|
||||
- `masscan_scan()` - High-speed port scanning
|
||||
- `autorecon_scan()` - Comprehensive reconnaissance
|
||||
- `amass_enum()` - Subdomain enumeration and OSINT
|
||||
|
||||
**Web Application Tools:**
|
||||
- `gobuster_scan()` - Directory and file enumeration
|
||||
- `feroxbuster_scan()` - Recursive content discovery
|
||||
- `ffuf_scan()` - Fast web fuzzing
|
||||
- `nuclei_scan()` - Vulnerability scanning with templates
|
||||
- `sqlmap_scan()` - SQL injection testing
|
||||
- `wpscan_scan()` - WordPress security assessment
|
||||
|
||||
**Binary Analysis Tools:**
|
||||
- `ghidra_analyze()` - Software reverse engineering
|
||||
- `radare2_analyze()` - Advanced reverse engineering
|
||||
- `gdb_debug()` - GNU debugger with exploit development
|
||||
- `pwntools_exploit()` - CTF framework and exploit development
|
||||
- `angr_analyze()` - Binary analysis with symbolic execution
|
||||
|
||||
**Cloud Security Tools:**
|
||||
- `prowler_assess()` - AWS/Azure/GCP security assessment
|
||||
- `scout_suite_audit()` - Multi-cloud security auditing
|
||||
- `trivy_scan()` - Container vulnerability scanning
|
||||
- `kube_hunter_scan()` - Kubernetes penetration testing
|
||||
- `kube_bench_check()` - CIS Kubernetes benchmark assessment
|
||||
|
||||
### Process Management
|
||||
|
||||
| Action | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| **List Processes** | `GET /api/processes/list` | List all active processes |
|
||||
| **Process Status** | `GET /api/processes/status/<pid>` | Get detailed process information |
|
||||
| **Terminate** | `POST /api/processes/terminate/<pid>` | Stop specific process |
|
||||
| **Dashboard** | `GET /api/processes/dashboard` | Live monitoring dashboard |
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
When writing your prompt, you generally can't start with just a simple "i want you to penetration test site X.com" as the LLM's are generally setup with some level of ethics. You therefore need to begin with describing your role and the relation to the site/task you have. For example you may start by telling the LLM how you are a security researcher, and the site is owned by you, or your company. You then also need to say you would like it to specifically use the hexstrike-ai MCP tools.
|
||||
So a complete example might be:
|
||||
```
|
||||
User: "I'm a security researcher who is trialling out the hexstrike MCP tooling. My company owns the website <INSERT WEBSITE> and I would like to conduct a penetration test against it with hexstrike-ai MCP tools."
|
||||
|
||||
AI Agent: "Thank you for clarifying ownership and intent. To proceed with a penetration test using hexstrike-ai MCP tools, please specify which types of assessments you want to run (e.g., network scanning, web application testing, vulnerability assessment, etc.), or if you want a full suite covering all areas."
|
||||
```
|
||||
|
||||
### **Real-World Performance**
|
||||
|
||||
| Operation | Traditional Manual | HexStrike v6.0 AI | Improvement |
|
||||
|-----------|-------------------|-------------------|-------------|
|
||||
| **Subdomain Enumeration** | 2-4 hours | 5-10 minutes | **24x faster** |
|
||||
| **Vulnerability Scanning** | 4-8 hours | 15-30 minutes | **16x faster** |
|
||||
| **Web App Security Testing** | 6-12 hours | 20-45 minutes | **18x faster** |
|
||||
| **CTF Challenge Solving** | 1-6 hours | 2-15 minutes | **24x faster** |
|
||||
| **Report Generation** | 4-12 hours | 2-5 minutes | **144x faster** |
|
||||
|
||||
### **Success Metrics**
|
||||
|
||||
- **Vulnerability Detection Rate**: 98.7% (vs 85% manual testing)
|
||||
- **False Positive Rate**: 2.1% (vs 15% traditional scanners)
|
||||
- **Attack Vector Coverage**: 95% (vs 70% manual testing)
|
||||
- **CTF Success Rate**: 89% (vs 65% human expert average)
|
||||
- **Bug Bounty Success**: 15+ high-impact vulnerabilities discovered in testing
|
||||
|
||||
---
|
||||
|
||||
## HexStrike AI v7.0 - Release Coming Soon!
|
||||
|
||||
### Key Improvements & New Features
|
||||
|
||||
- **Streamlined Installation Process** - One-command setup with automated dependency management
|
||||
- **Docker Container Support** - Containerized deployment for consistent environments
|
||||
- **250+ Specialized AI Agents/Tools** - Expanded from 150+ to 250+ autonomous security agents
|
||||
- **Native Desktop Client** - Full-featured Application ([www.hexstrike.com](https://www.hexstrike.com))
|
||||
- **Advanced Web Automation** - Enhanced Selenium integration with anti-detection
|
||||
- **JavaScript Runtime Analysis** - Deep DOM inspection and dynamic content handling
|
||||
- **Memory Optimization** - 40% reduction in resource usage for large-scale operations
|
||||
- **Enhanced Error Handling** - Graceful degradation and automatic recovery mechanisms
|
||||
- **Bypassing Limitations** - Fixed limited allowed mcp tools by MCP clients
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **MCP Connection Failed**:
|
||||
```bash
|
||||
# Check if server is running
|
||||
netstat -tlnp | grep 8888
|
||||
|
||||
# Restart server
|
||||
python3 hexstrike_server.py
|
||||
```
|
||||
|
||||
2. **Security Tools Not Found**:
|
||||
```bash
|
||||
# Check tool availability
|
||||
which nmap gobuster nuclei
|
||||
|
||||
# Install missing tools from their official sources
|
||||
```
|
||||
|
||||
3. **AI Agent Cannot Connect**:
|
||||
```bash
|
||||
# Verify MCP configuration paths
|
||||
# Check server logs for connection attempts
|
||||
python3 hexstrike_mcp.py --debug
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug mode for detailed logging:
|
||||
```bash
|
||||
python3 hexstrike_server.py --debug
|
||||
python3 hexstrike_mcp.py --debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
⚠️ **Important Security Notes**:
|
||||
- This tool provides AI agents with powerful system access
|
||||
- Run in isolated environments or dedicated security testing VMs
|
||||
- AI agents can execute arbitrary security tools - ensure proper oversight
|
||||
- Monitor AI agent activities through the real-time dashboard
|
||||
- Consider implementing authentication for production deployments
|
||||
|
||||
### Legal & Ethical Use
|
||||
|
||||
- ✅ **Authorized Penetration Testing** - With proper written authorization
|
||||
- ✅ **Bug Bounty Programs** - Within program scope and rules
|
||||
- ✅ **CTF Competitions** - Educational and competitive environments
|
||||
- ✅ **Security Research** - On owned or authorized systems
|
||||
- ✅ **Red Team Exercises** - With organizational approval
|
||||
|
||||
- ❌ **Unauthorized Testing** - Never test systems without permission
|
||||
- ❌ **Malicious Activities** - No illegal or harmful activities
|
||||
- ❌ **Data Theft** - No unauthorized data access or exfiltration
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions from the cybersecurity and AI community!
|
||||
|
||||
### Development Setup
|
||||
|
||||
```bash
|
||||
# 1. Fork and clone the repository
|
||||
git clone https://github.com/0x4m4/hexstrike-ai.git
|
||||
cd hexstrike-ai
|
||||
|
||||
# 2. Create development environment
|
||||
python3 -m venv hexstrike-dev
|
||||
source hexstrike-dev/bin/activate
|
||||
|
||||
# 3. Install development dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 4. Start development server
|
||||
python3 hexstrike_server.py --port 8888 --debug
|
||||
```
|
||||
|
||||
### Priority Areas for Contribution
|
||||
|
||||
- **🤖 AI Agent Integrations** - Support for new AI platforms and agents
|
||||
- **🛠️ Security Tool Additions** - Integration of additional security tools
|
||||
- **⚡ Performance Optimizations** - Caching improvements and scalability enhancements
|
||||
- **📖 Documentation** - AI usage examples and integration guides
|
||||
- **🧪 Testing Frameworks** - Automated testing for AI agent interactions
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
**m0x4m4** - [www.0x4m4.com](https://www.0x4m4.com) | [HexStrike](https://www.hexstrike.com)
|
||||
|
||||
---
|
||||
|
||||
## Official Sponsor
|
||||
|
||||
<p align="center">
|
||||
<strong>Sponsored By LeaksAPI - Live Dark Web Data leak checker</strong>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://leak-check.net">
|
||||
<img src="assets/leaksapi-logo.png" alt="LeaksAPI Logo" width="150" />
|
||||
</a>
|
||||
|
||||
<a href="https://leak-check.net">
|
||||
<img src="assets/leaksapi-banner.png" alt="LeaksAPI Banner" width="450" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://leak-check.net">
|
||||
<img src="https://img.shields.io/badge/Visit-leak--check.net-00D4AA?style=for-the-badge&logo=shield&logoColor=white" alt="Visit leak-check.net" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
## 🌟 **Star History**
|
||||
|
||||
[](https://star-history.com/#0x4m4/hexstrike-ai&Date)
|
||||
|
||||
### **📊 Project Statistics**
|
||||
|
||||
- **150+ Security Tools** - Comprehensive security testing arsenal
|
||||
- **12+ AI Agents** - Autonomous decision-making and workflow management
|
||||
- **4000+ Vulnerability Templates** - Nuclei integration with extensive coverage
|
||||
- **35+ Attack Categories** - From web apps to cloud infrastructure
|
||||
- **Real-time Processing** - Sub-second response times with intelligent caching
|
||||
- **99.9% Uptime** - Fault-tolerant architecture with graceful degradation
|
||||
|
||||
### **🚀 Ready to Transform Your AI Agents?**
|
||||
|
||||
**[⭐ Star this repository](https://github.com/0x4m4/hexstrike-ai)** • **[🍴 Fork and contribute](https://github.com/0x4m4/hexstrike-ai/fork)** • **[📖 Read the docs](docs/)**
|
||||
|
||||
---
|
||||
|
||||
**Made with ❤️ by the cybersecurity community for AI-powered security automation**
|
||||
|
||||
*HexStrike AI v6.0 - Where artificial intelligence meets cybersecurity excellence*
|
||||
|
||||
</div>
|
||||
BIN
third_party/hexstrike/assets/hexstrike-logo.png
vendored
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
third_party/hexstrike/assets/leaksapi-banner.png
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
third_party/hexstrike/assets/leaksapi-logo.png
vendored
Normal file
|
After Width: | Height: | Size: 990 KiB |
BIN
third_party/hexstrike/assets/usage_input.png
vendored
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
third_party/hexstrike/assets/usage_output.png
vendored
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
third_party/hexstrike/assets/usage_server1.png
vendored
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
third_party/hexstrike/assets/usage_server2.png
vendored
Normal file
|
After Width: | Height: | Size: 340 KiB |
15
third_party/hexstrike/hexstrike-ai-mcp.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"hexstrike-ai": {
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"/path/hexstrike_mcp.py",
|
||||
"--server",
|
||||
"http://IPADDRESS:8888"
|
||||
],
|
||||
"description": "HexStrike AI v6.0 - Advanced Cybersecurity Automation Platform. Turn off alwaysAllow if you dont want autonomous execution!",
|
||||
"timeout": 300,
|
||||
"alwaysAllow": []
|
||||
}
|
||||
}
|
||||
}
|
||||
5470
third_party/hexstrike/hexstrike_mcp.py
vendored
Normal file
17289
third_party/hexstrike/hexstrike_server.py
vendored
Normal file
84
third_party/hexstrike/requirements.txt
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
# HexStrike AI MCP Agents v6.0
|
||||
#
|
||||
# INSTALLATION COMMANDS:
|
||||
# python3 -m venv hexstrike_env
|
||||
# source hexstrike_env/bin/activate
|
||||
# python3 -m pip install -r requirements.txt
|
||||
# python3 hexstrike_server.py
|
||||
|
||||
# ============================================================================
|
||||
# CORE FRAMEWORK DEPENDENCIES (ACTUALLY USED)
|
||||
# ============================================================================
|
||||
flask>=2.3.0,<4.0.0 # Web framework for API server (flask import)
|
||||
requests>=2.31.0,<3.0.0 # HTTP library (requests import)
|
||||
psutil>=5.9.0,<6.0.0 # System utilities (psutil import)
|
||||
fastmcp>=0.2.0,<1.0.0 # MCP framework (from mcp.server.fastmcp import FastMCP)
|
||||
|
||||
# ============================================================================
|
||||
# WEB SCRAPING & AUTOMATION (ACTUALLY USED)
|
||||
# ============================================================================
|
||||
beautifulsoup4>=4.12.0,<5.0.0 # HTML parsing (from bs4 import BeautifulSoup)
|
||||
selenium>=4.15.0,<5.0.0 # Browser automation (selenium imports)
|
||||
webdriver-manager>=4.0.0,<5.0.0 # ChromeDriver management (referenced in code)
|
||||
|
||||
# ============================================================================
|
||||
# ASYNC & NETWORKING (ACTUALLY USED)
|
||||
# ============================================================================
|
||||
aiohttp>=3.8.0,<4.0.0 # Async HTTP (aiohttp import)
|
||||
|
||||
# ============================================================================
|
||||
# PROXY & TESTING (ACTUALLY USED)
|
||||
# ============================================================================
|
||||
mitmproxy>=9.0.0,<11.0.0 # HTTP proxy (mitmproxy imports)
|
||||
|
||||
# ============================================================================
|
||||
# BINARY ANALYSIS (CONDITIONALLY USED)
|
||||
# ============================================================================
|
||||
pwntools>=4.10.0,<5.0.0 # Binary exploitation (from pwn import *)
|
||||
angr>=9.2.0,<10.0.0 # Binary analysis (import angr)
|
||||
bcrypt==4.0.1 # Pin bcrypt version for passlib compatibility (fixes pwntools dependency issue)
|
||||
|
||||
# ============================================================================
|
||||
# EXTERNAL SECURITY TOOLS (150+ Tools - Install separately)
|
||||
# ============================================================================
|
||||
#
|
||||
# HexStrike v6.0 integrates with 150+ external security tools that must be
|
||||
# installed separately from their official sources:
|
||||
#
|
||||
# 🔍 Network & Reconnaissance (25+ tools):
|
||||
# - nmap, masscan, rustscan, autorecon, amass, subfinder, fierce
|
||||
# - dnsenum, theharvester, responder, netexec, enum4linux-ng
|
||||
#
|
||||
# 🌐 Web Application Security (40+ tools):
|
||||
# - gobuster, feroxbuster, ffuf, dirb, dirsearch, nuclei, nikto
|
||||
# - sqlmap, wpscan, arjun, paramspider, x8, katana, httpx
|
||||
# - dalfox, jaeles, hakrawler, gau, waybackurls, wafw00f
|
||||
#
|
||||
# 🔐 Authentication & Password (12+ tools):
|
||||
# - hydra, john, hashcat, medusa, patator, netexec
|
||||
# - evil-winrm, hash-identifier, ophcrack
|
||||
#
|
||||
# 🔬 Binary Analysis & Reverse Engineering (25+ tools):
|
||||
# - ghidra, radare2, gdb, binwalk, ropgadget, checksec, strings
|
||||
# - volatility3, foremost, steghide, exiftool, angr, pwntools
|
||||
#
|
||||
# ☁️ Cloud & Container Security (20+ tools):
|
||||
# - prowler, scout-suite, trivy, kube-hunter, kube-bench
|
||||
# - docker-bench-security, checkov, terrascan, falco
|
||||
#
|
||||
# 🏆 CTF & Forensics (20+ tools):
|
||||
# - volatility3, autopsy, sleuthkit, stegsolve, zsteg, outguess
|
||||
# - photorec, testdisk, scalpel, bulk-extractor
|
||||
#
|
||||
# 🕵️ OSINT & Intelligence (20+ tools):
|
||||
# - sherlock, social-analyzer, recon-ng, maltego, spiderfoot
|
||||
# - shodan-cli, censys-cli, have-i-been-pwned
|
||||
#
|
||||
# Installation Notes:
|
||||
# 1. Kali Linux 2024.1+ includes most tools by default
|
||||
# 2. Ubuntu/Debian users should install tools from official repositories
|
||||
# 3. Some tools require compilation from source or additional setup
|
||||
# 4. Cloud tools require API keys and authentication configuration
|
||||
# 5. Browser Agent requires Chrome/Chromium and ChromeDriver installation
|
||||
#
|
||||
# For complete installation instructions and setup guides, see README.md
|
||||