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:
SsSnake
2026-03-24 06:59:06 -07:00
parent 1092689f45
commit da53899f66
382 changed files with 15277 additions and 493964 deletions

View File

@@ -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"]})'}