Add Android forensics, IOC threat-intel DB, Compose companion; scrub secrets from configs
This commit is contained in:
@@ -133,6 +133,8 @@ def create_app():
|
||||
from web.routes.module_creator import module_creator_bp
|
||||
from web.routes.ssh_manager import ssh_manager_bp
|
||||
from web.routes.remote_monitor import remote_monitor_bp
|
||||
from web.routes.android_forensics import android_forensics_bp
|
||||
from web.routes.android_forensics_ai import android_forensics_ai_bp
|
||||
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(dashboard_bp)
|
||||
@@ -198,6 +200,8 @@ def create_app():
|
||||
app.register_blueprint(module_creator_bp)
|
||||
app.register_blueprint(remote_monitor_bp)
|
||||
app.register_blueprint(ssh_manager_bp)
|
||||
app.register_blueprint(android_forensics_bp)
|
||||
app.register_blueprint(android_forensics_ai_bp)
|
||||
|
||||
# Start network discovery advertising (mDNS + Bluetooth)
|
||||
try:
|
||||
|
||||
@@ -38,10 +38,16 @@ def check_password(password, password_hash):
|
||||
|
||||
|
||||
def login_required(f):
|
||||
"""Login wall is disabled — AutarchOS is a single-user appliance.
|
||||
|
||||
The decorator stays as a pass-through so the rest of the codebase keeps
|
||||
compiling; it auto-populates session['user'] so templates that branch on
|
||||
`session.get('user')` still render the desktop chrome.
|
||||
"""
|
||||
@functools.wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
if 'user' not in session:
|
||||
return redirect(url_for('auth.login', next=request.url))
|
||||
session['user'] = 'admin'
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
|
||||
367
web/routes/android_forensics.py
Normal file
367
web/routes/android_forensics.py
Normal file
@@ -0,0 +1,367 @@
|
||||
"""Autarch Android Forensics routes — forensic acquisition and IOC scanning."""
|
||||
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, render_template, request, jsonify
|
||||
from web.auth import login_required
|
||||
|
||||
android_forensics_bp = Blueprint('android_forensics', __name__, url_prefix='/android-forensics')
|
||||
|
||||
|
||||
def _mgr():
|
||||
from core.android_forensics import get_android_forensics_manager
|
||||
return get_android_forensics_manager()
|
||||
|
||||
|
||||
def _body():
|
||||
return request.get_json(silent=True) or {}
|
||||
|
||||
|
||||
def _serial():
|
||||
data = _body()
|
||||
serial = data.get('serial') or request.form.get('serial') or request.args.get('serial', '')
|
||||
return str(serial).strip() if serial else ''
|
||||
|
||||
|
||||
# ── Main Page ────────────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_bp.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
from core.hardware import get_hardware_manager
|
||||
hw = get_hardware_manager()
|
||||
status = hw.get_status()
|
||||
ioc_stats = _mgr().get_ioc_stats()
|
||||
return render_template('android_forensics.html', status=status, ioc_stats=ioc_stats)
|
||||
|
||||
|
||||
# ── Acquisition Routes ───────────────────────────────────────────────
|
||||
|
||||
@android_forensics_bp.route('/acquire/<module>', methods=['POST'])
|
||||
@login_required
|
||||
def acquire(module):
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
func_map = {
|
||||
'packages': _mgr().acquire_packages,
|
||||
'processes': _mgr().acquire_processes,
|
||||
'getprop': _mgr().acquire_getprop,
|
||||
'settings': _mgr().acquire_settings,
|
||||
'services': _mgr().acquire_services,
|
||||
'dumpsys': _mgr().acquire_dumpsys,
|
||||
'logcat': _mgr().acquire_logcat,
|
||||
'tmp_files': _mgr().acquire_tmp_files,
|
||||
}
|
||||
func = func_map.get(module)
|
||||
if not func:
|
||||
return jsonify({'error': f'Unknown module: {module}'})
|
||||
return jsonify(func(serial))
|
||||
|
||||
|
||||
@android_forensics_bp.route('/acquire/full', methods=['POST'])
|
||||
@login_required
|
||||
def acquire_full():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify(_mgr().full_acquisition(serial))
|
||||
|
||||
|
||||
# ── IOC Scan Routes ──────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_bp.route('/scan/apps', methods=['POST'])
|
||||
@login_required
|
||||
def scan_apps():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify(_mgr().scan_packages(serial))
|
||||
|
||||
|
||||
@android_forensics_bp.route('/scan/certs', methods=['POST'])
|
||||
@login_required
|
||||
def scan_certs():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify(_mgr().scan_certificates(serial))
|
||||
|
||||
|
||||
@android_forensics_bp.route('/scan/network', methods=['POST'])
|
||||
@login_required
|
||||
def scan_network():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify(_mgr().scan_network_indicators(serial))
|
||||
|
||||
|
||||
@android_forensics_bp.route('/scan/files', methods=['POST'])
|
||||
@login_required
|
||||
def scan_files():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify(_mgr().scan_file_indicators(serial))
|
||||
|
||||
|
||||
@android_forensics_bp.route('/scan/processes', methods=['POST'])
|
||||
@login_required
|
||||
def scan_processes():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify(_mgr().scan_process_indicators(serial))
|
||||
|
||||
|
||||
@android_forensics_bp.route('/scan/full', methods=['POST'])
|
||||
@login_required
|
||||
def scan_full():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify(_mgr().full_acquisition(serial))
|
||||
|
||||
|
||||
# ── Report Routes ────────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_bp.route('/report/list', methods=['POST'])
|
||||
@login_required
|
||||
def report_list():
|
||||
serial = _serial()
|
||||
if not serial:
|
||||
return jsonify({'error': 'No serial provided'})
|
||||
return jsonify({'ok': True, 'acquisitions': _mgr().get_acquisition_list(serial)})
|
||||
|
||||
|
||||
@android_forensics_bp.route('/report/view', methods=['POST'])
|
||||
@login_required
|
||||
def report_view():
|
||||
data = _body()
|
||||
serial = data.get('serial', '').strip()
|
||||
acq_id = data.get('acq_id', '').strip()
|
||||
if not serial or not acq_id:
|
||||
return jsonify({'error': 'serial and acq_id required'})
|
||||
return jsonify(_mgr().export_report(serial, acq_id))
|
||||
|
||||
|
||||
# ── Database Routes ──────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_bp.route('/db/stats', methods=['GET'])
|
||||
@login_required
|
||||
def db_stats():
|
||||
return jsonify(_mgr().get_ioc_stats())
|
||||
|
||||
|
||||
@android_forensics_bp.route('/db/reload', methods=['POST'])
|
||||
@login_required
|
||||
def db_reload():
|
||||
db = _mgr().reload_ioc_database()
|
||||
return jsonify({
|
||||
'ok': True,
|
||||
'counts': {k: len(v) for k, v in db.items() if isinstance(v, (dict, list))},
|
||||
})
|
||||
|
||||
|
||||
@android_forensics_bp.route('/ioc/export', methods=['GET'])
|
||||
@login_required
|
||||
def ioc_export():
|
||||
"""Compact IOC export for companion app caching."""
|
||||
db = _mgr().load_ioc_database()
|
||||
return jsonify({
|
||||
'packages': list(db.get('packages', {}).keys()),
|
||||
'domains': list(db.get('domains', {}).keys()),
|
||||
'hashes': list(db.get('hashes', {}).keys()),
|
||||
'ips': list(db.get('ips', [])),
|
||||
'generated': datetime.now().isoformat(),
|
||||
'counts': {
|
||||
'packages': len(db.get('packages', {})),
|
||||
'domains': len(db.get('domains', {})),
|
||||
'hashes': len(db.get('hashes', {})),
|
||||
'ips': len(db.get('ips', [])),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
# ── Direct (WebUSB) Mode ─────────────────────────────────────────────
|
||||
# Op keys match the JS URL→op transformation:
|
||||
# url.replace('/android-forensics/', '').replace(/\//g, '_')
|
||||
# So /acquire/packages → acquire_packages, /scan/apps → scan_apps, etc.
|
||||
|
||||
_DIRECT_COMMANDS = {
|
||||
'acquire_packages': {
|
||||
'packages': 'pm list packages -f',
|
||||
'packages_sys': 'pm list packages -s',
|
||||
},
|
||||
'acquire_processes': {
|
||||
'processes': 'ps -A',
|
||||
},
|
||||
'acquire_getprop': {
|
||||
'getprop': 'getprop',
|
||||
},
|
||||
'acquire_settings': {
|
||||
'settings_global': 'settings list global',
|
||||
'settings_secure': 'settings list secure',
|
||||
'settings_system': 'settings list system',
|
||||
},
|
||||
'acquire_services': {
|
||||
'services': 'service list',
|
||||
},
|
||||
'acquire_logcat': {
|
||||
'logcat': 'logcat -d -t 1000',
|
||||
},
|
||||
'acquire_tmp_files': {
|
||||
'tmp_files': 'ls -la /data/local/tmp/ 2>/dev/null',
|
||||
},
|
||||
'scan_apps': {
|
||||
'packages': 'pm list packages -f',
|
||||
'packages_sys': 'pm list packages -s',
|
||||
},
|
||||
'scan_network': {
|
||||
'tcp_connections': 'cat /proc/net/tcp 2>/dev/null',
|
||||
'dns1': 'getprop net.dns1',
|
||||
'dns2': 'getprop net.dns2',
|
||||
},
|
||||
'scan_processes': {
|
||||
'processes': 'ps -A',
|
||||
},
|
||||
'scan_files': {
|
||||
# file artifact scanning requires server-side IOC DB; not supported in direct mode
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@android_forensics_bp.route('/cmd', methods=['POST'])
|
||||
@login_required
|
||||
def direct_cmd():
|
||||
"""Return ADB shell commands for Direct (WebUSB) mode."""
|
||||
data = _body()
|
||||
op = data.get('op', '').replace('/', '_').replace('-', '_')
|
||||
cmds = _DIRECT_COMMANDS.get(op)
|
||||
if cmds:
|
||||
return jsonify({'commands': cmds, 'supported': True})
|
||||
return jsonify({'commands': {}, 'supported': False,
|
||||
'reason': 'This operation requires Server mode.'})
|
||||
|
||||
|
||||
@android_forensics_bp.route('/parse', methods=['POST'])
|
||||
@login_required
|
||||
def direct_parse():
|
||||
"""Parse raw ADB output from Direct (WebUSB) mode."""
|
||||
import re
|
||||
data = _body()
|
||||
op = data.get('op', '').replace('/', '_').replace('-', '_')
|
||||
raw = data.get('raw', {})
|
||||
mgr = _mgr()
|
||||
|
||||
def _parse_packages(pkg_output, sys_output=''):
|
||||
sys_pkgs = set()
|
||||
for line in (sys_output or '').splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith('package:'):
|
||||
sys_pkgs.add(line[8:].strip())
|
||||
pkgs = []
|
||||
for line in (pkg_output or '').splitlines():
|
||||
line = line.strip()
|
||||
if not line.startswith('package:'):
|
||||
continue
|
||||
line = line[8:]
|
||||
eq_pos = line.rfind('=')
|
||||
if eq_pos == -1:
|
||||
pkg_name, apk_path = line, ''
|
||||
else:
|
||||
apk_path, pkg_name = line[:eq_pos], line[eq_pos + 1:]
|
||||
pkg_name = pkg_name.strip()
|
||||
if pkg_name:
|
||||
pkgs.append({'package': pkg_name, 'path': apk_path.strip(),
|
||||
'system': pkg_name in sys_pkgs})
|
||||
return pkgs
|
||||
|
||||
try:
|
||||
if op in ('acquire_packages',):
|
||||
pkgs = _parse_packages(raw.get('packages', ''), raw.get('packages_sys', ''))
|
||||
return jsonify({'ok': True, 'packages': pkgs, 'total': len(pkgs)})
|
||||
|
||||
elif op == 'scan_apps':
|
||||
pkgs = _parse_packages(raw.get('packages', ''), raw.get('packages_sys', ''))
|
||||
return jsonify(mgr.scan_packages(None, packages_data=pkgs))
|
||||
|
||||
elif op == 'acquire_processes':
|
||||
lines = [l for l in (raw.get('processes', '') or '').splitlines() if l.strip()]
|
||||
return jsonify({'ok': True, 'output': raw.get('processes', ''),
|
||||
'count': max(0, len(lines) - 1)})
|
||||
|
||||
elif op == 'acquire_getprop':
|
||||
props = {}
|
||||
for line in (raw.get('getprop', '') or '').splitlines():
|
||||
m = re.match(r'\[(.+)\]:\s*\[(.*)\]\s*$', line)
|
||||
if m:
|
||||
props[m.group(1)] = m.group(2)
|
||||
return jsonify({'ok': True, 'props': props})
|
||||
|
||||
elif op == 'acquire_settings':
|
||||
result = {}
|
||||
for ns in ('global', 'secure', 'system'):
|
||||
settings = {}
|
||||
for line in (raw.get(f'settings_{ns}', '') or '').splitlines():
|
||||
if '=' in line:
|
||||
k, _, v = line.partition('=')
|
||||
settings[k.strip()] = v.strip()
|
||||
result[ns] = settings
|
||||
return jsonify({'ok': True, 'settings': result})
|
||||
|
||||
elif op in ('acquire_services', 'acquire_logcat', 'acquire_tmp_files'):
|
||||
key = op.replace('acquire_', '')
|
||||
out = raw.get(key, '')
|
||||
return jsonify({'ok': True, 'output': out, 'lines': len((out or '').splitlines())})
|
||||
|
||||
elif op == 'scan_network':
|
||||
db = mgr.load_ioc_database()
|
||||
findings = []
|
||||
ioc_ips = set(db.get('ips', []))
|
||||
# Check DNS properties
|
||||
for dns_key in ('dns1', 'dns2'):
|
||||
val = (raw.get(dns_key, '') or '').strip().lower()
|
||||
if val and val in db.get('domains', {}):
|
||||
match = db['domains'][val]
|
||||
findings.append({'type': 'dns_property', 'key': dns_key, 'value': val,
|
||||
'threat_name': match['name'], 'source': match['source'],
|
||||
'severity': 'high'})
|
||||
# Check TCP connections
|
||||
if ioc_ips:
|
||||
for line in (raw.get('tcp_connections', '') or '').splitlines()[1:]:
|
||||
parts = line.strip().split()
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
try:
|
||||
remote_hex = parts[2]
|
||||
ip_hex, port_hex = remote_hex.split(':')
|
||||
ip = '.'.join(str(int(ip_hex[i:i+2], 16)) for i in (6, 4, 2, 0))
|
||||
port = int(port_hex, 16)
|
||||
if ip not in ('0.0.0.0', '127.0.0.1') and ip in ioc_ips:
|
||||
findings.append({'type': 'tcp_connection', 'remote_ip': ip,
|
||||
'remote_port': port, 'severity': 'critical',
|
||||
'threat_name': 'Known malicious IP',
|
||||
'source': 'network_scan'})
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify({'ok': True, 'findings': findings, 'clean': len(findings) == 0})
|
||||
|
||||
elif op == 'scan_processes':
|
||||
db = mgr.load_ioc_database()
|
||||
proc_output = (raw.get('processes', '') or '').lower()
|
||||
findings = []
|
||||
for proc_name, match in db.get('processes', {}).items():
|
||||
if proc_name and proc_name in proc_output:
|
||||
findings.append({'type': 'process_match', 'process': proc_name,
|
||||
'threat_name': match['name'], 'source': match['source'],
|
||||
'severity': 'critical'})
|
||||
return jsonify({'ok': True, 'findings': findings, 'clean': len(findings) == 0})
|
||||
|
||||
else:
|
||||
key = op.replace('acquire_', '')
|
||||
out = raw.get(key, '') if isinstance(raw, dict) else ''
|
||||
return jsonify({'ok': True, 'output': out})
|
||||
|
||||
except Exception as exc:
|
||||
return jsonify({'error': str(exc)})
|
||||
314
web/routes/android_forensics_ai.py
Normal file
314
web/routes/android_forensics_ai.py
Normal file
@@ -0,0 +1,314 @@
|
||||
"""Autarch Android Forensics AI — threat analysis, CVE lookup, heuristic scanning, and alert receiver."""
|
||||
|
||||
from datetime import datetime
|
||||
from flask import Blueprint, request, jsonify
|
||||
from web.auth import login_required
|
||||
|
||||
android_forensics_ai_bp = Blueprint('android_forensics_ai', __name__, url_prefix='/android-forensics/ai')
|
||||
|
||||
_alerts = [] # in-memory alert store (last 500)
|
||||
_MAX_ALERTS = 500
|
||||
|
||||
|
||||
def _mgr():
|
||||
from core.android_forensics import get_android_forensics_manager
|
||||
return get_android_forensics_manager()
|
||||
|
||||
|
||||
def _cve():
|
||||
from core.cve_db import get_cve_db
|
||||
return get_cve_db()
|
||||
|
||||
|
||||
def _llm():
|
||||
from core.llm import get_llm
|
||||
return get_llm()
|
||||
|
||||
|
||||
def _body():
|
||||
return request.get_json(silent=True) or {}
|
||||
|
||||
|
||||
# ── AI Analysis ──────────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_ai_bp.route('/analyze', methods=['POST'])
|
||||
@login_required
|
||||
def analyze():
|
||||
data = _body()
|
||||
serial = data.get('serial', '').strip()
|
||||
acq_id = data.get('acq_id', '').strip()
|
||||
backend = data.get('backend', 'autarch')
|
||||
focus = data.get('focus', ['all'])
|
||||
|
||||
try:
|
||||
# Load acquisition report from disk, fall back to live acquisition
|
||||
report = None
|
||||
if serial and acq_id:
|
||||
exported = _mgr().export_report(serial, acq_id)
|
||||
report = exported.get('report') if exported else None
|
||||
|
||||
if not report:
|
||||
report = _mgr().full_acquisition(serial) if serial else {}
|
||||
|
||||
# Build context dict from report
|
||||
packages = report.get('packages', [])
|
||||
processes = report.get('processes', {})
|
||||
proc_output = processes.get('output', '') if isinstance(processes, dict) else str(processes)
|
||||
proc_lines = proc_output.splitlines()[:50]
|
||||
|
||||
props = report.get('getprop', {})
|
||||
model = props.get('ro.product.model', 'Unknown') if isinstance(props, dict) else 'Unknown'
|
||||
android_version = props.get('ro.build.version.release', 'Unknown') if isinstance(props, dict) else 'Unknown'
|
||||
|
||||
network_info = report.get('network', {})
|
||||
ioc_findings = report.get('ioc_findings', report.get('findings', []))
|
||||
|
||||
pkg_list = packages[:100] if isinstance(packages, list) else []
|
||||
user_pkgs = [p for p in pkg_list if not p.get('system', False)] if pkg_list and isinstance(pkg_list[0], dict) else pkg_list
|
||||
pkg_names = [p.get('package', str(p)) if isinstance(p, dict) else str(p) for p in pkg_list]
|
||||
|
||||
context_dict = {
|
||||
'model': model,
|
||||
'android_version': android_version,
|
||||
'pkg_count': len(pkg_list),
|
||||
'user_count': len(user_pkgs),
|
||||
'pkg_sample': ', '.join(pkg_names[:20]),
|
||||
'proc_sample': '\n'.join(proc_lines),
|
||||
'network_info': str(network_info)[:500],
|
||||
'ioc_findings': str(ioc_findings)[:500],
|
||||
}
|
||||
|
||||
if backend == 'raw':
|
||||
return jsonify({'ok': True, 'mode': 'raw', 'dump': context_dict})
|
||||
|
||||
if backend == 'ondevice':
|
||||
prompt_str = (
|
||||
f"Device: {context_dict['model']} Android {context_dict['android_version']}. "
|
||||
f"Packages ({context_dict['pkg_count']}): {context_dict['pkg_sample']}. "
|
||||
f"Processes: {context_dict['proc_sample'][:200]}. "
|
||||
f"Network: {context_dict['network_info'][:200]}. "
|
||||
f"IOC findings: {context_dict['ioc_findings'][:200]}. "
|
||||
"Identify threats and return risk assessment."
|
||||
)
|
||||
return jsonify({
|
||||
'ok': True,
|
||||
'mode': 'ondevice',
|
||||
'prompt': prompt_str,
|
||||
'schema': {
|
||||
'risk_level': 'string',
|
||||
'findings': 'array',
|
||||
'summary': 'string',
|
||||
},
|
||||
})
|
||||
|
||||
# autarch backend — full LLM analysis
|
||||
prompt = (
|
||||
"You are a mobile security analyst. Analyze this Android forensic acquisition and identify threats.\n\n"
|
||||
f"Device info: {context_dict['model']} Android {context_dict['android_version']}\n"
|
||||
f"Installed packages ({context_dict['pkg_count']} total, {context_dict['user_count']} user apps): {context_dict['pkg_sample']}\n"
|
||||
f"Running processes (sample): {context_dict['proc_sample']}\n"
|
||||
f"Network properties: {context_dict['network_info']}\n"
|
||||
f"IOC pre-scan findings: {context_dict['ioc_findings']}\n\n"
|
||||
'Return JSON: {"risk_level": "critical|high|medium|low|clean", '
|
||||
'"findings": [{"severity": "...", "type": "...", "detail": "...", "recommendation": "..."}], '
|
||||
'"summary": "one paragraph"}'
|
||||
)
|
||||
|
||||
response = _llm().generate(prompt, max_tokens=1000)
|
||||
try:
|
||||
import json
|
||||
parsed_result = json.loads(response)
|
||||
except Exception:
|
||||
parsed_result = response
|
||||
|
||||
return jsonify({'ok': True, 'mode': 'autarch', 'analysis': parsed_result})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'ok': False, 'error': str(e)})
|
||||
|
||||
|
||||
# ── CVE Check ────────────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_ai_bp.route('/cve-check', methods=['POST'])
|
||||
@login_required
|
||||
def cve_check():
|
||||
data = _body()
|
||||
packages = data.get('packages', [])
|
||||
sdk_version = data.get('sdk_version')
|
||||
|
||||
try:
|
||||
_severity_order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3, 'unknown': 4}
|
||||
|
||||
findings = []
|
||||
|
||||
pkg_results = _cve().check_packages_batch(packages)
|
||||
for pkg_name, cves in (pkg_results or {}).items():
|
||||
for cve in (cves or []):
|
||||
findings.append({
|
||||
'package': pkg_name,
|
||||
'cve_id': cve.get('cve_id', ''),
|
||||
'severity': cve.get('severity', 'unknown'),
|
||||
'description': cve.get('description', ''),
|
||||
'patched_version': cve.get('patched_version', ''),
|
||||
'source': cve.get('source', ''),
|
||||
})
|
||||
|
||||
if sdk_version:
|
||||
sdk_cves = _cve().get_cve_for_android_sdk(sdk_version) or []
|
||||
for cve in sdk_cves:
|
||||
findings.append({
|
||||
'package': f'android-sdk:{sdk_version}',
|
||||
'cve_id': cve.get('cve_id', ''),
|
||||
'severity': cve.get('severity', 'unknown'),
|
||||
'description': cve.get('description', ''),
|
||||
'patched_version': cve.get('patched_version', ''),
|
||||
'source': cve.get('source', ''),
|
||||
})
|
||||
|
||||
findings.sort(key=lambda f: _severity_order.get(f['severity'].lower(), 4))
|
||||
|
||||
return jsonify({
|
||||
'ok': True,
|
||||
'findings': findings,
|
||||
'total': len(findings),
|
||||
'packages_checked': len(packages),
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'ok': False, 'error': str(e)})
|
||||
|
||||
|
||||
# ── Classify ─────────────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_ai_bp.route('/classify', methods=['POST'])
|
||||
@login_required
|
||||
def classify():
|
||||
data = _body()
|
||||
raw_output = data.get('raw_output', '')
|
||||
data_type = data.get('type', 'packages')
|
||||
|
||||
try:
|
||||
_type_context = {
|
||||
'packages': 'a list of installed Android packages',
|
||||
'logcat': 'Android logcat output',
|
||||
'processes': 'a list of running Android processes',
|
||||
'getprop': 'Android system properties (getprop output)',
|
||||
}
|
||||
context_label = _type_context.get(data_type, f'Android {data_type} data')
|
||||
|
||||
prompt = (
|
||||
f"You are a mobile security analyst. Classify the following {context_label} "
|
||||
"for security threats, stalkerware, spyware, adware, or other malicious indicators. "
|
||||
"Provide a brief threat classification summary.\n\n"
|
||||
f"Data:\n{raw_output[:3000]}"
|
||||
)
|
||||
|
||||
response = _llm().generate(prompt, max_tokens=500)
|
||||
return jsonify({'ok': True, 'classification': response})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'ok': False, 'error': str(e)})
|
||||
|
||||
|
||||
# ── Heuristic Scan ───────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_ai_bp.route('/heuristic', methods=['POST'])
|
||||
@login_required
|
||||
def heuristic():
|
||||
data = _body()
|
||||
data_type = data.get('data_type', 'packages')
|
||||
items = data.get('items', [])
|
||||
threshold = float(data.get('threshold', 0.6))
|
||||
|
||||
_prompts = {
|
||||
'packages': (
|
||||
'Classify each Android package name for stalkerware/spyware/adware risk. '
|
||||
'Score 0.0-1.0. High risk: names impersonating system apps, random strings, MDM-sounding names. '
|
||||
'Return JSON array: [{"item": "...", "score": 0.0, "reason": "...", "category": "stalkerware|adware|suspicious|clean"}]'
|
||||
),
|
||||
'processes': (
|
||||
'Classify each process name for malware risk. Score 0.0-1.0. '
|
||||
'Suspicious: random strings, names mimicking system processes. '
|
||||
'Return JSON array: [{"item": "...", "score": 0.0, "reason": "...", "category": "malware|suspicious|clean"}]'
|
||||
),
|
||||
'network': (
|
||||
'Classify each domain/IP for C2/malware beacon risk. Score 0.0-1.0. '
|
||||
'High risk: DGA patterns, dynamic DNS abuse, unusual TLDs. '
|
||||
'Return JSON array: [{"item": "...", "score": 0.0, "reason": "...", "category": "c2_beacon|dga_domain|suspicious|clean"}]'
|
||||
),
|
||||
'files': (
|
||||
'Classify each file path for spyware/stalkerware artifact risk. Score 0.0-1.0. '
|
||||
'Return JSON array: [{"item": "...", "score": 0.0, "reason": "...", "category": "spyware|suspicious|clean"}]'
|
||||
),
|
||||
}
|
||||
|
||||
try:
|
||||
llm = _llm()
|
||||
if llm is None:
|
||||
return jsonify({'ok': False, 'error': 'LLM not loaded', 'findings': []})
|
||||
|
||||
base_prompt = _prompts.get(data_type, _prompts['packages'])
|
||||
chunk_size = 50
|
||||
all_results = []
|
||||
|
||||
for i in range(0, len(items), chunk_size):
|
||||
chunk = items[i:i + chunk_size]
|
||||
chunk_text = '\n'.join(str(it) for it in chunk)
|
||||
prompt = f"{base_prompt}\n\nItems:\n{chunk_text}"
|
||||
|
||||
try:
|
||||
response = llm.generate(prompt, max_tokens=1000)
|
||||
import json
|
||||
parsed = json.loads(response)
|
||||
if isinstance(parsed, list):
|
||||
all_results.extend(parsed)
|
||||
except Exception:
|
||||
# Skip chunks that fail to parse
|
||||
continue
|
||||
|
||||
filtered = [r for r in all_results if isinstance(r, dict) and float(r.get('score', 0)) >= threshold]
|
||||
|
||||
return jsonify({
|
||||
'ok': True,
|
||||
'findings': filtered,
|
||||
'total_scanned': len(items),
|
||||
'flagged': len(filtered),
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'ok': False, 'error': str(e), 'findings': []})
|
||||
|
||||
|
||||
# ── Alert Receiver ───────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_ai_bp.route('/alert', methods=['POST'])
|
||||
@login_required
|
||||
def alert():
|
||||
data = _body()
|
||||
entry = {
|
||||
'serial': data.get('serial', ''),
|
||||
'event_type': data.get('event_type', ''),
|
||||
'severity': data.get('severity', ''),
|
||||
'detail': data.get('detail', ''),
|
||||
'timestamp': data.get('timestamp', datetime.utcnow().isoformat()),
|
||||
}
|
||||
_alerts.append(entry)
|
||||
if len(_alerts) > _MAX_ALERTS:
|
||||
del _alerts[:-_MAX_ALERTS]
|
||||
return jsonify({'ok': True, 'alert_id': len(_alerts)})
|
||||
|
||||
|
||||
# ── Alerts List ──────────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_ai_bp.route('/alerts/list', methods=['GET'])
|
||||
@login_required
|
||||
def alerts_list():
|
||||
return jsonify({'ok': True, 'alerts': _alerts[-100:], 'total': len(_alerts)})
|
||||
|
||||
|
||||
# ── CVE Stats ────────────────────────────────────────────────────────
|
||||
|
||||
@android_forensics_ai_bp.route('/cve-stats', methods=['GET'])
|
||||
@login_required
|
||||
def cve_stats():
|
||||
return jsonify({'ok': True, 'stats': _cve().get_stats()})
|
||||
@@ -6,56 +6,29 @@ from web.auth import check_password, hash_password, load_credentials, save_crede
|
||||
auth_bp = Blueprint('auth', __name__)
|
||||
|
||||
|
||||
# Login is disabled — AutarchOS is a single-user appliance. The endpoints
|
||||
# below are kept so existing url_for('auth.*') calls and the companion app
|
||||
# API still resolve; they auto-grant a session.
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if 'user' in session:
|
||||
return redirect(url_for('dashboard.index'))
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.form.get('username', '')
|
||||
password = request.form.get('password', '')
|
||||
creds = load_credentials()
|
||||
|
||||
if username == creds['username'] and check_password(password, creds['password']):
|
||||
session['user'] = username
|
||||
if creds.get('force_change'):
|
||||
flash('Please change the default password.', 'warning')
|
||||
return redirect(url_for('settings.index'))
|
||||
next_url = request.args.get('next', url_for('dashboard.index'))
|
||||
return redirect(next_url)
|
||||
else:
|
||||
flash('Invalid credentials.', 'error')
|
||||
|
||||
return render_template('login.html')
|
||||
session['user'] = 'admin'
|
||||
next_url = request.args.get('next') or url_for('dashboard.index')
|
||||
return redirect(next_url)
|
||||
|
||||
|
||||
@auth_bp.route('/api/login', methods=['POST'])
|
||||
def api_login():
|
||||
"""JSON login endpoint for the companion app."""
|
||||
data = request.get_json(silent=True) or {}
|
||||
username = data.get('username', '')
|
||||
password = data.get('password', '')
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({'ok': False, 'error': 'Missing username or password'}), 400
|
||||
|
||||
creds = load_credentials()
|
||||
if username == creds['username'] and check_password(password, creds['password']):
|
||||
session['user'] = username
|
||||
return jsonify({'ok': True, 'user': username})
|
||||
else:
|
||||
return jsonify({'ok': False, 'error': 'Invalid credentials'}), 401
|
||||
session['user'] = 'admin'
|
||||
return jsonify({'ok': True, 'user': 'admin'})
|
||||
|
||||
|
||||
@auth_bp.route('/api/check', methods=['GET'])
|
||||
def api_check():
|
||||
"""Check if the current session is authenticated."""
|
||||
if 'user' in session:
|
||||
return jsonify({'ok': True, 'user': session['user']})
|
||||
return jsonify({'ok': False}), 401
|
||||
return jsonify({'ok': True, 'user': session.get('user', 'admin')})
|
||||
|
||||
|
||||
@auth_bp.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for('auth.login'))
|
||||
# No-op: logout makes no sense without a login wall. Bounce home.
|
||||
return redirect(url_for('dashboard.index'))
|
||||
|
||||
@@ -128,7 +128,7 @@ def _handle_agent_chat(message):
|
||||
return
|
||||
|
||||
tools = get_tool_registry()
|
||||
agent = Agent(llm=llm, tools=tools, max_steps=20, verbose=False)
|
||||
agent = Agent(llm=llm, tools=tools, max_steps=200, verbose=False)
|
||||
|
||||
# Inject system prompt — keep the THOUGHT/ACTION/PARAMS format from Agent,
|
||||
# prepend with our behavioral rules
|
||||
|
||||
537
web/static/css/autarchos.css
Normal file
537
web/static/css/autarchos.css
Normal file
@@ -0,0 +1,537 @@
|
||||
/* ================================================================
|
||||
* AutarchOS — desktop chrome + design system
|
||||
* Loaded AFTER style.css; cascades over and re-themes the platform.
|
||||
* ================================================================ */
|
||||
|
||||
:root {
|
||||
/* New AutarchOS palette */
|
||||
--bg: #05070a;
|
||||
--panel: #0b1118;
|
||||
--panel2: #0e161e;
|
||||
--glass: rgba(11,17,24,.72);
|
||||
--line: rgba(61,240,192,.16);
|
||||
--line2: rgba(61,240,192,.07);
|
||||
--text: #cdd9d6;
|
||||
--dim: #5d716e;
|
||||
--dimmer: #3a4845;
|
||||
--phos: #3df0c0;
|
||||
--phos-d: #1d9c80;
|
||||
--mag: #ff3d81;
|
||||
--amber: #ffb13d;
|
||||
--red: #ff4d57;
|
||||
--blue: #52b6ff;
|
||||
--glow: 0 0 18px rgba(61,240,192,.35);
|
||||
|
||||
/* Re-map legacy style.css variables to the new palette so all
|
||||
* existing 79 templates inherit the AutarchOS theme automatically.
|
||||
* The legacy names stay defined for backward compatibility. */
|
||||
--bg-primary: var(--bg);
|
||||
--bg-secondary: var(--panel);
|
||||
--bg-card: var(--panel2);
|
||||
--bg-input: var(--bg);
|
||||
--border: var(--line2);
|
||||
--text-primary: var(--text);
|
||||
--text-secondary: var(--dim);
|
||||
--text-muted: var(--dimmer);
|
||||
--accent: var(--phos);
|
||||
--accent-hover: #6cf4cd;
|
||||
--primary: var(--phos);
|
||||
--surface: var(--panel2);
|
||||
--success: var(--phos);
|
||||
--warning: var(--amber);
|
||||
--danger: var(--red);
|
||||
--danger-hover: #ff6b73;
|
||||
--defense: var(--blue);
|
||||
--offense: var(--red);
|
||||
--counter: var(--mag);
|
||||
--analyze: var(--phos);
|
||||
--osint: #76e0b4;
|
||||
--simulate: var(--amber);
|
||||
--hardware: #ffa05a;
|
||||
--radius: 6px;
|
||||
|
||||
--font-disp: 'Chakra Petch', 'Segoe UI', system-ui, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
/* ── Base type & background ─────────────────────────────────────── */
|
||||
|
||||
body {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12.5px;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
background-image:
|
||||
radial-gradient(1100px 700px at 78% -8%, rgba(61,240,192,.08), transparent 58%),
|
||||
radial-gradient(900px 600px at 8% 108%, rgba(255,61,129,.06), transparent 60%),
|
||||
radial-gradient(700px 500px at 50% 50%, rgba(82,182,255,.03), transparent 70%);
|
||||
}
|
||||
|
||||
::selection { background: var(--phos); color: #04110d; }
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-thumb{ background: var(--line); border-radius: 4px; }
|
||||
::-webkit-scrollbar-track{ background: transparent; }
|
||||
|
||||
h1, h2, h3, h4, h5, h6 { font-family: var(--font-disp); letter-spacing: .04em; }
|
||||
|
||||
/* Neutralize the legacy sidebar layout in case any template still ships it. */
|
||||
.layout { display: block; }
|
||||
.layout > .sidebar { display: none !important; }
|
||||
.layout > .content { margin-left: 0; max-width: none; padding: 0; }
|
||||
|
||||
/* ── Desktop substrate (grid + scanlines + watermark) ──────────── */
|
||||
|
||||
.desktop-bg {
|
||||
position: fixed; inset: 30px 0 64px 0; z-index: 0; pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(rgba(61,240,192,.025) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(61,240,192,.025) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
}
|
||||
.desktop-bg::after {
|
||||
content: ""; position: absolute; inset: 0;
|
||||
background: repeating-linear-gradient(0deg, transparent 0 2px,
|
||||
rgba(0,0,0,.16) 2px 3px);
|
||||
mix-blend-mode: multiply; opacity: .35;
|
||||
}
|
||||
.watermark {
|
||||
position: fixed; right: 6%; bottom: 12%;
|
||||
font-family: var(--font-disp); font-weight: 700; font-size: 200px;
|
||||
color: rgba(61,240,192,.025); letter-spacing: .05em;
|
||||
pointer-events: none; user-select: none; z-index: 0;
|
||||
}
|
||||
|
||||
/* ── Top system bar ────────────────────────────────────────────── */
|
||||
|
||||
.aos-topbar {
|
||||
position: fixed; top: 0; left: 0; right: 0; height: 30px; z-index: 9000;
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
|
||||
border-bottom: 1px solid var(--line2);
|
||||
display: flex; align-items: center; gap: 18px;
|
||||
padding: 0 14px; font-size: 11.5px;
|
||||
}
|
||||
.aos-topbar .logo {
|
||||
font-family: var(--font-disp); font-weight: 700; letter-spacing: .06em;
|
||||
color: #fff; display: flex; align-items: center; gap: 7px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.aos-topbar .logo b { color: var(--phos); }
|
||||
.aos-topbar .mk {
|
||||
width: 16px; height: 16px; border: 1px solid var(--phos);
|
||||
display: grid; place-items: center; color: var(--phos); font-size: 11px;
|
||||
box-shadow: var(--glow);
|
||||
}
|
||||
.aos-topbar .mi {
|
||||
color: var(--dim); cursor: pointer; transition: .15s; text-decoration: none;
|
||||
}
|
||||
.aos-topbar .mi:hover { color: var(--text); }
|
||||
.aos-topbar .sp { flex: 1; }
|
||||
.aos-topbar .posture {
|
||||
display: flex; align-items: center; gap: 7px; color: var(--amber);
|
||||
border: 1px solid rgba(255,177,61,.35);
|
||||
padding: 2px 9px; font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
|
||||
}
|
||||
.aos-topbar .tray { display: flex; align-items: center; gap: 14px; color: var(--dim); }
|
||||
.aos-topbar .tray svg { width: 15px; height: 15px; }
|
||||
.aos-topbar .tray .shield { color: var(--phos); }
|
||||
.aos-topbar .clock {
|
||||
color: var(--text); letter-spacing: .04em; min-width: 64px; text-align: right;
|
||||
}
|
||||
.aos-topbar .user {
|
||||
color: var(--dim); font-size: 10.5px; letter-spacing: .04em;
|
||||
}
|
||||
.aos-topbar .user b { color: var(--text); }
|
||||
.aos-topbar .user a { color: var(--mag); margin-left: 8px; text-decoration: none; }
|
||||
.aos-topbar .user a:hover { text-shadow: 0 0 10px rgba(255,61,129,.5); }
|
||||
|
||||
.pulse {
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
background: var(--phos); box-shadow: var(--glow);
|
||||
animation: aos-pulse 1.6s infinite; display: inline-block;
|
||||
}
|
||||
@keyframes aos-pulse {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: .4; transform: scale(.65); }
|
||||
}
|
||||
|
||||
/* ── Workspace (where {% block content %} lives) ──────────────── */
|
||||
|
||||
.aos-workspace {
|
||||
position: relative; z-index: 1;
|
||||
margin-top: 30px; /* clear topbar */
|
||||
margin-bottom: 64px; /* clear dock */
|
||||
padding: 20px 28px;
|
||||
min-height: calc(100vh - 30px - 64px);
|
||||
}
|
||||
|
||||
/* Flash messages re-skin */
|
||||
.flash-messages { margin-bottom: 14px; }
|
||||
.flash {
|
||||
border: 1px solid var(--line2);
|
||||
background: var(--panel2);
|
||||
color: var(--text);
|
||||
padding: 10px 14px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11.5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.flash-success { border-left: 3px solid var(--phos); }
|
||||
.flash-error,
|
||||
.flash-danger { border-left: 3px solid var(--red); }
|
||||
.flash-warning { border-left: 3px solid var(--amber); }
|
||||
.flash-info { border-left: 3px solid var(--blue); }
|
||||
|
||||
/* ── Dock ──────────────────────────────────────────────────────── */
|
||||
|
||||
.aos-dock {
|
||||
position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%);
|
||||
z-index: 9000;
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 8px 10px;
|
||||
background: var(--glass);
|
||||
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
||||
border: 1px solid var(--line2);
|
||||
}
|
||||
.aos-dock .app {
|
||||
width: 42px; height: 42px;
|
||||
display: grid; place-items: center;
|
||||
color: var(--dim);
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer; position: relative;
|
||||
transition: .16s; text-decoration: none;
|
||||
}
|
||||
.aos-dock .app svg { width: 21px; height: 21px; }
|
||||
.aos-dock .app:hover {
|
||||
color: var(--phos); transform: translateY(-5px);
|
||||
border-color: var(--line); background: rgba(61,240,192,.05);
|
||||
}
|
||||
.aos-dock .app.active { color: var(--text); }
|
||||
.aos-dock .app.active::after {
|
||||
content: ""; position: absolute; bottom: -3px; left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 4px; height: 4px; border-radius: 50%;
|
||||
background: var(--phos); box-shadow: var(--glow);
|
||||
}
|
||||
.aos-dock .app .tip {
|
||||
position: absolute; bottom: 52px; left: 50%; transform: translateX(-50%);
|
||||
background: var(--panel); border: 1px solid var(--line);
|
||||
padding: 3px 8px; font-size: 9.5px; letter-spacing: .08em;
|
||||
text-transform: uppercase; color: var(--phos); white-space: nowrap;
|
||||
opacity: 0; pointer-events: none; transition: .15s;
|
||||
}
|
||||
.aos-dock .app:hover .tip { opacity: 1; bottom: 48px; }
|
||||
.aos-dock .divider { width: 1px; height: 26px; background: var(--line2); margin: 0 4px; }
|
||||
|
||||
/* ── Apps launcher modal ───────────────────────────────────────── */
|
||||
|
||||
.aos-launcher-backdrop {
|
||||
position: fixed; inset: 0; z-index: 9500;
|
||||
background: rgba(5,7,10,.72);
|
||||
backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
|
||||
display: none; align-items: flex-start; justify-content: center;
|
||||
padding-top: 70px;
|
||||
}
|
||||
.aos-launcher-backdrop.open { display: flex; }
|
||||
.aos-launcher {
|
||||
width: min(1100px, 92vw); max-height: 78vh; overflow: auto;
|
||||
background: var(--panel); border: 1px solid var(--line);
|
||||
box-shadow: 0 30px 90px rgba(0,0,0,.7), 0 0 30px rgba(61,240,192,.08);
|
||||
padding: 18px 22px 22px;
|
||||
}
|
||||
.aos-launcher h2 {
|
||||
font-family: var(--font-disp); font-weight: 700; letter-spacing: .08em;
|
||||
font-size: 14px; color: var(--phos); text-transform: uppercase;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.aos-launcher .search {
|
||||
width: 100%; background: var(--bg); border: 1px solid var(--line);
|
||||
color: var(--text); padding: 9px 12px; font-family: var(--font-mono);
|
||||
font-size: 12px; outline: none; margin-bottom: 14px;
|
||||
}
|
||||
.aos-launcher .group { margin-bottom: 14px; }
|
||||
.aos-launcher .group-title {
|
||||
font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase;
|
||||
color: var(--dimmer); margin: 4px 0 8px;
|
||||
}
|
||||
.aos-launcher .apps {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
.aos-launcher .app-card {
|
||||
border: 1px solid var(--line2);
|
||||
background: var(--panel2);
|
||||
padding: 10px 12px;
|
||||
font-size: 11.5px;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
display: flex; align-items: center; gap: 9px;
|
||||
transition: .12s;
|
||||
}
|
||||
.aos-launcher .app-card:hover {
|
||||
border-color: var(--line); background: rgba(61,240,192,.04);
|
||||
color: var(--phos);
|
||||
}
|
||||
.aos-launcher .app-card .ico {
|
||||
width: 22px; height: 22px; flex-shrink: 0;
|
||||
display: grid; place-items: center; color: var(--phos);
|
||||
}
|
||||
.aos-launcher .app-card .ico svg { width: 16px; height: 16px; }
|
||||
.aos-launcher .app-card .meta { display: flex; flex-direction: column; min-width: 0; }
|
||||
.aos-launcher .app-card .meta b { color: var(--text); font-weight: 600; }
|
||||
.aos-launcher .app-card .meta small {
|
||||
color: var(--dim); font-size: 9.5px; letter-spacing: .03em;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ── Mockup component library ─────────────────────────────────── */
|
||||
|
||||
.kpis {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 10px; margin-bottom: 14px;
|
||||
}
|
||||
.kpi {
|
||||
border: 1px solid var(--line2); background: var(--panel2);
|
||||
padding: 12px; transition: .12s;
|
||||
}
|
||||
.kpi:hover { border-color: var(--line); }
|
||||
.kpi .l {
|
||||
font-size: 9px; letter-spacing: .16em; text-transform: uppercase; color: var(--dim);
|
||||
}
|
||||
.kpi .n {
|
||||
font-family: var(--font-disp); font-weight: 700; font-size: 28px;
|
||||
color: #fff; margin-top: 4px; line-height: 1;
|
||||
}
|
||||
.kpi .n small { font-size: 13px; color: var(--phos); }
|
||||
.kpi.danger .n { color: var(--red); }
|
||||
.kpi.warn .n { color: var(--amber); }
|
||||
|
||||
.sec {
|
||||
font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase;
|
||||
color: var(--dimmer); margin: 6px 0 10px;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.sec .live {
|
||||
margin-left: auto; color: var(--phos);
|
||||
display: flex; align-items: center; gap: 5px;
|
||||
}
|
||||
|
||||
.log { font-size: 11px; line-height: 1.85; }
|
||||
.log .row {
|
||||
display: flex; gap: 9px; white-space: nowrap; overflow: hidden;
|
||||
}
|
||||
.log .t { color: var(--dimmer); }
|
||||
.log .lv { width: 44px; flex-shrink: 0; }
|
||||
.log .lv.i { color: var(--blue); }
|
||||
.log .lv.w { color: var(--amber); }
|
||||
.log .lv.c { color: var(--red); }
|
||||
.log .lv.o { color: var(--phos); }
|
||||
.log .m { color: var(--dim); text-overflow: ellipsis; overflow: hidden; }
|
||||
.log .m b { color: var(--text); }
|
||||
|
||||
.cursor {
|
||||
display: inline-block; width: 6px; height: 12px;
|
||||
background: var(--phos); animation: aos-blink 1s steps(1) infinite;
|
||||
vertical-align: middle; margin-top: 6px;
|
||||
}
|
||||
@keyframes aos-blink { 50% { opacity: 0; } }
|
||||
|
||||
.spark {
|
||||
display: flex; align-items: flex-end; gap: 2px; height: 64px; margin-top: 6px;
|
||||
}
|
||||
.spark i {
|
||||
flex: 1; background: linear-gradient(180deg, var(--phos), var(--phos-d));
|
||||
opacity: .85;
|
||||
}
|
||||
.spark i.hot { background: linear-gradient(180deg, var(--mag), #a01f4e); }
|
||||
|
||||
.insight {
|
||||
border: 1px solid rgba(61,240,192,.2);
|
||||
background: linear-gradient(135deg, rgba(61,240,192,.05), transparent);
|
||||
padding: 12px; margin-top: 14px;
|
||||
font-size: 11.5px; line-height: 1.6;
|
||||
}
|
||||
.insight .ih {
|
||||
color: var(--phos); font-size: 9.5px; letter-spacing: .12em;
|
||||
text-transform: uppercase; margin-bottom: 7px;
|
||||
display: flex; align-items: center; gap: 7px;
|
||||
}
|
||||
.insight b { color: var(--phos); }
|
||||
|
||||
.inc {
|
||||
border: 1px solid var(--line2); background: var(--panel2);
|
||||
padding: 10px 12px; margin-bottom: 8px; cursor: pointer; transition: .12s;
|
||||
}
|
||||
.inc:hover { border-color: var(--line); }
|
||||
.inc.open { border-color: var(--line); background: rgba(61,240,192,.04); }
|
||||
.inc .top { display: flex; align-items: center; gap: 10px; }
|
||||
.inc .id { font-size: 11.5px; color: var(--text); }
|
||||
.inc .id b { color: #fff; }
|
||||
.inc .age { margin-left: auto; font-size: 10px; color: var(--dimmer); }
|
||||
.inc .body {
|
||||
display: none; margin-top: 9px;
|
||||
font-size: 11px; color: var(--dim); line-height: 1.7;
|
||||
}
|
||||
.inc.open .body { display: block; }
|
||||
.rec {
|
||||
border-left: 2px solid var(--phos); padding-left: 9px; margin-top: 8px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.sev {
|
||||
font-size: 9px; letter-spacing: .06em; text-transform: uppercase;
|
||||
padding: 2px 7px; border: 1px solid; flex-shrink: 0;
|
||||
}
|
||||
.sev.crit { color: var(--red); border-color: rgba(255,77,87,.4); background: rgba(255,77,87,.07); }
|
||||
.sev.high { color: var(--amber); border-color: rgba(255,177,61,.4); }
|
||||
.sev.med { color: var(--blue); border-color: rgba(82,182,255,.4); }
|
||||
.sev.low { color: var(--dim); border-color: var(--line2); }
|
||||
|
||||
.term {
|
||||
font-family: var(--font-mono); font-size: 11.5px; line-height: 1.7;
|
||||
color: var(--phos); white-space: pre-wrap;
|
||||
}
|
||||
.term .pl { color: var(--mag); }
|
||||
.term .d { color: var(--dim); }
|
||||
.term .w { color: var(--amber); }
|
||||
|
||||
.chat { display: flex; flex-direction: column; height: 100%; }
|
||||
.stream { flex: 1; overflow: auto; display: flex; flex-direction: column; gap: 14px; padding-right: 4px; }
|
||||
.mrow { display: flex; gap: 10px; max-width: 90%; }
|
||||
.mrow.me { align-self: flex-end; flex-direction: row-reverse; }
|
||||
.av {
|
||||
width: 26px; height: 26px; border: 1px solid var(--line);
|
||||
display: grid; place-items: center; flex-shrink: 0;
|
||||
font-size: 10px; color: var(--phos); font-family: var(--font-disp);
|
||||
}
|
||||
.mrow.me .av { color: var(--mag); border-color: rgba(255,61,129,.4); }
|
||||
.bub { font-size: 11.5px; line-height: 1.65; }
|
||||
.mrow.me .bub { color: var(--dim); text-align: right; }
|
||||
.bub b { color: var(--phos); }
|
||||
.codeblk {
|
||||
background: var(--bg); border-left: 2px solid var(--phos);
|
||||
padding: 8px 10px; font-size: 10.5px; color: var(--phos);
|
||||
margin: 7px 0; white-space: pre-wrap; font-family: var(--font-mono);
|
||||
}
|
||||
.composer { display: flex; gap: 8px; margin-top: 12px; flex-shrink: 0; }
|
||||
.field {
|
||||
flex: 1; display: flex; align-items: center; gap: 7px;
|
||||
border: 1px solid var(--line); padding: 8px 10px; background: var(--bg);
|
||||
}
|
||||
.field span { color: var(--phos); }
|
||||
.field input {
|
||||
flex: 1; background: none; border: none; color: var(--text);
|
||||
font-family: inherit; font-size: 11.5px; outline: none;
|
||||
}
|
||||
.send {
|
||||
border: 1px solid var(--line); background: rgba(61,240,192,.06);
|
||||
color: var(--phos); padding: 0 14px;
|
||||
font-family: inherit; font-size: 10px; letter-spacing: .08em;
|
||||
text-transform: uppercase; cursor: pointer;
|
||||
}
|
||||
.send:hover { box-shadow: var(--glow); }
|
||||
|
||||
/* ── Legacy class refinements (existing templates) ────────────── */
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 18px; padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--line2);
|
||||
}
|
||||
.page-header h1 {
|
||||
font-family: var(--font-disp); font-weight: 700;
|
||||
color: var(--phos); letter-spacing: .04em;
|
||||
font-size: 22px;
|
||||
}
|
||||
.page-header .subtitle { color: var(--dim); font-size: 11.5px; margin-top: 4px; }
|
||||
|
||||
.section {
|
||||
margin-top: 22px; padding-top: 14px;
|
||||
border-top: 1px solid var(--line2);
|
||||
}
|
||||
.section h2 {
|
||||
font-family: var(--font-disp); font-weight: 600; font-size: 13px;
|
||||
color: var(--text); letter-spacing: .1em; text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 10px; margin-bottom: 16px;
|
||||
}
|
||||
.stat-card {
|
||||
border: 1px solid var(--line2); background: var(--panel2);
|
||||
padding: 12px;
|
||||
}
|
||||
.stat-label { font-size: 9px; letter-spacing: .16em; text-transform: uppercase; color: var(--dim); }
|
||||
.stat-value { font-family: var(--font-disp); font-weight: 700; font-size: 22px; color: #fff; margin-top: 4px; }
|
||||
.stat-value.small { font-size: 13px; color: var(--text); }
|
||||
|
||||
.category-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.category-card {
|
||||
border: 1px solid var(--line2); background: var(--panel2);
|
||||
padding: 14px; text-decoration: none; color: var(--text);
|
||||
display: flex; flex-direction: column; gap: 4px; transition: .12s;
|
||||
position: relative; overflow: hidden;
|
||||
}
|
||||
.category-card:hover { border-color: var(--line); background: rgba(61,240,192,.04); }
|
||||
.category-card h3 {
|
||||
font-family: var(--font-disp); font-weight: 700; font-size: 14px;
|
||||
color: var(--phos); letter-spacing: .04em;
|
||||
}
|
||||
.category-card p { color: var(--dim); font-size: 11px; }
|
||||
.category-card .badge {
|
||||
align-self: flex-start; margin-top: 6px;
|
||||
font-size: 9px; letter-spacing: .12em; text-transform: uppercase;
|
||||
color: var(--dim); border: 1px solid var(--line2); padding: 2px 6px;
|
||||
}
|
||||
.category-card.cat-defense h3 { color: var(--blue); }
|
||||
.category-card.cat-offense h3 { color: var(--red); }
|
||||
.category-card.cat-counter h3 { color: var(--mag); }
|
||||
.category-card.cat-analyze h3 { color: var(--phos); }
|
||||
.category-card.cat-osint h3 { color: #76e0b4; }
|
||||
.category-card.cat-simulate h3 { color: var(--amber); }
|
||||
.category-card.cat-hardware h3 { color: #ffa05a; }
|
||||
|
||||
.data-table {
|
||||
width: 100%; border-collapse: collapse;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
.data-table th, .data-table td {
|
||||
padding: 8px 10px; text-align: left;
|
||||
border-bottom: 1px solid var(--line2);
|
||||
}
|
||||
.data-table th {
|
||||
font-family: var(--font-disp); font-weight: 600; font-size: 10.5px;
|
||||
letter-spacing: .12em; text-transform: uppercase; color: var(--dim);
|
||||
}
|
||||
.data-table tr:hover td { background: rgba(61,240,192,.025); }
|
||||
|
||||
.status-dot {
|
||||
display: inline-block; width: 7px; height: 7px; border-radius: 50%;
|
||||
background: var(--dim); margin-right: 7px; vertical-align: middle;
|
||||
}
|
||||
.status-dot.active { background: var(--phos); box-shadow: var(--glow); }
|
||||
|
||||
.btn {
|
||||
background: var(--panel2); color: var(--text);
|
||||
border: 1px solid var(--line2); padding: 7px 14px;
|
||||
font-family: var(--font-mono); font-size: 11px;
|
||||
cursor: pointer; transition: .12s;
|
||||
}
|
||||
.btn:hover { border-color: var(--line); color: var(--phos); }
|
||||
.btn-sm, .btn-small { padding: 4px 10px; font-size: 10px; }
|
||||
.btn-primary { background: rgba(61,240,192,.08); border-color: var(--line); color: var(--phos); }
|
||||
.btn-primary:hover { box-shadow: var(--glow); }
|
||||
.btn-danger { color: var(--red); border-color: rgba(255,77,87,.35); }
|
||||
.btn-danger:hover { box-shadow: 0 0 12px rgba(255,77,87,.25); }
|
||||
|
||||
/* ── Login wrapper ─────────────────────────────────────────────── */
|
||||
|
||||
.login-wrapper {
|
||||
position: relative; z-index: 1;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
min-height: 100vh; padding: 20px;
|
||||
}
|
||||
@@ -2161,6 +2161,8 @@ function halToggle() {
|
||||
if (!p) return;
|
||||
var visible = p.style.display !== 'none';
|
||||
p.style.display = visible ? 'none' : 'flex';
|
||||
// Remember open/closed state across page navigation
|
||||
sessionStorage.setItem('hal_panel_open', visible ? 'false' : 'true');
|
||||
if (!visible) {
|
||||
var inp = document.getElementById('hal-input');
|
||||
if (inp) inp.focus();
|
||||
@@ -2279,6 +2281,7 @@ function halAppendStyled(type, text) {
|
||||
}
|
||||
msgs.appendChild(div);
|
||||
halScroll();
|
||||
_halSaveMessages();
|
||||
}
|
||||
|
||||
function halAppend(role, text) {
|
||||
@@ -2289,6 +2292,8 @@ function halAppend(role, text) {
|
||||
div.textContent = text;
|
||||
msgs.appendChild(div);
|
||||
halScroll();
|
||||
// Persist to sessionStorage so messages survive page navigation
|
||||
_halSaveMessages();
|
||||
return div;
|
||||
}
|
||||
|
||||
@@ -2300,9 +2305,60 @@ function halScroll() {
|
||||
function halClear() {
|
||||
var m = document.getElementById('hal-messages');
|
||||
if (m) m.innerHTML = '';
|
||||
sessionStorage.removeItem('hal_messages');
|
||||
sessionStorage.removeItem('hal_panel_open');
|
||||
fetch('/api/chat/reset', {method: 'POST'}).catch(function() {});
|
||||
}
|
||||
|
||||
// Persist chat messages across page navigation
|
||||
function _halSaveMessages() {
|
||||
var msgs = document.getElementById('hal-messages');
|
||||
if (!msgs) return;
|
||||
var items = [];
|
||||
msgs.querySelectorAll('.hal-msg').forEach(function(el) {
|
||||
var role = 'hal';
|
||||
if (el.classList.contains('hal-msg-user')) role = 'user';
|
||||
else if (el.classList.contains('hal-msg-status')) role = 'status';
|
||||
else if (el.classList.contains('hal-msg-action')) role = 'action';
|
||||
else if (el.classList.contains('hal-msg-error')) role = 'error';
|
||||
items.push({role: role, text: el.textContent});
|
||||
});
|
||||
// Keep last 100 messages to avoid storage bloat
|
||||
if (items.length > 100) items = items.slice(-100);
|
||||
try { sessionStorage.setItem('hal_messages', JSON.stringify(items)); } catch(e) {}
|
||||
}
|
||||
|
||||
function _halRestoreMessages() {
|
||||
var msgs = document.getElementById('hal-messages');
|
||||
if (!msgs) return;
|
||||
try {
|
||||
var saved = sessionStorage.getItem('hal_messages');
|
||||
if (!saved) return;
|
||||
var items = JSON.parse(saved);
|
||||
items.forEach(function(item) {
|
||||
var div = document.createElement('div');
|
||||
if (item.role === 'user' || item.role === 'hal') {
|
||||
div.className = 'hal-msg hal-msg-' + item.role;
|
||||
div.textContent = item.text;
|
||||
} else {
|
||||
div.className = 'hal-msg hal-msg-' + item.role;
|
||||
div.textContent = item.text;
|
||||
}
|
||||
msgs.appendChild(div);
|
||||
});
|
||||
halScroll();
|
||||
} catch(e) {}
|
||||
|
||||
// Restore panel visibility
|
||||
if (sessionStorage.getItem('hal_panel_open') === 'true') {
|
||||
var p = document.getElementById('hal-panel');
|
||||
if (p) p.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
// Restore on page load
|
||||
document.addEventListener('DOMContentLoaded', _halRestoreMessages);
|
||||
|
||||
// ── HAL Auto-Analyst ──────────────────────────────────────────────────────────
|
||||
// Call halAnalyze() after any tool displays results. HAL will analyze the output
|
||||
// via the loaded LLM and open the chat panel with findings.
|
||||
|
||||
120
web/static/js/autarchos.js
Normal file
120
web/static/js/autarchos.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/* AutarchOS — desktop chrome client-side wiring.
|
||||
*
|
||||
* Owns: live clock, dock state highlight for current blueprint, apps launcher
|
||||
* modal (filterable list of every blueprint), keyboard shortcuts.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// ── Clock ────────────────────────────────────────────────────────
|
||||
function tickClock() {
|
||||
var el = document.getElementById('aos-clock');
|
||||
if (!el) return;
|
||||
el.textContent = new Date().toTimeString().slice(0, 8);
|
||||
}
|
||||
setInterval(tickClock, 1000);
|
||||
tickClock();
|
||||
|
||||
// ── Mark dock app for current blueprint ─────────────────────────
|
||||
function markActiveDockApp() {
|
||||
var bp = document.body.getAttribute('data-blueprint') || '';
|
||||
document.querySelectorAll('.aos-dock .app').forEach(function (a) {
|
||||
var slug = a.getAttribute('data-app') || '';
|
||||
a.classList.toggle('active', slug && slug === bp);
|
||||
});
|
||||
}
|
||||
markActiveDockApp();
|
||||
|
||||
// ── Apps launcher modal ─────────────────────────────────────────
|
||||
var backdrop = document.getElementById('aos-launcher');
|
||||
var searchInput = document.getElementById('aos-launcher-search');
|
||||
|
||||
function openLauncher() {
|
||||
if (!backdrop) return;
|
||||
backdrop.classList.add('open');
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
searchInput.focus();
|
||||
filterApps('');
|
||||
}
|
||||
}
|
||||
|
||||
function closeLauncher() {
|
||||
if (!backdrop) return;
|
||||
backdrop.classList.remove('open');
|
||||
}
|
||||
|
||||
function filterApps(query) {
|
||||
var q = (query || '').trim().toLowerCase();
|
||||
document.querySelectorAll('.aos-launcher .app-card').forEach(function (card) {
|
||||
var hay = (card.textContent || '').toLowerCase();
|
||||
card.style.display = (!q || hay.indexOf(q) !== -1) ? '' : 'none';
|
||||
});
|
||||
document.querySelectorAll('.aos-launcher .group').forEach(function (g) {
|
||||
var visible = g.querySelectorAll('.app-card:not([style*="display: none"])').length;
|
||||
g.style.display = visible ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-aos-action="open-launcher"]').forEach(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
openLauncher();
|
||||
});
|
||||
});
|
||||
|
||||
if (backdrop) {
|
||||
backdrop.addEventListener('click', function (e) {
|
||||
if (e.target === backdrop) closeLauncher();
|
||||
});
|
||||
}
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function () { filterApps(this.value); });
|
||||
searchInput.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') closeLauncher();
|
||||
if (e.key === 'Enter') {
|
||||
var first = document.querySelector(
|
||||
'.aos-launcher .app-card:not([style*="display: none"])'
|
||||
);
|
||||
if (first && first.href) window.location.href = first.href;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Global shortcuts
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape' && backdrop && backdrop.classList.contains('open')) {
|
||||
closeLauncher();
|
||||
}
|
||||
// Super/Ctrl+Space → open launcher
|
||||
if ((e.ctrlKey || e.metaKey) && e.code === 'Space') {
|
||||
e.preventDefault();
|
||||
if (backdrop && backdrop.classList.contains('open')) {
|
||||
closeLauncher();
|
||||
} else {
|
||||
openLauncher();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ── Sparkline helper (used by dashboard widgets) ────────────────
|
||||
window.aosRenderSpark = function (el, count, hotIndices) {
|
||||
if (!el) return;
|
||||
el.innerHTML = '';
|
||||
var hot = new Set(hotIndices || []);
|
||||
for (var i = 0; i < count; i++) {
|
||||
var b = document.createElement('i');
|
||||
b.style.height = (16 + Math.abs(Math.sin(i * 0.55)) * 60 + Math.random() * 18) + '%';
|
||||
if (hot.has(i)) {
|
||||
b.classList.add('hot');
|
||||
b.style.height = '92%';
|
||||
}
|
||||
el.appendChild(b);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Expandable incidents (dashboard / triage views) ─────────────
|
||||
document.querySelectorAll('.inc').forEach(function (i) {
|
||||
i.addEventListener('click', function () { i.classList.toggle('open'); });
|
||||
});
|
||||
})();
|
||||
864
web/templates/android_forensics.html
Normal file
864
web/templates/android_forensics.html
Normal file
@@ -0,0 +1,864 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Android Forensics — AUTARCH{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Autarch Android Forensics</h1>
|
||||
<p class="subtitle">Forensic acquisition and IOC scanning for Android devices</p>
|
||||
</div>
|
||||
|
||||
<!-- Mode bar -->
|
||||
<div class="card" style="margin:0.5rem 0 0.5rem 0;padding:0.6rem 1rem;display:flex;align-items:center;gap:0.6rem;flex-wrap:wrap">
|
||||
<span style="font-weight:600;font-size:0.85rem;color:var(--text-secondary)">ADB Mode:</span>
|
||||
<button id="af-mode-server" class="btn btn-sm active" onclick="afSetMode('server')">Server (Local ADB)</button>
|
||||
<button id="af-mode-direct" class="btn btn-sm" onclick="afSetMode('direct')">Direct (WebUSB)</button>
|
||||
<!-- Direct mode: connect/disconnect -->
|
||||
<span id="af-direct-bar" style="display:none;align-items:center;gap:0.5rem">
|
||||
<span id="af-device-label" style="font-size:0.85rem;color:var(--text-secondary)">Not connected</span>
|
||||
<button class="btn btn-sm" onclick="afDirectConnect()">Connect</button>
|
||||
<button class="btn btn-sm btn-danger" id="af-disconnect-btn" style="display:none" onclick="afDirectDisconnect()">Disconnect</button>
|
||||
</span>
|
||||
<span id="af-webusb-warning" style="display:none;color:#f97316;font-size:0.8rem;margin-left:0.5rem"></span>
|
||||
</div>
|
||||
|
||||
<!-- Server mode: device selector -->
|
||||
<div id="af-server-selector" class="card" style="margin:0.5rem 0 1rem 0;padding:0.8rem 1rem;display:flex;align-items:center;gap:0.8rem;flex-wrap:wrap">
|
||||
<label style="font-weight:600">Device:</label>
|
||||
<select id="af-device" style="flex:1;min-width:200px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px" onchange="afDeviceChanged()">
|
||||
<option value="">-- Select --</option>
|
||||
</select>
|
||||
<button class="btn btn-sm" onclick="afRefreshDevices()">Refresh</button>
|
||||
<!-- IOC DB badges -->
|
||||
<span class="badge" id="af-badge-pkgs" style="background:var(--bg-secondary);color:var(--text-secondary);margin-left:auto">pkgs: --</span>
|
||||
<span class="badge" id="af-badge-domains" style="background:var(--bg-secondary);color:var(--text-secondary)">domains: --</span>
|
||||
<span class="badge" id="af-badge-certs" style="background:var(--bg-secondary);color:var(--text-secondary)">certs: --</span>
|
||||
</div>
|
||||
|
||||
<!-- Status message -->
|
||||
<div id="af-status" style="display:none;padding:0.5rem 1rem;margin-bottom:0.75rem;border-radius:4px;font-size:0.9rem;"></div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="tab-bar">
|
||||
<button class="tab active" data-tab-group="af" data-tab="acquire" onclick="showTab('af','acquire')">Acquire</button>
|
||||
<button class="tab" data-tab-group="af" data-tab="scan" onclick="showTab('af','scan')">IOC Scan</button>
|
||||
<button class="tab" data-tab-group="af" data-tab="reports" onclick="showTab('af','reports')">Reports</button>
|
||||
<button class="tab" data-tab-group="af" data-tab="database" onclick="showTab('af','database')">Database</button>
|
||||
<button class="tab" data-tab-group="af" data-tab="ai" onclick="showTab('af','ai')">AI & CVE</button>
|
||||
<button class="tab" data-tab-group="af" data-tab="alerts" onclick="showTab('af','alerts')">Runtime Alerts</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Acquire ──────────────────────────────────────────────────── -->
|
||||
<div class="tab-content active" data-tab-group="af" data-tab="acquire">
|
||||
<div class="section">
|
||||
<h2>Acquisition Modules</h2>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:0.6rem;margin-bottom:1rem">
|
||||
<button class="btn" onclick="afAcquire('packages')">Package List</button>
|
||||
<button class="btn" onclick="afAcquire('processes')">Running Processes</button>
|
||||
<button class="btn" onclick="afAcquire('getprop')">System Properties</button>
|
||||
<button class="btn" onclick="afAcquire('settings')">Settings</button>
|
||||
<button class="btn" onclick="afAcquire('services')">Services</button>
|
||||
<button class="btn" onclick="afAcquire('dumpsys')">Dumpsys <small>(server only)</small></button>
|
||||
<button class="btn" onclick="afAcquire('logcat')">Logcat</button>
|
||||
<button class="btn" onclick="afAcquire('tmp_files')">Temp Files</button>
|
||||
<button class="btn" onclick="afAcquire('files')">File Listing</button>
|
||||
<button class="btn" onclick="afAcquire('logs')">Full Logs</button>
|
||||
<button class="btn" onclick="afAcquire('bugreport')">Bugreport Info</button>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="afFullAcquisition()">▶ Full Acquisition</button>
|
||||
<span id="af-acq-spinner" style="display:none;margin-left:0.75rem;color:var(--text-secondary)">Running…</span>
|
||||
</div>
|
||||
<div class="section" id="af-acq-results-section" style="display:none">
|
||||
<h2>Results</h2>
|
||||
<div id="af-acq-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── IOC Scan ─────────────────────────────────────────────────── -->
|
||||
<div class="tab-content" data-tab-group="af" data-tab="scan">
|
||||
<div class="section">
|
||||
<h2>IOC Scanning</h2>
|
||||
<!-- Heuristic controls -->
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;margin-bottom:0.75rem;padding:0.5rem 0.75rem;background:rgba(241,196,15,0.06);border:1px solid rgba(241,196,15,0.2);border-radius:4px">
|
||||
<label style="display:flex;align-items:center;gap:0.4rem;font-size:0.88rem;cursor:pointer">
|
||||
<input type="checkbox" id="af-heuristic-toggle" checked>
|
||||
<b>SLM Heuristics</b> — AI pattern scan after IOC match
|
||||
</label>
|
||||
<span style="font-size:0.78rem;color:var(--text-muted)">Threshold:</span>
|
||||
<input type="range" id="af-heuristic-threshold" min="40" max="90" value="65" style="width:80px" oninput="document.getElementById('af-heuristic-threshold-val').textContent=Math.round(this.value)+'%'">
|
||||
<span id="af-heuristic-threshold-val" style="font-size:0.82rem;min-width:30px">65%</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:1rem">
|
||||
<button class="btn btn-primary" onclick="afScan('apps')">Scan Apps</button>
|
||||
<button class="btn btn-primary" onclick="afScan('certs')">Scan Certificates <small>(server only)</small></button>
|
||||
<button class="btn btn-primary" onclick="afScan('network')">Scan Network</button>
|
||||
<button class="btn btn-primary" onclick="afScan('files')">Scan File Artifacts <small>(server only)</small></button>
|
||||
<button class="btn btn-primary" onclick="afScan('processes')">Scan Processes</button>
|
||||
<button class="btn btn-danger" onclick="afScan('full')">Full Scan <small>(server only)</small></button>
|
||||
</div>
|
||||
<span id="af-scan-spinner" style="display:none;color:var(--text-secondary)">Scanning…</span>
|
||||
</div>
|
||||
<div class="section" id="af-scan-results-section" style="display:none">
|
||||
<h2 id="af-scan-title">Findings</h2>
|
||||
<div id="af-scan-clean" style="display:none;padding:0.75rem;background:rgba(39,174,96,0.1);border-radius:4px;color:#27ae60">
|
||||
✅ No IOC matches found. Device appears clean.
|
||||
</div>
|
||||
<table class="table" id="af-scan-table" style="display:none">
|
||||
<thead><tr>
|
||||
<th>Severity</th><th>Type</th><th>Threat</th><th>Detail</th><th style="font-size:0.75rem">Source</th>
|
||||
</tr></thead>
|
||||
<tbody id="af-scan-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Reports ──────────────────────────────────────────────────── -->
|
||||
<div class="tab-content" data-tab-group="af" data-tab="reports">
|
||||
<div class="section">
|
||||
<h2>Past Acquisitions</h2>
|
||||
<button class="btn" onclick="afLoadReports()">Load Acquisitions</button>
|
||||
</div>
|
||||
<div id="af-reports-section" style="display:none">
|
||||
<table class="table">
|
||||
<thead><tr><th>Timestamp</th><th>Modules</th><th>Findings</th><th></th></tr></thead>
|
||||
<tbody id="af-reports-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="af-report-viewer" style="display:none;margin-top:1rem">
|
||||
<h3>Report: <span id="af-report-id"></span></h3>
|
||||
<pre id="af-report-json" style="background:var(--bg-secondary);padding:1rem;border-radius:4px;max-height:500px;overflow:auto;font-size:0.78rem;white-space:pre-wrap;word-break:break-all"></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Database ─────────────────────────────────────────────────── -->
|
||||
<div class="tab-content" data-tab-group="af" data-tab="database">
|
||||
<div class="section">
|
||||
<h2>IOC Database</h2>
|
||||
<div style="display:flex;gap:0.6rem;flex-wrap:wrap">
|
||||
<button class="btn" onclick="afDbStats()">Load Stats</button>
|
||||
<button class="btn btn-primary" onclick="afDbReload()">Reload Database</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="af-db-section" style="display:none">
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:0.75rem;margin:1rem 0">
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-packages">--</div><div class="stat-lbl">Packages</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-domains">--</div><div class="stat-lbl">Domains</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-certs">--</div><div class="stat-lbl">Certs</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-hashes">--</div><div class="stat-lbl">Hashes</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-ips">--</div><div class="stat-lbl">IPs</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-processes">--</div><div class="stat-lbl">Processes</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-filepaths">--</div><div class="stat-lbl">File Paths</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-st-yara">--</div><div class="stat-lbl">YARA Rules</div></div>
|
||||
</div>
|
||||
<table class="table" style="max-width:500px">
|
||||
<thead><tr><th>Source</th><th>Count / Status</th></tr></thead>
|
||||
<tbody id="af-db-sources"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── AI & CVE ───────────────────────────────────────────────── -->
|
||||
<div class="tab-content" data-tab-group="af" data-tab="ai">
|
||||
<div class="section">
|
||||
<h2>AI Threat Analysis</h2>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;margin-bottom:0.75rem">
|
||||
<span style="font-weight:600;font-size:0.9rem">Backend:</span>
|
||||
<label style="font-size:0.88rem"><input type="radio" name="af-ai-backend" value="autarch" checked> AUTARCH LLM</label>
|
||||
<label style="font-size:0.88rem"><input type="radio" name="af-ai-backend" value="ondevice"> On-device prompt</label>
|
||||
<label style="font-size:0.88rem"><input type="radio" name="af-ai-backend" value="raw"> Raw export</label>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;margin-bottom:0.75rem">
|
||||
<span style="font-weight:600;font-size:0.9rem">Focus:</span>
|
||||
<label style="font-size:0.88rem"><input type="checkbox" class="af-focus" value="packages" checked> Packages</label>
|
||||
<label style="font-size:0.88rem"><input type="checkbox" class="af-focus" value="network" checked> Network</label>
|
||||
<label style="font-size:0.88rem"><input type="checkbox" class="af-focus" value="processes" checked> Processes</label>
|
||||
<label style="font-size:0.88rem"><input type="checkbox" class="af-focus" value="files"> Files</label>
|
||||
</div>
|
||||
<div style="display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:0.75rem">
|
||||
<input type="text" id="af-ai-acq-id" placeholder="Acquisition ID (leave empty for live scan)" style="flex:1;min-width:220px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px;font-size:0.88rem">
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="afAiAnalyze()">💡 Run AI Analysis</button>
|
||||
<span id="af-ai-spinner" style="display:none;margin-left:0.75rem;color:var(--text-secondary)">Analyzing…</span>
|
||||
</div>
|
||||
<div id="af-ai-results-section" class="section" style="display:none">
|
||||
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:0.5rem;flex-wrap:wrap">
|
||||
<span style="font-weight:600">Risk Level:</span>
|
||||
<span id="af-ai-risk" style="font-size:1.2rem;font-weight:700"></span>
|
||||
</div>
|
||||
<p id="af-ai-summary" style="color:var(--text-secondary);font-size:0.9rem;margin-bottom:1rem"></p>
|
||||
<table class="table" id="af-ai-findings-table" style="display:none">
|
||||
<thead><tr><th>Severity</th><th>Type</th><th>Detail</th><th>Recommendation</th></tr></thead>
|
||||
<tbody id="af-ai-findings-body"></tbody>
|
||||
</table>
|
||||
<pre id="af-ai-raw" style="display:none;background:var(--bg-secondary);padding:1rem;border-radius:4px;max-height:400px;overflow:auto;font-size:0.78rem;white-space:pre-wrap;word-break:break-all"></pre>
|
||||
</div>
|
||||
|
||||
<hr style="border-color:var(--border-color);margin:0.5rem 0">
|
||||
|
||||
<div class="section">
|
||||
<h2>CVE Lookup</h2>
|
||||
<div style="display:flex;gap:0.6rem;flex-wrap:wrap;margin-bottom:0.75rem">
|
||||
<input type="text" id="af-cve-pkg" placeholder="Package name (e.g. com.whatsapp)" style="flex:1;min-width:200px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px;font-size:0.88rem">
|
||||
<input type="text" id="af-cve-ver" placeholder="Version (optional)" style="width:130px;padding:0.4rem;background:var(--bg-secondary);color:var(--text-primary);border:1px solid var(--border-color);border-radius:4px;font-size:0.88rem">
|
||||
<button class="btn" onclick="afCveLookup()">Look Up CVEs</button>
|
||||
</div>
|
||||
<button class="btn" onclick="afCveCheckDevice()">🔍 Scan All Device Packages</button>
|
||||
<span id="af-cve-spinner" style="display:none;margin-left:0.75rem;color:var(--text-secondary)">Checking CVEs…</span>
|
||||
</div>
|
||||
<div id="af-cve-results-section" style="display:none;margin:0 1rem 1rem">
|
||||
<div style="margin-bottom:0.5rem;font-size:0.88rem;color:var(--text-secondary)" id="af-cve-summary"></div>
|
||||
<table class="table">
|
||||
<thead><tr><th>Package</th><th>CVE ID</th><th>Severity</th><th>Description</th><th>Patched In</th></tr></thead>
|
||||
<tbody id="af-cve-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<hr style="border-color:var(--border-color);margin:0.5rem 0">
|
||||
|
||||
<div class="section">
|
||||
<h2>CVE Database Stats</h2>
|
||||
<button class="btn" onclick="afCveStats()">Load Stats</button>
|
||||
<div id="af-cve-stats-section" style="display:none;margin-top:1rem">
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:0.75rem">
|
||||
<div class="stat-card"><div class="stat-val" id="af-cvs-cached">--</div><div class="stat-lbl">Cached Pkgs</div></div>
|
||||
<div class="stat-card"><div class="stat-val" id="af-cvs-query" style="font-size:0.9rem">--</div><div class="stat-lbl">Last Query</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Runtime Alerts ────────────────────────────────────────── -->
|
||||
<div class="tab-content" data-tab-group="af" data-tab="alerts">
|
||||
<div class="section">
|
||||
<h2>Runtime Alerts</h2>
|
||||
<div style="display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap;margin-bottom:0.75rem">
|
||||
<button class="btn" onclick="afLoadAlerts()">↻ Refresh</button>
|
||||
<label style="display:flex;align-items:center;gap:0.4rem;font-size:0.88rem;cursor:pointer">
|
||||
<input type="checkbox" id="af-alerts-autorefresh" checked onchange="afToggleAlertAutoRefresh()">
|
||||
Auto-refresh every 30s
|
||||
</label>
|
||||
<span id="af-alerts-count" style="font-size:0.85rem;color:var(--text-secondary)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="af-alerts-empty" style="display:none;padding:1rem 1.5rem;color:var(--text-muted)">No runtime alerts received yet. Alerts are sent from the Archon companion app.</div>
|
||||
<table class="table" id="af-alerts-table" style="display:none">
|
||||
<thead><tr><th>Time</th><th>Device</th><th>Severity</th><th>Event</th><th>Detail</th></tr></thead>
|
||||
<tbody id="af-alerts-body"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.stat-card { background:var(--bg-secondary,#1a1a2e); border:1px solid var(--border-color,#333); border-radius:6px; padding:1rem; text-align:center; }
|
||||
.stat-val { font-size:1.8rem; font-weight:700; color:var(--primary,#7c4dff); }
|
||||
.stat-lbl { font-size:0.8rem; color:var(--text-muted,#888); margin-top:0.25rem; }
|
||||
.sev-critical { color:#e74c3c; font-weight:700; }
|
||||
.sev-high { color:#e67e22; font-weight:600; }
|
||||
.sev-medium { color:#f1c40f; }
|
||||
.sev-low { color:#888; }
|
||||
.acq-panel { background:var(--bg-secondary,#1a1a2e); border:1px solid var(--border-color,#333); border-radius:6px; margin-bottom:0.6rem; }
|
||||
.acq-ph { padding:0.5rem 0.9rem; cursor:pointer; display:flex; justify-content:space-between; font-weight:600; font-size:0.9rem; }
|
||||
.acq-pb { padding:0.75rem; display:none; max-height:280px; overflow:auto; }
|
||||
.acq-pb pre { font-size:0.76rem; margin:0; white-space:pre-wrap; word-break:break-all; }
|
||||
.heuristic-tag { display:inline-block; background:rgba(241,196,15,0.2); color:#f1c40f; font-size:0.72rem; padding:0.1rem 0.3rem; border-radius:3px; margin-right:0.3rem; font-weight:700; }
|
||||
.sev-clean { color:#27ae60; font-weight:600; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
/* ── State ─────────────────────────────────────────────────────── */
|
||||
var afMode = 'server';
|
||||
var afSerial = '';
|
||||
|
||||
/* ── Init ──────────────────────────────────────────────────────── */
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var saved = localStorage.getItem('af_mode') || 'server';
|
||||
afSetMode(saved);
|
||||
if (saved === 'server') afRefreshDevices();
|
||||
afLoadBadges();
|
||||
if (document.getElementById('af-alerts-autorefresh').checked) {
|
||||
afAlertTimer = setInterval(afLoadAlerts, 30000);
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Mode Switching ─────────────────────────────────────────────── */
|
||||
function afSetMode(mode) {
|
||||
afMode = mode;
|
||||
localStorage.setItem('af_mode', mode);
|
||||
document.getElementById('af-mode-server').classList.toggle('active', mode === 'server');
|
||||
document.getElementById('af-mode-direct').classList.toggle('active', mode === 'direct');
|
||||
document.getElementById('af-direct-bar').style.display = mode === 'direct' ? 'flex' : 'none';
|
||||
document.getElementById('af-server-selector').style.display = mode === 'server' ? '' : 'none';
|
||||
|
||||
if (mode === 'direct') {
|
||||
var warn = document.getElementById('af-webusb-warning');
|
||||
if (!navigator.usb) {
|
||||
warn.textContent = location.protocol !== 'https:'
|
||||
? 'WebUSB requires HTTPS'
|
||||
: 'WebUSB not supported — use Chrome, Edge, or Brave';
|
||||
warn.style.display = '';
|
||||
} else {
|
||||
warn.style.display = 'none';
|
||||
}
|
||||
afUpdateDirectStatus();
|
||||
} else {
|
||||
afRefreshDevices();
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Direct Mode Connection ─────────────────────────────────────── */
|
||||
async function afDirectConnect() {
|
||||
var lbl = document.getElementById('af-device-label');
|
||||
lbl.textContent = 'Connecting...';
|
||||
try {
|
||||
var deviceObj = await HWDirect.adbRequestDevice();
|
||||
if (!deviceObj) { lbl.textContent = 'Cancelled'; return; }
|
||||
await HWDirect.adbConnect(deviceObj);
|
||||
afUpdateDirectStatus();
|
||||
HWDirect.adbGetInfo().then(function() { afUpdateDirectStatus(); }).catch(function() {});
|
||||
} catch(e) {
|
||||
lbl.textContent = 'Error: ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
function afDirectDisconnect() {
|
||||
HWDirect.adbDisconnect();
|
||||
afSerial = '';
|
||||
afUpdateDirectStatus();
|
||||
}
|
||||
|
||||
function afUpdateDirectStatus() {
|
||||
var connected = HWDirect.adbIsConnected();
|
||||
document.getElementById('af-device-label').textContent = connected ? HWDirect.adbGetDeviceLabel() : 'Not connected';
|
||||
document.getElementById('af-disconnect-btn').style.display = connected ? '' : 'none';
|
||||
afSerial = connected ? '__webusb__' : '';
|
||||
}
|
||||
|
||||
/* ── Server Mode Device Selection ──────────────────────────────── */
|
||||
function afRefreshDevices() {
|
||||
if (afMode === 'direct') { afUpdateDirectStatus(); return; }
|
||||
fetch('/hardware/adb/devices')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
var sel = document.getElementById('af-device');
|
||||
var prev = sel.value;
|
||||
sel.innerHTML = '<option value="">-- Select --</option>';
|
||||
(data.devices || []).forEach(function(d) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = d.serial;
|
||||
opt.textContent = d.serial + (d.model ? ' (' + d.model + ')' : '');
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
if (prev) sel.value = prev;
|
||||
afDeviceChanged();
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
function afDeviceChanged() {
|
||||
if (afMode === 'direct') return;
|
||||
afSerial = document.getElementById('af-device').value;
|
||||
}
|
||||
|
||||
/* ── Core Request Helper ────────────────────────────────────────── */
|
||||
function afPost(url, body) {
|
||||
if (afMode === 'direct') return afDirect(url, body);
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
}).then(function(r) { return r.json(); });
|
||||
}
|
||||
|
||||
async function afDirect(url, body) {
|
||||
if (!HWDirect.adbIsConnected()) {
|
||||
return {error: 'No WebUSB device connected — click Connect first.'};
|
||||
}
|
||||
// Convert URL to op key: /android-forensics/acquire/packages → acquire_packages
|
||||
var op = url.replace('/android-forensics/', '').replace(/\//g, '_');
|
||||
try {
|
||||
var cmdRes = await fetch('/android-forensics/cmd', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({op: op})
|
||||
});
|
||||
var cmdData = await cmdRes.json();
|
||||
if (!cmdData.supported) {
|
||||
return {error: cmdData.reason || 'This operation requires Server mode.'};
|
||||
}
|
||||
var raw = {};
|
||||
for (var key in cmdData.commands) {
|
||||
if (!cmdData.commands.hasOwnProperty(key)) continue;
|
||||
var result = await HWDirect.adbShell(cmdData.commands[key]);
|
||||
raw[key] = (typeof result === 'object' && result !== null)
|
||||
? (result.stdout || result.output || '')
|
||||
: (result || '');
|
||||
}
|
||||
var parseRes = await fetch('/android-forensics/parse', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({op: op, body: body, raw: raw})
|
||||
});
|
||||
return parseRes.json();
|
||||
} catch(e) {
|
||||
return {error: 'Direct mode error: ' + e.message};
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Status ─────────────────────────────────────────────────────── */
|
||||
function afStatus(msg, type) {
|
||||
var el = document.getElementById('af-status');
|
||||
el.style.background = {success:'rgba(39,174,96,0.15)', danger:'rgba(231,76,60,0.15)', warning:'rgba(241,196,15,0.15)', info:'rgba(52,152,219,0.15)'}[type] || 'rgba(52,152,219,0.15)';
|
||||
el.style.color = {success:'#27ae60', danger:'#e74c3c', warning:'#f39c12', info:'#3498db'}[type] || '#3498db';
|
||||
el.textContent = msg;
|
||||
el.style.display = 'block';
|
||||
setTimeout(function() { el.style.display = 'none'; }, 7000);
|
||||
}
|
||||
|
||||
/* ── IOC Badges ─────────────────────────────────────────────────── */
|
||||
function afLoadBadges() {
|
||||
fetch('/android-forensics/db/stats')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
var c = data.counts || {};
|
||||
document.getElementById('af-badge-pkgs').textContent = 'pkgs: ' + (c.packages || 0);
|
||||
document.getElementById('af-badge-domains').textContent = 'domains: ' + (c.domains || 0);
|
||||
document.getElementById('af-badge-certs').textContent = 'certs: ' + (c.certs || 0);
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
/* ── Acquisition ────────────────────────────────────────────────── */
|
||||
function afAcquire(module) {
|
||||
if (!afSerial) { afStatus('Select a device first.', 'warning'); return; }
|
||||
afStatus('Acquiring ' + module + '...', 'info');
|
||||
afPost('/android-forensics/acquire/' + module, {serial: afSerial})
|
||||
.then(function(data) {
|
||||
afShowAcqPanel(module, data);
|
||||
document.getElementById('af-acq-results-section').style.display = '';
|
||||
afStatus(module + ' acquired.', data.ok ? 'success' : 'danger');
|
||||
})
|
||||
.catch(function(e) { afStatus('Error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
function afFullAcquisition() {
|
||||
if (!afSerial) { afStatus('Select a device first.', 'warning'); return; }
|
||||
if (afMode === 'direct') { afStatus('Full acquisition requires Server mode.', 'warning'); return; }
|
||||
var spinner = document.getElementById('af-acq-spinner');
|
||||
spinner.style.display = 'inline';
|
||||
afStatus('Running full acquisition...', 'info');
|
||||
fetch('/android-forensics/acquire/full', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({serial: afSerial})
|
||||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||||
spinner.style.display = 'none';
|
||||
var summary = data.scan_summary || {};
|
||||
var n = summary.total_findings || 0;
|
||||
afStatus('Full acquisition complete. Findings: ' + n, n > 0 ? 'danger' : 'success');
|
||||
var mods = data.modules || {};
|
||||
Object.keys(mods).forEach(function(mod) { afShowAcqPanel(mod, mods[mod]); });
|
||||
document.getElementById('af-acq-results-section').style.display = '';
|
||||
if ((data.ioc_findings || []).length > 0) {
|
||||
showTab('af', 'scan');
|
||||
afRenderFindings(data.ioc_findings, 'Full Acquisition');
|
||||
}
|
||||
}).catch(function(e) { spinner.style.display = 'none'; afStatus('Error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
function afShowAcqPanel(module, data) {
|
||||
var container = document.getElementById('af-acq-results');
|
||||
var existing = document.getElementById('af-panel-' + module);
|
||||
if (existing) existing.remove();
|
||||
var summary = data.total ? data.total + ' packages'
|
||||
: data.count ? data.count + ' items'
|
||||
: data.lines ? data.lines + ' lines'
|
||||
: data.size ? data.size + ' bytes'
|
||||
: !data.ok ? 'Error: ' + (data.error || 'unknown')
|
||||
: 'OK';
|
||||
var panel = document.createElement('div');
|
||||
panel.className = 'acq-panel';
|
||||
panel.id = 'af-panel-' + module;
|
||||
panel.innerHTML = '<div class="acq-ph" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display===\'block\'?\'none\':\'block\'">'
|
||||
+ '<span>' + afEsc(module) + ' — ' + afEsc(summary) + '</span><span>▼</span></div>'
|
||||
+ '<div class="acq-pb"><pre>' + afEsc(JSON.stringify(data, null, 2)) + '</pre></div>';
|
||||
container.prepend(panel);
|
||||
}
|
||||
|
||||
/* ── IOC Scanning ───────────────────────────────────────────────── */
|
||||
async function afScan(type) {
|
||||
if (!afSerial) { afStatus('Select a device first.', 'warning'); return; }
|
||||
if (afMode === 'direct' && (type === 'certs' || type === 'files' || type === 'full')) {
|
||||
afStatus(type + ' scan requires Server mode.', 'warning'); return;
|
||||
}
|
||||
var spinner = document.getElementById('af-scan-spinner');
|
||||
spinner.style.display = 'inline';
|
||||
afStatus('Scanning ' + type + '...', 'info');
|
||||
try {
|
||||
var data = await afPost('/android-forensics/scan/' + type, {serial: afSerial});
|
||||
if (data.error) { spinner.style.display = 'none'; afStatus('Error: ' + data.error, 'danger'); return; }
|
||||
var findings = (data.ioc_findings || data.findings || []).slice();
|
||||
|
||||
// SLM heuristic pass (server mode + toggle on)
|
||||
var doHeuristic = document.getElementById('af-heuristic-toggle').checked && afMode === 'server';
|
||||
if (doHeuristic && type !== 'full') {
|
||||
afStatus('Running SLM heuristics...', 'info');
|
||||
var hFindings = await afHeuristicPass(type, data);
|
||||
findings = findings.concat(hFindings);
|
||||
}
|
||||
|
||||
spinner.style.display = 'none';
|
||||
afRenderFindings(findings, type + ' scan');
|
||||
afStatus('Scan complete. Findings: ' + findings.length, findings.length > 0 ? 'danger' : 'success');
|
||||
} catch(e) {
|
||||
spinner.style.display = 'none';
|
||||
afStatus('Error: ' + e, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function afHeuristicPass(scanType, scanData) {
|
||||
var threshold = parseInt(document.getElementById('af-heuristic-threshold').value, 10) / 100;
|
||||
var items = [], dataType = 'packages';
|
||||
|
||||
try {
|
||||
if (scanType === 'apps') {
|
||||
var r = await afPost('/android-forensics/acquire/packages', {serial: afSerial});
|
||||
items = (r.packages || []).map(function(p) { return p.package; });
|
||||
dataType = 'packages';
|
||||
} else if (scanType === 'processes') {
|
||||
var r = await afPost('/android-forensics/acquire/processes', {serial: afSerial});
|
||||
items = (r.output || '').split('\n').slice(1, 120)
|
||||
.map(function(l) { var p = l.trim().split(/\s+/); return p[p.length-1] || ''; })
|
||||
.filter(Boolean);
|
||||
dataType = 'processes';
|
||||
} else if (scanType === 'network') {
|
||||
var r = await afPost('/android-forensics/acquire/getprop', {serial: afSerial});
|
||||
var props = r.props || {};
|
||||
items = Object.values(props).filter(function(v) { return v && (v.indexOf('.') > -1 || v.indexOf(':') > -1); }).slice(0, 80);
|
||||
dataType = 'network';
|
||||
} else if (scanType === 'files') {
|
||||
var r = await afPost('/android-forensics/acquire/tmp_files', {serial: afSerial});
|
||||
items = (r.output || '').split('\n').map(function(l) { var p = l.trim().split(/\s+/); return p[p.length-1] || ''; }).filter(Boolean);
|
||||
dataType = 'files';
|
||||
}
|
||||
} catch(e) { return []; }
|
||||
|
||||
if (!items.length) return [];
|
||||
try {
|
||||
var res = await fetch('/android-forensics/ai/heuristic', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({data_type: dataType, items: items.slice(0, 200), threshold: threshold})
|
||||
}).then(function(r) { return r.json(); });
|
||||
return (res.findings || []).map(function(f) {
|
||||
return {
|
||||
severity: f.score >= 0.8 ? 'high' : 'medium',
|
||||
type: 'heuristic',
|
||||
threat_name: f.category + ' [SLM]',
|
||||
detail: f.item,
|
||||
source: f.reason,
|
||||
heuristic: true,
|
||||
score: f.score
|
||||
};
|
||||
});
|
||||
} catch(e) { return []; }
|
||||
}
|
||||
|
||||
function afRenderFindings(findings, title) {
|
||||
var section = document.getElementById('af-scan-results-section');
|
||||
var clean = document.getElementById('af-scan-clean');
|
||||
var table = document.getElementById('af-scan-table');
|
||||
var tbody = document.getElementById('af-scan-body');
|
||||
var ioc = (findings || []).filter(function(f) { return !f.heuristic; });
|
||||
var heur = (findings || []).filter(function(f) { return f.heuristic; });
|
||||
document.getElementById('af-scan-title').textContent =
|
||||
(title || 'Findings') + ' — ' + ioc.length + ' IOC match(es)' + (heur.length ? ', ' + heur.length + ' heuristic(s)' : '');
|
||||
section.style.display = '';
|
||||
|
||||
if (!findings || findings.length === 0) {
|
||||
clean.style.display = 'block';
|
||||
table.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
clean.style.display = 'none';
|
||||
table.style.display = '';
|
||||
tbody.innerHTML = '';
|
||||
findings.forEach(function(f) {
|
||||
var sev = f.severity || 'high';
|
||||
var detail = f.package || f.process || f.path || f.remote_ip || f.detail || f.item || '';
|
||||
var hTag = f.heuristic
|
||||
? '<span class="heuristic-tag">' + Math.round((f.score || 0) * 100) + '%</span>'
|
||||
: '';
|
||||
var rowBg = f.heuristic ? ' style="background:rgba(241,196,15,0.04)"' : '';
|
||||
tbody.insertAdjacentHTML('beforeend',
|
||||
'<tr' + rowBg + '>'
|
||||
+ '<td><span class="sev-' + afEsc(sev) + '">' + afEsc(sev.toUpperCase()) + '</span></td>'
|
||||
+ '<td>' + hTag + afEsc(f.type || '') + '</td>'
|
||||
+ '<td>' + afEsc(f.threat_name || '') + '</td>'
|
||||
+ '<td><code style="font-size:0.82rem">' + afEsc(detail) + '</code></td>'
|
||||
+ '<td style="font-size:0.72rem;color:var(--text-muted,#888)" title="' + afEsc(f.source || '') + '">'
|
||||
+ afEsc((f.source || '').substring(0, 60)) + '</td>'
|
||||
+ '</tr>');
|
||||
});
|
||||
}
|
||||
|
||||
/* ── AI Analysis ────────────────────────────────────────────────── */
|
||||
function afAiAnalyze() {
|
||||
if (!afSerial && afMode === 'server') { afStatus('Select a device first.', 'warning'); return; }
|
||||
var backend = document.querySelector('input[name="af-ai-backend"]:checked').value;
|
||||
var focus = Array.from(document.querySelectorAll('.af-focus:checked')).map(function(el) { return el.value; });
|
||||
var acqId = document.getElementById('af-ai-acq-id').value.trim();
|
||||
var spinner = document.getElementById('af-ai-spinner');
|
||||
spinner.style.display = 'inline';
|
||||
document.getElementById('af-ai-results-section').style.display = 'none';
|
||||
fetch('/android-forensics/ai/analyze', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({serial: afSerial, acq_id: acqId, backend: backend, focus: focus})
|
||||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||||
spinner.style.display = 'none';
|
||||
if (!data.ok) { afStatus('AI error: ' + (data.error || 'unknown'), 'danger'); return; }
|
||||
var section = document.getElementById('af-ai-results-section');
|
||||
section.style.display = '';
|
||||
|
||||
if (data.mode === 'raw') {
|
||||
document.getElementById('af-ai-risk').textContent = 'RAW';
|
||||
document.getElementById('af-ai-summary').textContent = 'Raw acquisition dump — use for external analysis.';
|
||||
var raw = document.getElementById('af-ai-raw');
|
||||
raw.textContent = JSON.stringify(data.dump, null, 2);
|
||||
raw.style.display = '';
|
||||
document.getElementById('af-ai-findings-table').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
if (data.mode === 'ondevice') {
|
||||
document.getElementById('af-ai-risk').textContent = 'ON-DEVICE';
|
||||
document.getElementById('af-ai-summary').textContent = 'Send this prompt to Gemini Nano on the device.';
|
||||
var raw = document.getElementById('af-ai-raw');
|
||||
raw.textContent = data.prompt || '';
|
||||
raw.style.display = '';
|
||||
document.getElementById('af-ai-findings-table').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
// autarch mode — parse analysis
|
||||
var analysis = data.analysis || {};
|
||||
if (typeof analysis === 'string') {
|
||||
try { analysis = JSON.parse(analysis); } catch(e) {}
|
||||
}
|
||||
var risk = typeof analysis === 'object' ? (analysis.risk_level || 'unknown') : 'unknown';
|
||||
var riskColors = {critical:'#e74c3c', high:'#e67e22', medium:'#f1c40f', low:'#27ae60', clean:'#27ae60'};
|
||||
var riskEl = document.getElementById('af-ai-risk');
|
||||
riskEl.textContent = risk.toUpperCase();
|
||||
riskEl.style.color = riskColors[risk.toLowerCase()] || 'var(--text-primary)';
|
||||
document.getElementById('af-ai-summary').textContent = (typeof analysis === 'object' ? analysis.summary : String(analysis)) || '';
|
||||
|
||||
var aFindings = typeof analysis === 'object' ? (analysis.findings || []) : [];
|
||||
var ftable = document.getElementById('af-ai-findings-table');
|
||||
var ftbody = document.getElementById('af-ai-findings-body');
|
||||
document.getElementById('af-ai-raw').style.display = 'none';
|
||||
ftbody.innerHTML = '';
|
||||
if (aFindings.length) {
|
||||
aFindings.forEach(function(f) {
|
||||
var sev = f.severity || 'medium';
|
||||
ftbody.insertAdjacentHTML('beforeend',
|
||||
'<tr><td><span class="sev-' + afEsc(sev) + '">' + afEsc(sev.toUpperCase()) + '</span></td>'
|
||||
+ '<td>' + afEsc(f.type || '') + '</td>'
|
||||
+ '<td>' + afEsc(f.detail || '') + '</td>'
|
||||
+ '<td style="font-size:0.82rem">' + afEsc(f.recommendation || '') + '</td></tr>');
|
||||
});
|
||||
ftable.style.display = '';
|
||||
} else {
|
||||
ftable.style.display = 'none';
|
||||
if (!document.getElementById('af-ai-summary').textContent)
|
||||
document.getElementById('af-ai-summary').textContent = 'No findings reported.';
|
||||
}
|
||||
}).catch(function(e) { spinner.style.display = 'none'; afStatus('AI error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
/* ── CVE Lookup ─────────────────────────────────────────────────── */
|
||||
function afCveLookup() {
|
||||
var pkg = document.getElementById('af-cve-pkg').value.trim();
|
||||
if (!pkg) { afStatus('Enter a package name.', 'warning'); return; }
|
||||
var ver = document.getElementById('af-cve-ver').value.trim() || null;
|
||||
var spinner = document.getElementById('af-cve-spinner');
|
||||
spinner.style.display = 'inline';
|
||||
fetch('/android-forensics/ai/cve-check', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({packages: [{name: pkg, version: ver}]})
|
||||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||||
spinner.style.display = 'none';
|
||||
if (!data.ok) { afStatus('CVE error: ' + data.error, 'danger'); return; }
|
||||
afRenderCveFindings(data.findings || [], '1 package');
|
||||
}).catch(function(e) { spinner.style.display = 'none'; afStatus('CVE error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
function afCveCheckDevice() {
|
||||
if (!afSerial) { afStatus('Select a device first.', 'warning'); return; }
|
||||
var spinner = document.getElementById('af-cve-spinner');
|
||||
spinner.style.display = 'inline';
|
||||
afStatus('Fetching package list...', 'info');
|
||||
afPost('/android-forensics/acquire/packages', {serial: afSerial}).then(function(r) {
|
||||
var pkgs = (r.packages || []).map(function(p) { return {name: p.package}; });
|
||||
if (!pkgs.length) { spinner.style.display = 'none'; afStatus('No packages found.', 'warning'); return; }
|
||||
afStatus('Checking ' + pkgs.length + ' packages against CVE DB...', 'info');
|
||||
return fetch('/android-forensics/ai/cve-check', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({packages: pkgs})
|
||||
}).then(function(res) { return res.json(); });
|
||||
}).then(function(data) {
|
||||
if (!data) return;
|
||||
spinner.style.display = 'none';
|
||||
afRenderCveFindings(data.findings || [], data.packages_checked + ' packages');
|
||||
afStatus('CVE check complete. ' + (data.total || 0) + ' CVE(s) found.', (data.total || 0) > 0 ? 'danger' : 'success');
|
||||
}).catch(function(e) { spinner.style.display = 'none'; afStatus('CVE error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
function afRenderCveFindings(findings, label) {
|
||||
var section = document.getElementById('af-cve-results-section');
|
||||
document.getElementById('af-cve-summary').textContent = label + ' — ' + findings.length + ' CVE(s) found.';
|
||||
var tbody = document.getElementById('af-cve-body');
|
||||
tbody.innerHTML = '';
|
||||
if (!findings.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" style="color:var(--text-muted)">No CVEs found.</td></tr>';
|
||||
} else {
|
||||
findings.forEach(function(f) {
|
||||
var sev = f.severity || 'medium';
|
||||
tbody.insertAdjacentHTML('beforeend',
|
||||
'<tr><td style="font-size:0.82rem"><code>' + afEsc(f.package || '') + '</code></td>'
|
||||
+ '<td><b>' + afEsc(f.cve_id || '') + '</b></td>'
|
||||
+ '<td><span class="sev-' + afEsc(sev) + '">' + afEsc(sev.toUpperCase()) + '</span></td>'
|
||||
+ '<td style="font-size:0.82rem">' + afEsc((f.description || '').substring(0, 120)) + '</td>'
|
||||
+ '<td style="font-size:0.82rem">' + afEsc(f.patched_version || 'unknown') + '</td></tr>');
|
||||
});
|
||||
}
|
||||
section.style.display = '';
|
||||
}
|
||||
|
||||
function afCveStats() {
|
||||
fetch('/android-forensics/ai/cve-stats').then(function(r) { return r.json(); }).then(function(data) {
|
||||
var s = data.stats || {};
|
||||
document.getElementById('af-cvs-cached').textContent = s.total_cached || 0;
|
||||
document.getElementById('af-cvs-query').textContent = s.last_query ? s.last_query.split('T')[0] : 'never';
|
||||
document.getElementById('af-cve-stats-section').style.display = '';
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
/* ── Runtime Alerts ─────────────────────────────────────────────── */
|
||||
var afAlertTimer = null;
|
||||
|
||||
function afLoadAlerts() {
|
||||
fetch('/android-forensics/ai/alerts/list').then(function(r) { return r.json(); }).then(function(data) {
|
||||
var alerts = (data.alerts || []).slice().reverse();
|
||||
document.getElementById('af-alerts-count').textContent = 'Total: ' + (data.total || 0);
|
||||
var empty = document.getElementById('af-alerts-empty');
|
||||
var table = document.getElementById('af-alerts-table');
|
||||
var tbody = document.getElementById('af-alerts-body');
|
||||
if (!alerts.length) {
|
||||
empty.style.display = '';
|
||||
table.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
empty.style.display = 'none';
|
||||
table.style.display = '';
|
||||
tbody.innerHTML = '';
|
||||
alerts.forEach(function(a) {
|
||||
var sev = a.severity || 'medium';
|
||||
var ts = a.timestamp ? a.timestamp.replace('T', ' ').split('.')[0] : '';
|
||||
tbody.insertAdjacentHTML('beforeend',
|
||||
'<tr><td style="font-size:0.82rem">' + afEsc(ts) + '</td>'
|
||||
+ '<td style="font-size:0.82rem"><code>' + afEsc(a.serial || '') + '</code></td>'
|
||||
+ '<td><span class="sev-' + afEsc(sev) + '">' + afEsc(sev.toUpperCase()) + '</span></td>'
|
||||
+ '<td>' + afEsc(a.event_type || '') + '</td>'
|
||||
+ '<td style="font-size:0.82rem">' + afEsc(a.detail || '') + '</td></tr>');
|
||||
});
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
function afToggleAlertAutoRefresh() {
|
||||
var on = document.getElementById('af-alerts-autorefresh').checked;
|
||||
if (afAlertTimer) { clearInterval(afAlertTimer); afAlertTimer = null; }
|
||||
if (on) { afAlertTimer = setInterval(afLoadAlerts, 30000); }
|
||||
}
|
||||
|
||||
/* ── Reports ────────────────────────────────────────────────────── */
|
||||
function afLoadReports() {
|
||||
if (!afSerial) { afStatus('Select a device first.', 'warning'); return; }
|
||||
afPost('/android-forensics/report/list', {serial: afSerial})
|
||||
.then(function(data) {
|
||||
var acqs = data.acquisitions || [];
|
||||
var tbody = document.getElementById('af-reports-body');
|
||||
tbody.innerHTML = '';
|
||||
if (!acqs.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="color:var(--text-muted)">No acquisitions found.</td></tr>';
|
||||
} else {
|
||||
acqs.forEach(function(a) {
|
||||
var btnSerial = afEsc(afSerial);
|
||||
var btnId = afEsc(a.acq_id);
|
||||
tbody.insertAdjacentHTML('beforeend',
|
||||
'<tr>'
|
||||
+ '<td>' + afEsc(a.timestamp || a.acq_id) + '</td>'
|
||||
+ '<td style="font-size:0.82rem">' + afEsc((a.modules || []).join(', ')) + '</td>'
|
||||
+ '<td>' + (a.findings || 0) + '</td>'
|
||||
+ '<td><button class="btn btn-sm" onclick="afViewReport(\'' + btnSerial + '\',\'' + btnId + '\')">View</button></td>'
|
||||
+ '</tr>');
|
||||
});
|
||||
}
|
||||
document.getElementById('af-reports-section').style.display = '';
|
||||
})
|
||||
.catch(function(e) { afStatus('Error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
function afViewReport(serial, acqId) {
|
||||
fetch('/android-forensics/report/view', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({serial: serial, acq_id: acqId})
|
||||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||||
if (data.error) { afStatus('Error: ' + data.error, 'danger'); return; }
|
||||
document.getElementById('af-report-id').textContent = acqId;
|
||||
document.getElementById('af-report-json').textContent = JSON.stringify(data.report || data, null, 2);
|
||||
document.getElementById('af-report-viewer').style.display = '';
|
||||
}).catch(function(e) { afStatus('Error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
/* ── Database ───────────────────────────────────────────────────── */
|
||||
function afDbStats() {
|
||||
fetch('/android-forensics/db/stats')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
var c = data.counts || {};
|
||||
var s = data.sources || {};
|
||||
document.getElementById('af-st-packages').textContent = c.packages || 0;
|
||||
document.getElementById('af-st-domains').textContent = c.domains || 0;
|
||||
document.getElementById('af-st-certs').textContent = c.certs || 0;
|
||||
document.getElementById('af-st-hashes').textContent = c.hashes || 0;
|
||||
document.getElementById('af-st-ips').textContent = c.ips || 0;
|
||||
document.getElementById('af-st-processes').textContent = c.processes || 0;
|
||||
document.getElementById('af-st-filepaths').textContent = c.file_paths || 0;
|
||||
document.getElementById('af-st-yara').textContent = s.yara_rules || 0;
|
||||
var tbody = document.getElementById('af-db-sources');
|
||||
tbody.innerHTML = '';
|
||||
[
|
||||
['Stalkerware YAML', s.stalkerware_ioc_yaml ? 'Yes' : 'No'],
|
||||
['Amnesty campaigns', s.amnesty_investigations || 0],
|
||||
['MVT campaigns', s.mvt_campaigns || 0],
|
||||
['Citizen Lab', s.citizen_lab_campaigns || 0],
|
||||
['YARA rules', s.yara_rules || 0],
|
||||
].forEach(function(row) {
|
||||
tbody.insertAdjacentHTML('beforeend',
|
||||
'<tr><td>' + afEsc(row[0]) + '</td><td><b>' + afEsc(String(row[1])) + '</b></td></tr>');
|
||||
});
|
||||
document.getElementById('af-db-section').style.display = '';
|
||||
}).catch(function(e) { afStatus('Error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
function afDbReload() {
|
||||
afStatus('Reloading IOC database...', 'info');
|
||||
fetch('/android-forensics/db/reload', {method: 'POST'})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function() { afStatus('IOC database reloaded.', 'success'); afLoadBadges(); afDbStats(); })
|
||||
.catch(function(e) { afStatus('Error: ' + e, 'danger'); });
|
||||
}
|
||||
|
||||
/* ── Utility ────────────────────────────────────────────────────── */
|
||||
function afEsc(s) {
|
||||
return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -4,225 +4,300 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}AUTARCH{% endblock %}</title>
|
||||
<!-- Critical inline CSS — prevents white flash / FOUC if external sheet delays -->
|
||||
<style>
|
||||
:root{--bg-primary:#0f1117;--bg-secondary:#1a1d27;--bg-card:#222536;--bg-input:#2a2d3e;--border:#333650;--text-primary:#e4e6f0;--text-secondary:#8b8fa8;--text-muted:#5c6078;--accent:#6366f1;--accent-hover:#818cf8;--danger:#ef4444;--radius:8px}
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:var(--bg-primary);color:var(--text-primary);line-height:1.6}
|
||||
.layout{display:flex;min-height:100vh}
|
||||
.sidebar{width:240px;background:var(--bg-secondary);border-right:1px solid var(--border);display:flex;flex-direction:column;position:fixed;height:100vh;overflow-y:auto}
|
||||
.content{flex:1;margin-left:240px;padding:32px;max-width:1200px}
|
||||
.login-wrapper{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;padding:20px}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
</style>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/autarchos.css') }}">
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% if session.get('user') %}
|
||||
<div class="layout">
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>AUTARCH</h2>
|
||||
<span class="subtitle">Security Platform</span>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('dashboard.index') }}" class="{% if request.endpoint == 'dashboard.index' %}active{% endif %}">Dashboard</a></li>
|
||||
<li><a href="{{ url_for('port_scanner.index') }}" class="{% if request.blueprint == 'port_scanner' %}active{% endif %}">🔍 Port Scanner</a></li>
|
||||
<li><a href="{{ url_for('targets.index') }}" class="{% if request.blueprint == 'targets' %}active{% endif %}">Investigations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Categories</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('autonomy.index') }}" class="{% if request.blueprint == 'autonomy' %}active{% endif %}" style="color:var(--accent)">Autonomy</a></li>
|
||||
<li><a href="{{ url_for('defense.index') }}" class="{% if request.blueprint == 'defense' and request.endpoint not in ('defense.linux_index', 'defense.windows_index', 'defense.monitor_index') %}active{% endif %}">Defense</a></li>
|
||||
<li><a href="{{ url_for('defense.linux_index') }}" class="{% if request.endpoint == 'defense.linux_index' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Linux</a></li>
|
||||
<li><a href="{{ url_for('defense.windows_index') }}" class="{% if request.endpoint == 'defense.windows_index' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Windows</a></li>
|
||||
<li><a href="{{ url_for('defense.monitor_index') }}" class="{% if request.endpoint == 'defense.monitor_index' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Threat Monitor</a></li>
|
||||
<li><a href="{{ url_for('threat_intel.index') }}" class="{% if request.blueprint == 'threat_intel' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Threat Intel</a></li>
|
||||
<li><a href="{{ url_for('log_correlator.index') }}" class="{% if request.blueprint == 'log_correlator' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Log Correlator</a></li>
|
||||
<li><a href="{{ url_for('container_sec.index') }}" class="{% if request.blueprint == 'container_sec' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Container Sec</a></li>
|
||||
<li><a href="{{ url_for('email_sec.index') }}" class="{% if request.blueprint == 'email_sec' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Email Sec</a></li>
|
||||
<li><a href="{{ url_for('offense.index') }}" class="{% if request.blueprint == 'offense' %}active{% endif %}">Offense</a></li>
|
||||
<li><a href="{{ url_for('loadtest.index') }}" class="{% if request.blueprint == 'loadtest' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Load Test</a></li>
|
||||
<li><a href="{{ url_for('phishmail.index') }}" class="{% if request.blueprint == 'phishmail' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Gone Fishing</a></li>
|
||||
<li><a href="{{ url_for('social_eng.index') }}" class="{% if request.blueprint == 'social_eng' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Social Eng</a></li>
|
||||
<li><a href="{{ url_for('hack_hijack.index') }}" class="{% if request.blueprint == 'hack_hijack' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Hack Hijack</a></li>
|
||||
<li><a href="{{ url_for('webapp_scanner.index') }}" class="{% if request.blueprint == 'webapp_scanner' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Web Scanner</a></li>
|
||||
<li><a href="{{ url_for('c2_framework.index') }}" class="{% if request.blueprint == 'c2_framework' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ C2 Framework</a></li>
|
||||
<li><a href="{{ url_for('api_fuzzer.index') }}" class="{% if request.blueprint == 'api_fuzzer' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ API Fuzzer</a></li>
|
||||
<li><a href="{{ url_for('cloud_scan.index') }}" class="{% if request.blueprint == 'cloud_scan' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Cloud Scan</a></li>
|
||||
<li><a href="{{ url_for('vuln_scanner.index') }}" class="{% if request.blueprint == 'vuln_scanner' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Vuln Scanner</a></li>
|
||||
<li><a href="{{ url_for('exploit_dev.index') }}" class="{% if request.blueprint == 'exploit_dev' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Exploit Dev</a></li>
|
||||
<li><a href="{{ url_for('ad_audit.index') }}" class="{% if request.blueprint == 'ad_audit' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ AD Audit</a></li>
|
||||
<li><a href="{{ url_for('counter.index') }}" class="{% if request.blueprint == 'counter' %}active{% endif %}">Counter</a></li>
|
||||
<li><a href="{{ url_for('steganography.index') }}" class="{% if request.blueprint == 'steganography' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Steganography</a></li>
|
||||
<li><a href="{{ url_for('anti_forensics.index') }}" class="{% if request.blueprint == 'anti_forensics' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Anti-Forensics</a></li>
|
||||
<li><a href="{{ url_for('analyze.index') }}" class="{% if request.blueprint == 'analyze' and request.endpoint != 'analyze.hash_detection' %}active{% endif %}">Analyze</a></li>
|
||||
<li><a href="{{ url_for('analyze.hash_detection') }}" class="{% if request.endpoint == 'analyze.hash_detection' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Hash Toolkit</a></li>
|
||||
<li><a href="{{ url_for('llm_trainer.index') }}" class="{% if request.blueprint == 'llm_trainer' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ LLM Trainer</a></li>
|
||||
<li><a href="{{ url_for('password_toolkit.index') }}" class="{% if request.blueprint == 'password_toolkit' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Password Toolkit</a></li>
|
||||
<li><a href="{{ url_for('report_engine.index') }}" class="{% if request.blueprint == 'report_engine' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Reports</a></li>
|
||||
<li><a href="{{ url_for('ble_scanner.index') }}" class="{% if request.blueprint == 'ble_scanner' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ BLE Scanner</a></li>
|
||||
<li><a href="{{ url_for('forensics.index') }}" class="{% if request.blueprint == 'forensics' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Forensics</a></li>
|
||||
<li><a href="{{ url_for('rfid_tools.index') }}" class="{% if request.blueprint == 'rfid_tools' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ RFID/NFC</a></li>
|
||||
<li><a href="{{ url_for('malware_sandbox.index') }}" class="{% if request.blueprint == 'malware_sandbox' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Malware Sandbox</a></li>
|
||||
<li><a href="{{ url_for('reverse_eng.index') }}" class="{% if request.blueprint == 'reverse_eng' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Reverse Eng</a></li>
|
||||
<li><a href="{{ url_for('osint.index') }}" class="{% if request.blueprint == 'osint' %}active{% endif %}">OSINT</a></li>
|
||||
<li><a href="{{ url_for('ipcapture.index') }}" class="{% if request.blueprint == 'ipcapture' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ IP Capture</a></li>
|
||||
<li><a href="{{ url_for('simulate.index') }}" class="{% if request.blueprint == 'simulate' and request.endpoint != 'simulate.legendary_creator' %}active{% endif %}">Simulate</a></li>
|
||||
<li><a href="{{ url_for('simulate.legendary_creator') }}" class="{% if request.endpoint == 'simulate.legendary_creator' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Legendary Creator</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Network</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('network.index') }}" class="{% if request.blueprint == 'network' %}active{% endif %}">🛡 Network Security</a></li>
|
||||
<li><a href="{{ url_for('wireshark.index') }}" class="{% if request.blueprint == 'wireshark' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Wireshark</a></li>
|
||||
<li><a href="{{ url_for('net_mapper.index') }}" class="{% if request.blueprint == 'net_mapper' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Net Mapper</a></li>
|
||||
<li><a href="{{ url_for('wifi_audit.index') }}" class="{% if request.blueprint == 'wifi_audit' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ WiFi Audit</a></li>
|
||||
<li><a href="{{ url_for('deauth.index') }}" class="{% if request.blueprint == 'deauth' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem;color:var(--danger,#f55)">└ Deauth</a></li>
|
||||
<li><a href="{{ url_for('pineapple.index') }}" class="{% if request.blueprint == 'pineapple' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem;color:var(--danger,#f55)">└ Evil Twin / Pineapple</a></li>
|
||||
<li><a href="{{ url_for('mitm_proxy.index') }}" class="{% if request.blueprint == 'mitm_proxy' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem;color:var(--danger,#f55)">└ MITM Proxy</a></li>
|
||||
<li><a href="{{ url_for('remote_monitor.index') }}" class="{% if request.blueprint == 'remote_monitor' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Remote Monitor</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Tools</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('module_creator.index') }}" class="{% if request.blueprint == 'module_creator' %}active{% endif %}" style="color:var(--accent)">➕ Create Module</a></li>
|
||||
<li><a href="{{ url_for('encmodules.index') }}" class="{% if request.blueprint == 'encmodules' %}active{% endif %}" style="color:var(--danger,#f55)">🔒 Enc Modules</a></li>
|
||||
<li><a href="{{ url_for('hardware.index') }}" class="{% if request.blueprint == 'hardware' %}active{% endif %}">Hardware</a></li>
|
||||
<li><a href="{{ url_for('android_exploit.index') }}" class="{% if request.blueprint == 'android_exploit' %}active{% endif %}">Android Exploit</a></li>
|
||||
<li><a href="{{ url_for('sms_forge.index') }}" class="{% if request.blueprint == 'sms_forge' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ SMS Forge</a></li>
|
||||
<li><a href="{{ url_for('rcs_tools.index') }}" class="{% if request.blueprint == 'rcs_tools' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ RCS Tools</a></li>
|
||||
<li><a href="{{ url_for('iphone_exploit.index') }}" class="{% if request.blueprint == 'iphone_exploit' %}active{% endif %}">iPhone Exploit</a></li>
|
||||
<li><a href="{{ url_for('android_protect.index') }}" class="{% if request.blueprint == 'android_protect' %}active{% endif %}">Shield</a></li>
|
||||
<li><a href="{{ url_for('revshell.index') }}" class="{% if request.blueprint == 'revshell' %}active{% endif %}">Reverse Shell</a></li>
|
||||
<li><a href="{{ url_for('archon.index') }}" class="{% if request.blueprint == 'archon' %}active{% endif %}">Archon</a></li>
|
||||
<li><a href="{{ url_for('sdr_tools.index') }}" class="{% if request.blueprint == 'sdr_tools' %}active{% endif %}">SDR/RF Tools</a></li>
|
||||
<li><a href="{{ url_for('starlink_hack.index') }}" class="{% if request.blueprint == 'starlink_hack' %}active{% endif %}">Starlink Hack</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">System</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="{{ url_for('ssh_manager.index') }}" class="{% if request.blueprint == 'ssh_manager' %}active{% endif %}">SSH / SSHD</a></li>
|
||||
<li><a href="{{ url_for('upnp.index') }}" class="{% if request.blueprint == 'upnp' %}active{% endif %}">UPnP</a></li>
|
||||
<li><a href="{{ url_for('wireguard.index') }}" class="{% if request.blueprint == 'wireguard' %}active{% endif %}">WireGuard</a></li>
|
||||
<li><a href="{{ url_for('msf.index') }}" class="{% if request.blueprint == 'msf' %}active{% endif %}">MSF Console</a></li>
|
||||
<li><a href="{{ url_for('dns_service.index') }}" class="{% if request.blueprint == 'dns_service' and request.endpoint != 'dns_service.nameserver' %}active{% endif %}">DNS Server</a></li>
|
||||
<li><a href="{{ url_for('dns_service.nameserver') }}" class="{% if request.endpoint == 'dns_service.nameserver' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Nameserver</a></li>
|
||||
<li><a href="{{ url_for('settings.index') }}" class="{% if request.blueprint == 'settings' and request.endpoint not in ('settings.llm_settings', 'settings.deps_index') %}active{% endif %}">Settings</a></li>
|
||||
<li><a href="{{ url_for('settings.llm_settings') }}" class="{% if request.endpoint == 'settings.llm_settings' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ LLM Config</a></li>
|
||||
<li><a href="{{ url_for('settings.mcp_settings') }}" class="{% if request.endpoint == 'settings.mcp_settings' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ MCP Server</a></li>
|
||||
<li><a href="{{ url_for('settings.deps_index') }}" class="{% if request.endpoint == 'settings.deps_index' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Dependencies</a></li>
|
||||
<li><a href="{{ url_for('dashboard.manual') }}" class="{% if request.endpoint == 'dashboard.manual' %}active{% endif %}">User Manual</a></li>
|
||||
<li><a href="{{ url_for('dashboard.manual_windows') }}" class="{% if request.endpoint == 'dashboard.manual_windows' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">└ Windows Guide</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="padding:6px 12px">
|
||||
<button id="btn-reload-modules" onclick="reloadModules()" class="btn btn-small"
|
||||
style="width:100%;font-size:0.75rem;padding:5px 0;background:var(--bg-input);border:1px solid var(--border);color:var(--text-secondary)"
|
||||
title="Re-scan modules/ directory for new or changed modules">
|
||||
↻ Refresh Modules
|
||||
</button>
|
||||
</div>
|
||||
<div class="sidebar-footer">
|
||||
<span class="admin-name">{{ session.get('user', 'admin') }}</span>
|
||||
<a href="{{ url_for('auth.logout') }}" class="logout-link">Logout</a>
|
||||
<div style="margin-top:0.55rem;padding:0.4rem 0.5rem;border-top:1px solid rgba(255,255,255,0.06);
|
||||
font-size:0.62rem;color:#555;line-height:1.4;text-align:center">
|
||||
🔒 RESTRICTED PUBLIC RELEASE<br>
|
||||
Certain features are limited or disabled.<br>
|
||||
Authorized use only — activity is logged.
|
||||
<body data-blueprint="{{ request.blueprint or '' }}">
|
||||
{% if session.get('user') %}
|
||||
<div class="desktop-bg"></div>
|
||||
<div class="watermark">Λ</div>
|
||||
|
||||
<!-- ── Top system bar ─────────────────────────────────────── -->
|
||||
<div class="aos-topbar">
|
||||
<a class="logo" href="{{ url_for('dashboard.index') }}">
|
||||
<span class="mk">Λ</span>Aut<b>archOS</b>
|
||||
</a>
|
||||
<a class="mi" href="#" data-aos-action="open-launcher">Apps</a>
|
||||
<a class="mi" href="{{ url_for('settings.index') }}">Settings</a>
|
||||
<a class="mi" href="{{ url_for('dashboard.manual') }}">Help</a>
|
||||
<span class="sp"></span>
|
||||
<span class="posture">
|
||||
<span class="pulse" style="background:var(--amber);box-shadow:0 0 10px rgba(255,177,61,.6)"></span>
|
||||
Authed
|
||||
</span>
|
||||
<span class="tray">
|
||||
<svg class="shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 3l8 3v5c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z"/></svg>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M5 12.5a10 10 0 0 1 14 0M8 15.5a6 6 0 0 1 8 0M12 19h.01"/></svg>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><rect x="3" y="8" width="18" height="8" rx="1"/><path d="M16 12h2"/></svg>
|
||||
</span>
|
||||
<span class="user">
|
||||
<b>{{ session.get('user', 'admin') }}</b>
|
||||
</span>
|
||||
<span class="clock" id="aos-clock">--:--:--</span>
|
||||
</div>
|
||||
|
||||
<!-- ── Workspace ─────────────────────────────────────────── -->
|
||||
<main class="aos-workspace">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- ── Dock ──────────────────────────────────────────────── -->
|
||||
<nav class="aos-dock">
|
||||
<a class="app" data-app="dashboard" href="{{ url_for('dashboard.index') }}">
|
||||
<div class="tip">Overview</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>
|
||||
</a>
|
||||
<a class="app" data-app="defense" href="{{ url_for('defense.index') }}">
|
||||
<div class="tip">Defense</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 3l8 3v5c0 5-3.5 8-8 10-4.5-2-8-5-8-10V6z"/></svg>
|
||||
</a>
|
||||
<a class="app" data-app="offense" href="{{ url_for('offense.index') }}">
|
||||
<div class="tip">Offense</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 4l-7 8 4 0-2 8 7-8-4 0z"/></svg>
|
||||
</a>
|
||||
<a class="app" data-app="counter" href="{{ url_for('counter.index') }}">
|
||||
<div class="tip">Counter</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>
|
||||
</a>
|
||||
<a class="app" data-app="net_mapper" href="{{ url_for('net_mapper.index') }}">
|
||||
<div class="tip">Topology</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="5" r="2.2"/><circle cx="5" cy="18" r="2.2"/><circle cx="19" cy="18" r="2.2"/><path d="M12 7.2 6.5 15.8M12 7.2l5.5 8.6"/></svg>
|
||||
</a>
|
||||
<a class="app" data-app="android_forensics" href="{{ url_for('android_forensics.index') }}">
|
||||
<div class="tip">Forensics</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="6" y="3" width="12" height="18" rx="2"/><path d="M10 18h4"/></svg>
|
||||
</a>
|
||||
<a class="app" data-app="msf" href="{{ url_for('msf.index') }}">
|
||||
<div class="tip">MSF Console</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="4" width="18" height="16" rx="1"/><path d="M7 9l3 3-3 3M13 15h4"/></svg>
|
||||
</a>
|
||||
<span class="divider"></span>
|
||||
<a class="app" data-app="chat" href="#" onclick="if(typeof halToggle==='function'){halToggle();}return false;">
|
||||
<div class="tip">HAL</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="6" width="16" height="12" rx="1.5"/><path d="M9 11h.01M15 11h.01M9 15h6"/></svg>
|
||||
</a>
|
||||
<a class="app" data-app="settings" href="{{ url_for('settings.index') }}">
|
||||
<div class="tip">Settings</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1A2 2 0 1 1 7 4.6l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>
|
||||
</a>
|
||||
<a class="app" data-aos-action="open-launcher" href="#">
|
||||
<div class="tip">All Apps</div>
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="5" cy="5" r="2"/><circle cx="12" cy="5" r="2"/><circle cx="19" cy="5" r="2"/><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/><circle cx="5" cy="19" r="2"/><circle cx="12" cy="19" r="2"/><circle cx="19" cy="19" r="2"/></svg>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- ── Apps launcher modal ───────────────────────────────── -->
|
||||
<div class="aos-launcher-backdrop" id="aos-launcher">
|
||||
<div class="aos-launcher">
|
||||
<h2>All Apps</h2>
|
||||
<input id="aos-launcher-search" class="search" type="text" placeholder="search apps... (Ctrl+Space to toggle)">
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">Core</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('dashboard.index') }}"><span class="ico">▦</span><span class="meta"><b>Dashboard</b><small>Command overview</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('autonomy.index') }}"><span class="ico">⚙</span><span class="meta"><b>Autonomy</b><small>Autonomous HAL agent</small></span></a>
|
||||
<a class="app-card" href="#" onclick="if(typeof halToggle==='function'){halToggle();}return false;"><span class="ico">◉</span><span class="meta"><b>HAL Chat</b><small>AI copilot</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('targets.index') }}"><span class="ico">⊕</span><span class="meta"><b>Investigations</b><small>Target dossiers</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('port_scanner.index') }}"><span class="ico">◐</span><span class="meta"><b>Port Scanner</b><small>Quick host scan</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">Defense</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('defense.index') }}"><span class="ico">▤</span><span class="meta"><b>Defense</b><small>Hardening & audits</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('defense.linux_index') }}"><span class="ico">└</span><span class="meta"><b>Linux Audit</b><small>System hardening</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('defense.windows_index') }}"><span class="ico">└</span><span class="meta"><b>Windows Audit</b><small>System hardening</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('defense.monitor_index') }}"><span class="ico">⊙</span><span class="meta"><b>Threat Monitor</b><small>Live detection</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('threat_intel.index') }}"><span class="ico">◬</span><span class="meta"><b>Threat Intel</b><small>IoC lookups</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('log_correlator.index') }}"><span class="ico">▥</span><span class="meta"><b>Log Correlator</b><small>SIEM-style</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('container_sec.index') }}"><span class="ico">▣</span><span class="meta"><b>Container Sec</b><small>Docker / Podman</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('email_sec.index') }}"><span class="ico">✉</span><span class="meta"><b>Email Sec</b><small>SPF / DKIM / DMARC</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">Offense</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('offense.index') }}"><span class="ico">⚔</span><span class="meta"><b>Offense</b><small>Pentesting tools</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('msf.index') }}"><span class="ico">⌗</span><span class="meta"><b>MSF Console</b><small>Metasploit</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('exploit_dev.index') }}"><span class="ico">⌬</span><span class="meta"><b>Exploit Dev</b><small>Workshop</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('vuln_scanner.index') }}"><span class="ico">◯</span><span class="meta"><b>Vuln Scanner</b><small>CVE scanner</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('webapp_scanner.index') }}"><span class="ico">◍</span><span class="meta"><b>Web Scanner</b><small>WebApp audit</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('c2_framework.index') }}"><span class="ico">⚐</span><span class="meta"><b>C2 Framework</b><small>Command & control</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('api_fuzzer.index') }}"><span class="ico">≋</span><span class="meta"><b>API Fuzzer</b><small>API testing</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('cloud_scan.index') }}"><span class="ico">☁</span><span class="meta"><b>Cloud Scan</b><small>AWS / Azure / GCP</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('ad_audit.index') }}"><span class="ico">⛒</span><span class="meta"><b>AD Audit</b><small>Active Directory</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('hack_hijack.index') }}"><span class="ico">◑</span><span class="meta"><b>Hack Hijack</b><small>Session takeover</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('phishmail.index') }}"><span class="ico">⌖</span><span class="meta"><b>Phishmail</b><small>Phishing simulation</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('social_eng.index') }}"><span class="ico">⛁</span><span class="meta"><b>Social Eng</b><small>SET toolkit</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('loadtest.index') }}"><span class="ico">⏚</span><span class="meta"><b>Load Test</b><small>Stress testing</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('password_toolkit.index') }}"><span class="ico">⚷</span><span class="meta"><b>Password Toolkit</b><small>Hashes & cracking</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('revshell.index') }}"><span class="ico">⌥</span><span class="meta"><b>Reverse Shell</b><small>Multi-language gen</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">Counter / Analyze</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('counter.index') }}"><span class="ico">⌗</span><span class="meta"><b>Counter</b><small>Threat hunting</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('analyze.index') }}"><span class="ico">⌕</span><span class="meta"><b>Analyze</b><small>Forensics & hex</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('analyze.hash_detection') }}"><span class="ico">⌖</span><span class="meta"><b>Hash Toolkit</b><small>Hash ID & crack</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('forensics.index') }}"><span class="ico">⌬</span><span class="meta"><b>Forensics</b><small>File analysis</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('anti_forensics.index') }}"><span class="ico">⌧</span><span class="meta"><b>Anti-Forensics</b><small>Evidence removal</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('steganography.index') }}"><span class="ico">⌗</span><span class="meta"><b>Steganography</b><small>Hidden data</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('malware_sandbox.index') }}"><span class="ico">⌘</span><span class="meta"><b>Malware Sandbox</b><small>Behavior analysis</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('reverse_eng.index') }}"><span class="ico">↺</span><span class="meta"><b>Reverse Eng</b><small>Disassembly</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('android_forensics.index') }}"><span class="ico">◫</span><span class="meta"><b>Android Forensics</b><small>Acquisition & IOC</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">OSINT & Simulate</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('osint.index') }}"><span class="ico">⌥</span><span class="meta"><b>OSINT</b><small>25,475 indexed sites</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('ipcapture.index') }}"><span class="ico">⊜</span><span class="meta"><b>IP Capture</b><small>Honey-link</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('simulate.index') }}"><span class="ico">⌕</span><span class="meta"><b>Simulate</b><small>Attack simulation</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('simulate.legendary_creator') }}"><span class="ico">⋄</span><span class="meta"><b>Legendary Creator</b><small>Profile builder</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">Network</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('network.index') }}"><span class="ico">▦</span><span class="meta"><b>Network Security</b><small>8-tab suite</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('net_mapper.index') }}"><span class="ico">⌬</span><span class="meta"><b>Net Mapper</b><small>Topology</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('wireshark.index') }}"><span class="ico">◯</span><span class="meta"><b>Wireshark</b><small>Packet capture</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('wifi_audit.index') }}"><span class="ico">≋</span><span class="meta"><b>WiFi Audit</b><small>Wireless scan</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('deauth.index') }}"><span class="ico">⌅</span><span class="meta"><b>Deauth</b><small>WiFi disruption</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('pineapple.index') }}"><span class="ico">⌖</span><span class="meta"><b>Pineapple</b><small>Evil twin</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('mitm_proxy.index') }}"><span class="ico">⌗</span><span class="meta"><b>MITM Proxy</b><small>Intercept</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('remote_monitor.index') }}"><span class="ico">◉</span><span class="meta"><b>Remote Monitor</b><small>PIAP devices</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">Hardware & Mobile</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('hardware.index') }}"><span class="ico">⌘</span><span class="meta"><b>Hardware</b><small>ADB / Fastboot / ESP32</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('android_exploit.index') }}"><span class="ico">▤</span><span class="meta"><b>Android Exploit</b><small>Recon & payload</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('android_protect.index') }}"><span class="ico">⛨</span><span class="meta"><b>Android Shield</b><small>Anti-stalkerware</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('iphone_exploit.index') }}"><span class="ico">▢</span><span class="meta"><b>iPhone Exploit</b><small>iOS research</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('sms_forge.index') }}"><span class="ico">✉</span><span class="meta"><b>SMS Forge</b><small>SMS / RCS</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('rcs_tools.index') }}"><span class="ico">✉</span><span class="meta"><b>RCS Tools</b><small>Rich messaging</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('ble_scanner.index') }}"><span class="ico">⌬</span><span class="meta"><b>BLE Scanner</b><small>Bluetooth LE</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('rfid_tools.index') }}"><span class="ico">⊜</span><span class="meta"><b>RFID / NFC</b><small>Proximity tools</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('sdr_tools.index') }}"><span class="ico">≋</span><span class="meta"><b>SDR Tools</b><small>Software-defined radio</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('starlink_hack.index') }}"><span class="ico">⌬</span><span class="meta"><b>Starlink</b><small>Satellite research</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('archon.index') }}"><span class="ico">Λ</span><span class="meta"><b>Archon</b><small>Companion app</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group">
|
||||
<div class="group-title">System</div>
|
||||
<div class="apps">
|
||||
<a class="app-card" href="{{ url_for('settings.index') }}"><span class="ico">⚙</span><span class="meta"><b>Settings</b><small>System config</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('settings.llm_settings') }}"><span class="ico">⌗</span><span class="meta"><b>LLM Config</b><small>Backend & models</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('settings.mcp_settings') }}"><span class="ico">⌘</span><span class="meta"><b>MCP Server</b><small>Claude / MCP</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('settings.deps_index') }}"><span class="ico">⊞</span><span class="meta"><b>Dependencies</b><small>System tools</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('ssh_manager.index') }}"><span class="ico">⌬</span><span class="meta"><b>SSH / SSHD</b><small>Key management</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('upnp.index') }}"><span class="ico">⌃</span><span class="meta"><b>UPnP</b><small>Port forwarding</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('wireguard.index') }}"><span class="ico">⌕</span><span class="meta"><b>WireGuard</b><small>VPN tunnels</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('dns_service.index') }}"><span class="ico">⌬</span><span class="meta"><b>DNS Server</b><small>Local resolver</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('report_engine.index') }}"><span class="ico">⌗</span><span class="meta"><b>Reports</b><small>Output engine</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('module_creator.index') }}"><span class="ico">+</span><span class="meta"><b>Module Creator</b><small>Build modules</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('encmodules.index') }}"><span class="ico">⌗</span><span class="meta"><b>Enc Modules</b><small>Encrypted modules</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('llm_trainer.index') }}"><span class="ico">⌬</span><span class="meta"><b>LLM Trainer</b><small>Fine-tuning</small></span></a>
|
||||
<a class="app-card" href="{{ url_for('dashboard.manual') }}"><span class="ico">⌘</span><span class="meta"><b>User Manual</b><small>Docs</small></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% else %}
|
||||
<div class="login-wrapper">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block login_content %}{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<script src="{{ url_for('static', filename='js/lib/adb-bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/lib/fastboot-bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/lib/esptool-bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/hardware-direct.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
<!-- Agent Hal Global Chat Panel -->
|
||||
{% if session.get('user') %}
|
||||
<div id="hal-panel" class="hal-panel" style="display:none">
|
||||
<div class="hal-header">
|
||||
<span>● Hal</span>
|
||||
<label class="hal-mode-switch" title="Toggle Agent mode (tools) vs Direct chat">
|
||||
<input type="checkbox" id="hal-mode-toggle" onchange="halModeChanged(this)">
|
||||
<span class="hal-mode-slider"></span>
|
||||
<span class="hal-mode-label" id="hal-mode-label">Chat</span>
|
||||
</label>
|
||||
<button onclick="halToggleFeedback()" id="hal-feedback-btn" class="hal-feedback-btn"
|
||||
title="Toggle auto-analysis feedback on tool results"
|
||||
style="font-size:0.6rem;padding:1px 5px;border-radius:3px;border:1px solid var(--accent);color:var(--accent);background:transparent;cursor:pointer">FB</button>
|
||||
<button onclick="halToggle()" class="hal-close" title="Close">✕</button>
|
||||
<script src="{{ url_for('static', filename='js/lib/adb-bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/lib/fastboot-bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/lib/esptool-bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/hardware-direct.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/autarchos.js') }}"></script>
|
||||
|
||||
{% if session.get('user') %}
|
||||
<!-- ── HAL global panel ─────────────────────────────────────── -->
|
||||
<div id="hal-panel" class="hal-panel" style="display:none">
|
||||
<div class="hal-header">
|
||||
<span>● Hal</span>
|
||||
<label class="hal-mode-switch" title="Toggle Agent mode (tools) vs Direct chat">
|
||||
<input type="checkbox" id="hal-mode-toggle" onchange="halModeChanged(this)">
|
||||
<span class="hal-mode-slider"></span>
|
||||
<span class="hal-mode-label" id="hal-mode-label">Chat</span>
|
||||
</label>
|
||||
<button onclick="halToggleFeedback()" id="hal-feedback-btn" class="hal-feedback-btn"
|
||||
title="Toggle auto-analysis feedback on tool results"
|
||||
style="font-size:0.6rem;padding:1px 5px;border-radius:3px;border:1px solid var(--accent);color:var(--accent);background:transparent;cursor:pointer">FB</button>
|
||||
<button onclick="halToggle()" class="hal-close" title="Close">✕</button>
|
||||
</div>
|
||||
<div id="hal-messages" class="hal-messages"></div>
|
||||
<div class="hal-footer">
|
||||
<input id="hal-input" type="text" placeholder="Ask Hal..." onkeypress="if(event.key==='Enter')halSend()">
|
||||
<button onclick="halSend()" class="btn btn-sm btn-primary" id="hal-send-btn">Send</button>
|
||||
<button onclick="halStop()" class="btn btn-sm" id="hal-stop-btn" title="Stop generation" style="display:none;background:var(--danger,#ff3b30);color:#fff">Stop</button>
|
||||
<button onclick="halClear()" class="btn btn-sm" title="Clear history">↺</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="hal-toggle-btn" class="hal-toggle-btn" onclick="halToggle()">HAL</button>
|
||||
|
||||
<!-- ── Debug console ─────────────────────────────────────── -->
|
||||
<div id="debug-panel" class="debug-panel" style="display:none">
|
||||
<div class="debug-header" id="debug-drag-handle">
|
||||
<div style="display:flex;align-items:center;gap:0.5rem">
|
||||
<span id="debug-live-dot" class="debug-live-dot"></span>
|
||||
<span style="font-weight:700;letter-spacing:0.08em;font-size:0.8rem">AUTARCH DEBUG</span>
|
||||
</div>
|
||||
<div id="hal-messages" class="hal-messages"></div>
|
||||
<div class="hal-footer">
|
||||
<input id="hal-input" type="text" placeholder="Ask Hal..." onkeypress="if(event.key==='Enter')halSend()">
|
||||
<button onclick="halSend()" class="btn btn-sm btn-primary" id="hal-send-btn">Send</button>
|
||||
<button onclick="halStop()" class="btn btn-sm" id="hal-stop-btn" title="Stop generation" style="display:none;background:var(--danger,#ff3b30);color:#fff">Stop</button>
|
||||
<button onclick="halClear()" class="btn btn-sm" title="Clear history">↺</button>
|
||||
<div style="display:flex;align-items:center;gap:0.5rem">
|
||||
<span id="debug-msg-count" style="font-size:0.7rem;color:#666;font-family:monospace">0 msgs</span>
|
||||
<button class="debug-btn" onclick="debugClear()" title="Clear output">⌫</button>
|
||||
<button class="debug-btn" onclick="debugClose()" title="Close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="hal-toggle-btn" class="hal-toggle-btn" onclick="halToggle()">HAL</button>
|
||||
|
||||
<!-- Debug Console Window -->
|
||||
<div id="debug-panel" class="debug-panel" style="display:none">
|
||||
<div class="debug-header" id="debug-drag-handle">
|
||||
<div style="display:flex;align-items:center;gap:0.5rem">
|
||||
<span id="debug-live-dot" class="debug-live-dot"></span>
|
||||
<span style="font-weight:700;letter-spacing:0.08em;font-size:0.8rem">AUTARCH DEBUG</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:0.5rem">
|
||||
<span id="debug-msg-count" style="font-size:0.7rem;color:#666;font-family:monospace">0 msgs</span>
|
||||
<button class="debug-btn" onclick="debugClear()" title="Clear output">⌫</button>
|
||||
<button class="debug-btn" onclick="debugClose()" title="Close">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="debug-output" class="debug-output"></div>
|
||||
<div class="debug-controls">
|
||||
<span style="font-size:0.7rem;color:#555;margin-right:0.5rem;font-weight:600">FILTER:</span>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="warn" checked onchange="debugSetMode(this)"> Warnings & Errors</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="verbose" onchange="debugSetMode(this)"> Full Verbose</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="debug" onchange="debugSetMode(this)"> Full Debug + Symbols</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="output" onchange="debugSetMode(this)"> Output Only</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="all" onchange="debugSetMode(this)"> Show Everything</label>
|
||||
</div>
|
||||
<div id="debug-output" class="debug-output"></div>
|
||||
<div class="debug-controls">
|
||||
<span style="font-size:0.7rem;color:#555;margin-right:0.5rem;font-weight:600">FILTER:</span>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="warn" checked onchange="debugSetMode(this)"> Warnings & Errors</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="verbose" onchange="debugSetMode(this)"> Full Verbose</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="debug" onchange="debugSetMode(this)"> Full Debug + Symbols</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="output" onchange="debugSetMode(this)"> Output Only</label>
|
||||
<label class="debug-check-label"><input type="checkbox" name="dbg-mode" value="all" onchange="debugSetMode(this)"> Show Everything</label>
|
||||
</div>
|
||||
<button id="debug-toggle-btn" class="debug-toggle-btn" style="display:none" onclick="debugOpen()" title="Open Debug Console">DBG</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button id="debug-toggle-btn" class="debug-toggle-btn" style="display:none" onclick="debugOpen()" title="Open Debug Console">DBG</button>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,39 +1,41 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Dashboard - AUTARCH{% endblock %}
|
||||
{% block title %}Command Overview — AUTARCH{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>Dashboard</h1>
|
||||
<h1>Command Overview</h1>
|
||||
<div class="subtitle">{{ system.hostname }} · {{ system.platform }} · {{ system.arch }}</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Modules</div>
|
||||
<div class="stat-value">{{ modules.get('total', 0) }}</div>
|
||||
<div class="kpis">
|
||||
<div class="kpi">
|
||||
<div class="l">Modules</div>
|
||||
<div class="n">{{ modules.get('total', 0) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">LLM Backend</div>
|
||||
<div class="stat-value small">{{ llm_backend }}</div>
|
||||
<div class="kpi">
|
||||
<div class="l">LLM Backend</div>
|
||||
<div class="n" style="font-size:18px">{{ llm_backend|upper }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Hostname</div>
|
||||
<div class="stat-value small">{{ system.hostname }}</div>
|
||||
<div class="kpi {% if not upnp_enabled %}warn{% endif %}">
|
||||
<div class="l">UPnP</div>
|
||||
<div class="n" style="font-size:18px">{{ 'ON' if upnp_enabled else 'OFF' }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Uptime</div>
|
||||
<div class="stat-value small">{{ system.uptime }}</div>
|
||||
<div class="kpi">
|
||||
<div class="l">Uptime</div>
|
||||
<div class="n" style="font-size:18px">{{ system.uptime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sec">Categories · Quick Launch</div>
|
||||
<div class="category-grid">
|
||||
<a href="{{ url_for('defense.index') }}" class="category-card cat-defense">
|
||||
<h3>Defense</h3>
|
||||
<p>System hardening, audits, monitoring</p>
|
||||
<p>Hardening, audits, monitoring</p>
|
||||
<span class="badge">{{ modules.get('defense', 0) }} modules</span>
|
||||
</a>
|
||||
<a href="{{ url_for('offense.index') }}" class="category-card cat-offense">
|
||||
<h3>Offense</h3>
|
||||
<p>Penetration testing, Metasploit</p>
|
||||
<p>Pentesting, Metasploit</p>
|
||||
<span class="badge">{{ modules.get('offense', 0) }} modules</span>
|
||||
</a>
|
||||
<a href="{{ url_for('counter.index') }}" class="category-card cat-counter">
|
||||
@@ -43,12 +45,12 @@
|
||||
</a>
|
||||
<a href="{{ url_for('analyze.index') }}" class="category-card cat-analyze">
|
||||
<h3>Analyze</h3>
|
||||
<p>Forensics, file analysis</p>
|
||||
<p>Forensics, hex, hashes</p>
|
||||
<span class="badge">{{ modules.get('analyze', 0) }} modules</span>
|
||||
</a>
|
||||
<a href="{{ url_for('osint.index') }}" class="category-card cat-osint">
|
||||
<h3>OSINT</h3>
|
||||
<p>Reconnaissance, username search</p>
|
||||
<p>25,475 indexed sites</p>
|
||||
<span class="badge">{{ modules.get('osint', 0) }} modules</span>
|
||||
</a>
|
||||
<a href="{{ url_for('simulate.index') }}" class="category-card cat-simulate">
|
||||
@@ -58,36 +60,61 @@
|
||||
</a>
|
||||
<a href="{{ url_for('hardware.index') }}" class="category-card cat-hardware">
|
||||
<h3>Hardware</h3>
|
||||
<p>ADB, Fastboot, ESP32 flashing</p>
|
||||
<p>ADB, Fastboot, ESP32</p>
|
||||
<span class="badge">{{ modules.get('hardware', 0) }} modules</span>
|
||||
</a>
|
||||
<a href="{{ url_for('android_forensics.index') }}" class="category-card cat-counter">
|
||||
<h3>Android Forensics</h3>
|
||||
<p>Acquisition · IOC scanning</p>
|
||||
<span class="badge">new</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>System Tools</h2>
|
||||
<table class="data-table">
|
||||
<thead><tr><th>Tool</th><th>Status</th></tr></thead>
|
||||
<tbody>
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px;margin-top:22px">
|
||||
<div>
|
||||
<div class="sec">Traffic 24h<span class="live"><span class="pulse"></span>Idle</span></div>
|
||||
<div class="spark" id="aos-dash-spark"></div>
|
||||
<div class="insight">
|
||||
<div class="ih"><span class="pulse"></span>AI Synthesis</div>
|
||||
HAL standing by. {{ modules.get('total', 0) }} modules loaded across
|
||||
{{ modules.keys()|length - 1 }} categories. Ask HAL for a sweep, threat triage,
|
||||
or to walk a specific scan.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="sec">System Info</div>
|
||||
<table class="data-table">
|
||||
<tbody>
|
||||
<tr><td>Platform</td><td>{{ system.platform }}</td></tr>
|
||||
<tr><td>Architecture</td><td>{{ system.arch }}</td></tr>
|
||||
<tr><td>Python</td><td>{{ system.python }}</td></tr>
|
||||
<tr><td>IP</td><td>{{ system.ip }}</td></tr>
|
||||
<tr><td>UPnP</td><td><span class="status-dot {{ 'active' if upnp_enabled else 'inactive' }}"></span>{{ 'Enabled' if upnp_enabled else 'Disabled' }}</td></tr>
|
||||
{% if llm_model %}<tr><td>LLM Model</td><td style="font-size:10.5px">{{ llm_model.split('/')[-1] if '/' in llm_model else llm_model }}</td></tr>{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sec" style="margin-top:22px">System Tools</div>
|
||||
<table class="data-table">
|
||||
<thead><tr><th>Tool</th><th>Status</th></tr></thead>
|
||||
<tbody>
|
||||
{% for tool, available in tools.items() %}
|
||||
<tr>
|
||||
<td>{{ tool }}</td>
|
||||
<td><span class="status-dot {{ 'active' if available else 'inactive' }}"></span>{{ 'Available' if available else 'Not found' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="section">
|
||||
<h2>System Info</h2>
|
||||
<table class="data-table">
|
||||
<tbody>
|
||||
<tr><td>Platform</td><td>{{ system.platform }}</td></tr>
|
||||
<tr><td>Architecture</td><td>{{ system.arch }}</td></tr>
|
||||
<tr><td>Python</td><td>{{ system.python }}</td></tr>
|
||||
<tr><td>IP</td><td>{{ system.ip }}</td></tr>
|
||||
<tr><td>UPnP</td><td><span class="status-dot {{ 'active' if upnp_enabled else 'inactive' }}"></span>{{ 'Enabled' if upnp_enabled else 'Disabled' }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
if (window.aosRenderSpark) {
|
||||
window.aosRenderSpark(document.getElementById('aos-dash-spark'), 40, [12, 15]);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user