diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..987e4a3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +venv +.venv +__pycache__ +*.pyc +.pytest_cache +.mypy_cache +.git +loot +dist +build +*.egg-info +third_party/MetasploitMCP +venv/ +venv/* +.env +runtime/ +/.pytest_cache \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index aa0861e..47c5d9a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: context: . dockerfile: Dockerfile.kali container_name: pentestagent-kali - privileged: true # Required for VPN and some tools + privileged: true # Required for VPN and some tools. NOTE: this is risky on shared hosts; prefer running inside a disposable VM. cap_add: - NET_ADMIN - SYS_ADMIN @@ -31,6 +31,9 @@ services: - PENTESTAGENT_MODEL=${PENTESTAGENT_MODEL} - ENABLE_TOR=${ENABLE_TOR:-false} - INIT_METASPLOIT=${INIT_METASPLOIT:-false} + # By default msfrpcd binds to loopback; to intentionally expose Metasploit RPC to the host + # set EXPOSE_MSF_RPC=true in your environment. This is NOT recommended on shared machines. + - EXPOSE_MSF_RPC=${EXPOSE_MSF_RPC:-false} volumes: - ./loot:/app/loot networks: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 1373637..a3c1dec 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -11,12 +11,12 @@ NC='\033[0m' echo -e "${GREEN}🔧 PentestAgent Container Starting...${NC}" -# Start VPN if config provided -if [ -f "/vpn/config.ovpn" ]; then +# Start VPN if config provided and openvpn is available +if [ -f "/vpn/config.ovpn" ] && command -v openvpn >/dev/null 2>&1; then echo -e "${YELLOW}📡 Starting VPN connection...${NC}" - openvpn --config /vpn/config.ovpn --daemon + openvpn --config /vpn/config.ovpn --daemon || echo "openvpn failed to start" sleep 5 - + # Check VPN connection if ip a show tun0 &>/dev/null; then echo -e "${GREEN}✅ VPN connected${NC}" @@ -25,22 +25,35 @@ if [ -f "/vpn/config.ovpn" ]; then fi fi -# Start Tor if enabled -if [ "$ENABLE_TOR" = "true" ]; then +# Start Tor if enabled and if a service command is available +if [ "$ENABLE_TOR" = "true" ] && command -v service >/dev/null 2>&1; then echo -e "${YELLOW}🧅 Starting Tor...${NC}" - service tor start + service tor start || echo "tor service not available" sleep 3 fi -# Initialize any databases -if [ "$INIT_METASPLOIT" = "true" ]; then +# Initialize any databases (guarded) +if [ "$INIT_METASPLOIT" = "true" ] && command -v msfdb >/dev/null 2>&1; then echo -e "${YELLOW}🗄️ Initializing Metasploit database...${NC}" - msfdb init 2>/dev/null || true + msfdb init 2>/dev/null || echo "msfdb init failed" fi -# Create output directory with timestamp -OUTPUT_DIR="/output/$(date +%Y%m%d_%H%M%S)" +# Ensure persistent output directory lives under /app/loot (mounted by compose) +OUTPUT_DIR="/app/loot/$(date +%Y%m%d_%H%M%S)" mkdir -p "$OUTPUT_DIR" + +# Optionally chown mounted volume on startup (only when running as root and explicitly enabled) +if [ "$(id -u)" = "0" ] && [ "${CHOWN_ON_START,,}" = "true" ]; then + # If PUID/PGID supplied use them, otherwise keep default permissions + if [ -n "${PUID:-}" ] && [ -n "${PGID:-}" ]; then + groupadd -g ${PGID} pentestagent 2>/dev/null || true + useradd -u ${PUID} -g ${PGID} -m pentestagent 2>/dev/null || true + chown -R ${PUID}:${PGID} /app/loot || true + else + chown -R pentestagent:pentestagent /app/loot 2>/dev/null || true + fi +fi + export PENTESTAGENT_OUTPUT_DIR="$OUTPUT_DIR" echo -e "${GREEN}📁 Output directory: $OUTPUT_DIR${NC}" diff --git a/pentestagent/interface/main.py b/pentestagent/interface/main.py index 51c6564..bc48e2b 100644 --- a/pentestagent/interface/main.py +++ b/pentestagent/interface/main.py @@ -389,6 +389,15 @@ def main(): # If no command provided, default to TUI if args.command is None: + # Ensure a default model is configured; provide a friendly error if not + if not DEFAULT_MODEL: + print("Error: No default model configured (PENTESTAGENT_MODEL).") + print("Set PENTESTAGENT_MODEL in .env file or pass --model on the command line.") + print( + "Example: PENTESTAGENT_MODEL=gpt-5 or PENTESTAGENT_MODEL=claude-sonnet-4-20250514" + ) + return + run_tui(target=None, model=DEFAULT_MODEL, use_docker=False) return diff --git a/pentestagent/interface/tui.py b/pentestagent/interface/tui.py index 19329e1..d7ce51f 100644 --- a/pentestagent/interface/tui.py +++ b/pentestagent/interface/tui.py @@ -1139,9 +1139,21 @@ 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 + # Validate model/runtime presence and provide a user-friendly error + if not self.model: + self._add_system( + "[!] No model configured. Set PENTESTAGENT_MODEL environment variable or create a .env file (see .env.example)." + ) + self._set_status("error") + self._is_initializing = False + return + + if not self.runtime: + self._add_system("[!] Runtime failed to initialize.") + self._set_status("error") + self._is_initializing = False + return + llm = LLM( model=self.model, config=ModelConfig(temperature=0.7), diff --git a/scripts/setup.sh b/scripts/setup.sh index 3dea4bd..82693a8 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -154,6 +154,17 @@ if [ "${LAUNCH_METASPLOIT_MCP,,}" = "true" ] && [ -n "${MSF_PASSWORD:-}" ]; then LOG_DIR="loot/artifacts" mkdir -p "$LOG_DIR" MSF_LOG="$LOG_DIR/metasploit_msfrpcd.log" + # For safety, bind msfrpcd to loopback by default. To intentionally expose RPC to the host + # set EXPOSE_MSF_RPC=true in your environment (not recommended on shared hosts). + if [ "${EXPOSE_MSF_RPC,,}" != "true" ]; then + if [ "$MSF_SERVER" != "127.0.0.1" ] && [ "$MSF_SERVER" != "localhost" ]; then + echo "Warning: MSF_SERVER is set to '$MSF_SERVER' but EXPOSE_MSF_RPC is not true. Overriding to 127.0.0.1 for safety." + fi + MSF_SERVER=127.0.0.1 + else + echo "EXPOSE_MSF_RPC=true: msfrpcd will bind to $MSF_SERVER and may be reachable from the host network. Ensure you know the risks." + fi + 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