AUTARCH v1.9 — remote monitoring, SSH manager, daemon, vault, cleanup
- Add Remote Monitoring Station with PIAP device profile system - Add SSH/SSHD manager with fail2ban integration - Add privileged daemon architecture for safe root operations - Add encrypted vault, HAL memory, HAL auto-analyst - Add network security suite, module creator, codex training - Add start.sh launcher script and GTK3 desktop launcher - Remove Output/ build artifacts, installer files, loose docs - Update .gitignore for runtime data and build artifacts - Update README for v1.9 with new launch method, screenshots, and features Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -188,14 +188,15 @@ def _run_nmap(args: dict, config) -> str:
|
||||
cmd.append(target)
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||
nmap_timeout = config.get_int('mcp', 'nmap_timeout', 120)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=nmap_timeout)
|
||||
return json.dumps({
|
||||
'stdout': result.stdout,
|
||||
'stderr': result.stderr,
|
||||
'exit_code': result.returncode
|
||||
})
|
||||
except subprocess.TimeoutExpired:
|
||||
return json.dumps({'error': 'Scan timed out after 120 seconds'})
|
||||
return json.dumps({'error': f'Scan timed out after {nmap_timeout} seconds'})
|
||||
except Exception as e:
|
||||
return json.dumps({'error': str(e)})
|
||||
|
||||
@@ -207,8 +208,11 @@ def _run_geoip(args: dict) -> str:
|
||||
|
||||
try:
|
||||
import urllib.request
|
||||
url = f"http://ip-api.com/json/{ip}?fields=status,message,country,regionName,city,zip,lat,lon,timezone,isp,org,as,query"
|
||||
with urllib.request.urlopen(url, timeout=10) as resp:
|
||||
config = get_config()
|
||||
geoip_endpoint = config.get('mcp', 'geoip_endpoint', 'http://ip-api.com/json/')
|
||||
geoip_timeout = config.get_int('mcp', 'geoip_timeout', 10)
|
||||
url = f"{geoip_endpoint}{ip}?fields=status,message,country,regionName,city,zip,lat,lon,timezone,isp,org,as,query"
|
||||
with urllib.request.urlopen(url, timeout=geoip_timeout) as resp:
|
||||
return resp.read().decode()
|
||||
except Exception as e:
|
||||
return json.dumps({'error': str(e)})
|
||||
@@ -219,11 +223,13 @@ def _run_dns(args: dict) -> str:
|
||||
if not domain:
|
||||
return json.dumps({'error': 'domain is required'})
|
||||
|
||||
config = get_config()
|
||||
dns_timeout = config.get_int('mcp', 'dns_timeout', 10)
|
||||
record_type = args.get('record_type', 'A')
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['dig', '+short', domain, record_type],
|
||||
capture_output=True, text=True, timeout=10
|
||||
capture_output=True, text=True, timeout=dns_timeout
|
||||
)
|
||||
records = [r for r in result.stdout.strip().split('\n') if r]
|
||||
return json.dumps({'domain': domain, 'type': record_type, 'records': records})
|
||||
@@ -244,10 +250,12 @@ def _run_whois(args: dict) -> str:
|
||||
if not target:
|
||||
return json.dumps({'error': 'target is required'})
|
||||
|
||||
config = get_config()
|
||||
whois_timeout = config.get_int('mcp', 'whois_timeout', 15)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['whois', target],
|
||||
capture_output=True, text=True, timeout=15
|
||||
capture_output=True, text=True, timeout=whois_timeout
|
||||
)
|
||||
return json.dumps({'target': target, 'output': result.stdout[:4000]})
|
||||
except FileNotFoundError:
|
||||
@@ -274,7 +282,9 @@ def _run_tcpdump(args: dict) -> str:
|
||||
cmd.append(bpf_filter)
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
config = get_config()
|
||||
tcpdump_timeout = config.get_int('mcp', 'tcpdump_timeout', 30)
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=tcpdump_timeout)
|
||||
return json.dumps({
|
||||
'stdout': result.stdout,
|
||||
'stderr': result.stderr,
|
||||
@@ -428,68 +438,95 @@ def create_mcp_server():
|
||||
"""Create and return the FastMCP server instance."""
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("autarch", instructions="AUTARCH security framework tools")
|
||||
config = get_config()
|
||||
mcp_settings = config.get_mcp_settings()
|
||||
|
||||
fastmcp_kwargs = {
|
||||
'instructions': mcp_settings['instructions'],
|
||||
}
|
||||
if mcp_settings['log_level']:
|
||||
fastmcp_kwargs['log_level'] = mcp_settings['log_level']
|
||||
if mcp_settings['mask_errors']:
|
||||
fastmcp_kwargs['mask_error_details'] = True
|
||||
if mcp_settings['rate_limit']:
|
||||
fastmcp_kwargs['rate_limit'] = mcp_settings['rate_limit']
|
||||
|
||||
mcp = FastMCP("autarch", **fastmcp_kwargs)
|
||||
|
||||
# Filter out disabled tools
|
||||
disabled = set(t.strip() for t in mcp_settings['disabled_tools'].split(',') if t.strip())
|
||||
|
||||
# Register all tools
|
||||
tool_defs = get_autarch_tools()
|
||||
|
||||
@mcp.tool()
|
||||
def nmap_scan(target: str, ports: str = "", scan_type: str = "quick") -> str:
|
||||
"""Run an nmap network scan against a target. Returns scan results including open ports and services."""
|
||||
return execute_tool('nmap_scan', {'target': target, 'ports': ports, 'scan_type': scan_type})
|
||||
if 'nmap_scan' not in disabled:
|
||||
@mcp.tool()
|
||||
def nmap_scan(target: str, ports: str = "", scan_type: str = "quick") -> str:
|
||||
"""Run an nmap network scan against a target. Returns scan results including open ports and services."""
|
||||
return execute_tool('nmap_scan', {'target': target, 'ports': ports, 'scan_type': scan_type})
|
||||
|
||||
@mcp.tool()
|
||||
def geoip_lookup(ip: str) -> str:
|
||||
"""Look up geographic and network information for an IP address."""
|
||||
return execute_tool('geoip_lookup', {'ip': ip})
|
||||
if 'geoip_lookup' not in disabled:
|
||||
@mcp.tool()
|
||||
def geoip_lookup(ip: str) -> str:
|
||||
"""Look up geographic and network information for an IP address."""
|
||||
return execute_tool('geoip_lookup', {'ip': ip})
|
||||
|
||||
@mcp.tool()
|
||||
def dns_lookup(domain: str, record_type: str = "A") -> str:
|
||||
"""Perform DNS lookups for a domain. Supports A, AAAA, MX, NS, TXT, CNAME, SOA record types."""
|
||||
return execute_tool('dns_lookup', {'domain': domain, 'record_type': record_type})
|
||||
if 'dns_lookup' not in disabled:
|
||||
@mcp.tool()
|
||||
def dns_lookup(domain: str, record_type: str = "A") -> str:
|
||||
"""Perform DNS lookups for a domain. Supports A, AAAA, MX, NS, TXT, CNAME, SOA record types."""
|
||||
return execute_tool('dns_lookup', {'domain': domain, 'record_type': record_type})
|
||||
|
||||
@mcp.tool()
|
||||
def whois_lookup(target: str) -> str:
|
||||
"""Perform WHOIS lookup for a domain or IP address."""
|
||||
return execute_tool('whois_lookup', {'target': target})
|
||||
if 'whois_lookup' not in disabled:
|
||||
@mcp.tool()
|
||||
def whois_lookup(target: str) -> str:
|
||||
"""Perform WHOIS lookup for a domain or IP address."""
|
||||
return execute_tool('whois_lookup', {'target': target})
|
||||
|
||||
@mcp.tool()
|
||||
def packet_capture(interface: str = "", count: int = 10, filter: str = "") -> str:
|
||||
"""Capture network packets using tcpdump. Returns captured packet summary."""
|
||||
return execute_tool('packet_capture', {'interface': interface, 'count': count, 'filter': filter})
|
||||
if 'packet_capture' not in disabled:
|
||||
@mcp.tool()
|
||||
def packet_capture(interface: str = "", count: int = 10, filter: str = "") -> str:
|
||||
"""Capture network packets using tcpdump. Returns captured packet summary."""
|
||||
return execute_tool('packet_capture', {'interface': interface, 'count': count, 'filter': filter})
|
||||
|
||||
@mcp.tool()
|
||||
def wireguard_status() -> str:
|
||||
"""Get WireGuard VPN tunnel status and peer information."""
|
||||
return execute_tool('wireguard_status', {})
|
||||
if 'wireguard_status' not in disabled:
|
||||
@mcp.tool()
|
||||
def wireguard_status() -> str:
|
||||
"""Get WireGuard VPN tunnel status and peer information."""
|
||||
return execute_tool('wireguard_status', {})
|
||||
|
||||
@mcp.tool()
|
||||
def upnp_status() -> str:
|
||||
"""Get UPnP port mapping status."""
|
||||
return execute_tool('upnp_status', {})
|
||||
if 'upnp_status' not in disabled:
|
||||
@mcp.tool()
|
||||
def upnp_status() -> str:
|
||||
"""Get UPnP port mapping status."""
|
||||
return execute_tool('upnp_status', {})
|
||||
|
||||
@mcp.tool()
|
||||
def system_info() -> str:
|
||||
"""Get AUTARCH system information: hostname, platform, uptime, tool availability."""
|
||||
return execute_tool('system_info', {})
|
||||
if 'system_info' not in disabled:
|
||||
@mcp.tool()
|
||||
def system_info() -> str:
|
||||
"""Get AUTARCH system information: hostname, platform, uptime, tool availability."""
|
||||
return execute_tool('system_info', {})
|
||||
|
||||
@mcp.tool()
|
||||
def llm_chat(message: str, system_prompt: str = "") -> str:
|
||||
"""Send a message to the currently configured LLM backend and get a response."""
|
||||
args = {'message': message}
|
||||
if system_prompt:
|
||||
args['system_prompt'] = system_prompt
|
||||
return execute_tool('llm_chat', args)
|
||||
if 'llm_chat' not in disabled:
|
||||
@mcp.tool()
|
||||
def llm_chat(message: str, system_prompt: str = "") -> str:
|
||||
"""Send a message to the currently configured LLM backend and get a response."""
|
||||
args = {'message': message}
|
||||
if system_prompt:
|
||||
args['system_prompt'] = system_prompt
|
||||
return execute_tool('llm_chat', args)
|
||||
|
||||
@mcp.tool()
|
||||
def android_devices() -> str:
|
||||
"""List connected Android devices via ADB."""
|
||||
return execute_tool('android_devices', {})
|
||||
if 'android_devices' not in disabled:
|
||||
@mcp.tool()
|
||||
def android_devices() -> str:
|
||||
"""List connected Android devices via ADB."""
|
||||
return execute_tool('android_devices', {})
|
||||
|
||||
@mcp.tool()
|
||||
def config_get(section: str, key: str) -> str:
|
||||
"""Get an AUTARCH configuration value. Sensitive keys (api_key, password) are blocked."""
|
||||
return execute_tool('config_get', {'section': section, 'key': key})
|
||||
if 'config_get' not in disabled:
|
||||
@mcp.tool()
|
||||
def config_get(section: str, key: str) -> str:
|
||||
"""Get an AUTARCH configuration value. Sensitive keys (api_key, password) are blocked."""
|
||||
return execute_tool('config_get', {'section': section, 'key': key})
|
||||
|
||||
return mcp
|
||||
|
||||
@@ -502,6 +539,12 @@ def run_stdio():
|
||||
|
||||
def run_sse(host: str = '0.0.0.0', port: int = 8081):
|
||||
"""Run the MCP server in SSE (Server-Sent Events) mode for web clients."""
|
||||
config = get_config()
|
||||
mcp_settings = config.get_mcp_settings()
|
||||
if host == '0.0.0.0':
|
||||
host = mcp_settings['host']
|
||||
if port == 8081:
|
||||
port = mcp_settings['port']
|
||||
mcp = create_mcp_server()
|
||||
mcp.run(transport='sse', host=host, port=port)
|
||||
|
||||
@@ -535,6 +578,13 @@ def start_sse_server(host: str = '0.0.0.0', port: int = 8081) -> dict:
|
||||
"""Start the MCP SSE server in the background."""
|
||||
global _server_process
|
||||
|
||||
config = get_config()
|
||||
mcp_settings = config.get_mcp_settings()
|
||||
if host == '0.0.0.0':
|
||||
host = mcp_settings['host']
|
||||
if port == 8081:
|
||||
port = mcp_settings['port']
|
||||
|
||||
status = get_server_status()
|
||||
if status['running']:
|
||||
return {'ok': False, 'error': f'Already running (PID {status["pid"]})'}
|
||||
|
||||
Reference in New Issue
Block a user