"""Android Protection Shield routes — anti-stalkerware/spyware scanning and remediation.""" import os from flask import Blueprint, render_template, request, jsonify from web.auth import login_required android_protect_bp = Blueprint('android_protect', __name__, url_prefix='/android-protect') def _mgr(): from core.android_protect import get_android_protect_manager return get_android_protect_manager() def _serial(): """Extract serial from JSON body, form data, or query params.""" data = request.get_json(silent=True) or {} serial = data.get('serial') or request.form.get('serial') or request.args.get('serial', '') return str(serial).strip() if serial else '' # ── Main Page ─────────────────────────────────────────────────────── @android_protect_bp.route('/') @login_required def index(): from core.hardware import get_hardware_manager hw = get_hardware_manager() status = hw.get_status() sig_stats = _mgr().get_signature_stats() return render_template('android_protect.html', status=status, sig_stats=sig_stats) # ── Scan Routes ───────────────────────────────────────────────────── @android_protect_bp.route('/scan/quick', methods=['POST']) @login_required def scan_quick(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().quick_scan(serial)) @android_protect_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_protection_scan(serial)) @android_protect_bp.route('/scan/export', methods=['POST']) @login_required def scan_export(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) scan = _mgr().full_protection_scan(serial) return jsonify(_mgr().export_scan_report(serial, scan)) @android_protect_bp.route('/scan/stalkerware', methods=['POST']) @login_required def scan_stalkerware(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_stalkerware(serial)) @android_protect_bp.route('/scan/hidden', methods=['POST']) @login_required def scan_hidden(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_hidden_apps(serial)) @android_protect_bp.route('/scan/admins', methods=['POST']) @login_required def scan_admins(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_device_admins(serial)) @android_protect_bp.route('/scan/accessibility', methods=['POST']) @login_required def scan_accessibility(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_accessibility_services(serial)) @android_protect_bp.route('/scan/listeners', methods=['POST']) @login_required def scan_listeners(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_notification_listeners(serial)) @android_protect_bp.route('/scan/spyware', methods=['POST']) @login_required def scan_spyware(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_spyware_indicators(serial)) @android_protect_bp.route('/scan/integrity', methods=['POST']) @login_required def scan_integrity(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_system_integrity(serial)) @android_protect_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_suspicious_processes(serial)) @android_protect_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_protect_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_config(serial)) @android_protect_bp.route('/scan/devopt', methods=['POST']) @login_required def scan_devopt(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_developer_options(serial)) # ── Permission Routes ─────────────────────────────────────────────── @android_protect_bp.route('/perms/dangerous', methods=['POST']) @login_required def perms_dangerous(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().find_dangerous_apps(serial)) @android_protect_bp.route('/perms/analyze', methods=['POST']) @login_required def perms_analyze(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) if not package: return jsonify({'error': 'No package provided'}) return jsonify(_mgr().analyze_app_permissions(serial, package)) @android_protect_bp.route('/perms/heatmap', methods=['POST']) @login_required def perms_heatmap(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().permission_heatmap(serial)) # ── Remediation Routes ────────────────────────────────────────────── @android_protect_bp.route('/fix/disable', methods=['POST']) @login_required def fix_disable(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial or not package: return jsonify({'error': 'Serial and package required'}) return jsonify(_mgr().disable_threat(serial, package)) @android_protect_bp.route('/fix/uninstall', methods=['POST']) @login_required def fix_uninstall(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial or not package: return jsonify({'error': 'Serial and package required'}) return jsonify(_mgr().uninstall_threat(serial, package)) @android_protect_bp.route('/fix/revoke', methods=['POST']) @login_required def fix_revoke(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial or not package: return jsonify({'error': 'Serial and package required'}) return jsonify(_mgr().revoke_dangerous_perms(serial, package)) @android_protect_bp.route('/fix/remove-admin', methods=['POST']) @login_required def fix_remove_admin(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial or not package: return jsonify({'error': 'Serial and package required'}) return jsonify(_mgr().remove_device_admin(serial, package)) @android_protect_bp.route('/fix/remove-cert', methods=['POST']) @login_required def fix_remove_cert(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() cert_hash = data.get('cert_hash', '').strip() if not serial or not cert_hash: return jsonify({'error': 'Serial and cert_hash required'}) return jsonify(_mgr().remove_ca_cert(serial, cert_hash)) @android_protect_bp.route('/fix/clear-proxy', methods=['POST']) @login_required def fix_clear_proxy(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().clear_proxy(serial)) # ── Shizuku Routes ────────────────────────────────────────────────── @android_protect_bp.route('/shizuku/status', methods=['POST']) @login_required def shizuku_status(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().shizuku_status(serial)) @android_protect_bp.route('/shizuku/install', methods=['POST']) @login_required def shizuku_install(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) # Handle file upload if 'apk' in request.files: from flask import current_app f = request.files['apk'] upload_dir = current_app.config.get('UPLOAD_FOLDER', '/tmp') path = os.path.join(upload_dir, 'shizuku.apk') f.save(path) return jsonify(_mgr().install_shizuku(serial, path)) data = request.get_json(silent=True) or {} apk_path = data.get('apk_path', '').strip() if not apk_path: return jsonify({'error': 'No APK provided'}) return jsonify(_mgr().install_shizuku(serial, apk_path)) @android_protect_bp.route('/shizuku/start', methods=['POST']) @login_required def shizuku_start(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().start_shizuku(serial)) # ── Shield App Routes ────────────────────────────────────────────── @android_protect_bp.route('/shield/status', methods=['POST']) @login_required def shield_status(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().check_shield_app(serial)) @android_protect_bp.route('/shield/install', methods=['POST']) @login_required def shield_install(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) if 'apk' in request.files: from flask import current_app f = request.files['apk'] upload_dir = current_app.config.get('UPLOAD_FOLDER', '/tmp') path = os.path.join(upload_dir, 'shield.apk') f.save(path) return jsonify(_mgr().install_shield_app(serial, path)) data = request.get_json(silent=True) or {} apk_path = data.get('apk_path', '').strip() if not apk_path: return jsonify({'error': 'No APK provided'}) return jsonify(_mgr().install_shield_app(serial, apk_path)) @android_protect_bp.route('/shield/configure', methods=['POST']) @login_required def shield_configure(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() config = data.get('config', {}) if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().configure_shield(serial, config)) @android_protect_bp.route('/shield/permissions', methods=['POST']) @login_required def shield_permissions(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().grant_shield_permissions(serial)) # ── Database Routes ───────────────────────────────────────────────── @android_protect_bp.route('/db/stats', methods=['POST']) @login_required def db_stats(): return jsonify(_mgr().get_signature_stats()) @android_protect_bp.route('/db/update', methods=['POST']) @login_required def db_update(): return jsonify(_mgr().update_signatures()) # ── Honeypot Routes ──────────────────────────────────────────────── @android_protect_bp.route('/honeypot/status', methods=['POST']) @login_required def honeypot_status(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().honeypot_status(serial)) @android_protect_bp.route('/honeypot/scan-trackers', methods=['POST']) @login_required def honeypot_scan_trackers(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_tracker_apps(serial)) @android_protect_bp.route('/honeypot/scan-tracker-perms', methods=['POST']) @login_required def honeypot_scan_tracker_perms(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().scan_tracker_permissions(serial)) @android_protect_bp.route('/honeypot/ad-settings', methods=['POST']) @login_required def honeypot_ad_settings(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().get_tracking_settings(serial)) @android_protect_bp.route('/honeypot/reset-ad-id', methods=['POST']) @login_required def honeypot_reset_ad_id(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().reset_advertising_id(serial)) @android_protect_bp.route('/honeypot/opt-out', methods=['POST']) @login_required def honeypot_opt_out(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().opt_out_ad_tracking(serial)) @android_protect_bp.route('/honeypot/set-dns', methods=['POST']) @login_required def honeypot_set_dns(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() provider = data.get('provider', '').strip() if not serial: return jsonify({'error': 'No serial provided'}) if not provider: return jsonify({'error': 'No provider specified'}) return jsonify(_mgr().set_private_dns(serial, provider)) @android_protect_bp.route('/honeypot/clear-dns', methods=['POST']) @login_required def honeypot_clear_dns(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().clear_private_dns(serial)) @android_protect_bp.route('/honeypot/disable-location-scan', methods=['POST']) @login_required def honeypot_disable_location_scan(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().disable_location_accuracy(serial)) @android_protect_bp.route('/honeypot/disable-diagnostics', methods=['POST']) @login_required def honeypot_disable_diagnostics(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().disable_usage_diagnostics(serial)) @android_protect_bp.route('/honeypot/restrict-background', methods=['POST']) @login_required def honeypot_restrict_background(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial or not package: return jsonify({'error': 'Serial and package required'}) return jsonify(_mgr().restrict_app_background(serial, package)) @android_protect_bp.route('/honeypot/revoke-tracker-perms', methods=['POST']) @login_required def honeypot_revoke_tracker_perms(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial or not package: return jsonify({'error': 'Serial and package required'}) return jsonify(_mgr().revoke_tracker_permissions(serial, package)) @android_protect_bp.route('/honeypot/clear-tracker-data', methods=['POST']) @login_required def honeypot_clear_tracker_data(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() package = data.get('package', '').strip() if not serial or not package: return jsonify({'error': 'Serial and package required'}) return jsonify(_mgr().clear_app_tracking_data(serial, package)) @android_protect_bp.route('/honeypot/force-stop-trackers', methods=['POST']) @login_required def honeypot_force_stop_trackers(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().force_stop_trackers(serial)) @android_protect_bp.route('/honeypot/deploy-hosts', methods=['POST']) @login_required def honeypot_deploy_hosts(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().deploy_hosts_blocklist(serial)) @android_protect_bp.route('/honeypot/remove-hosts', methods=['POST']) @login_required def honeypot_remove_hosts(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().remove_hosts_blocklist(serial)) @android_protect_bp.route('/honeypot/hosts-status', methods=['POST']) @login_required def honeypot_hosts_status(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().get_hosts_status(serial)) @android_protect_bp.route('/honeypot/iptables-setup', methods=['POST']) @login_required def honeypot_iptables_setup(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() port = data.get('port', 9040) if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().setup_iptables_redirect(serial, port)) @android_protect_bp.route('/honeypot/iptables-clear', methods=['POST']) @login_required def honeypot_iptables_clear(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().clear_iptables_redirect(serial)) @android_protect_bp.route('/honeypot/fake-location', methods=['POST']) @login_required def honeypot_fake_location(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() lat = data.get('lat') lon = data.get('lon') if not serial: return jsonify({'error': 'No serial provided'}) if lat is None or lon is None: return jsonify({'error': 'lat and lon required'}) return jsonify(_mgr().set_fake_location(serial, float(lat), float(lon))) @android_protect_bp.route('/honeypot/random-location', methods=['POST']) @login_required def honeypot_random_location(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().set_random_fake_location(serial)) @android_protect_bp.route('/honeypot/clear-location', methods=['POST']) @login_required def honeypot_clear_location(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().clear_fake_location(serial)) @android_protect_bp.route('/honeypot/rotate-identity', methods=['POST']) @login_required def honeypot_rotate_identity(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().rotate_device_identity(serial)) @android_protect_bp.route('/honeypot/fake-fingerprint', methods=['POST']) @login_required def honeypot_fake_fingerprint(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().generate_fake_fingerprint(serial)) @android_protect_bp.route('/honeypot/activate', methods=['POST']) @login_required def honeypot_activate(): data = request.get_json(silent=True) or {} serial = data.get('serial', '').strip() tier = data.get('tier', 1) if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().honeypot_activate(serial, int(tier))) @android_protect_bp.route('/honeypot/deactivate', methods=['POST']) @login_required def honeypot_deactivate(): serial = _serial() if not serial: return jsonify({'error': 'No serial provided'}) return jsonify(_mgr().honeypot_deactivate(serial)) @android_protect_bp.route('/honeypot/tracker-stats', methods=['POST']) @login_required def honeypot_tracker_stats(): return jsonify(_mgr().get_tracker_stats()) @android_protect_bp.route('/honeypot/update-domains', methods=['POST']) @login_required def honeypot_update_domains(): return jsonify(_mgr().update_tracker_domains()) # ── Direct (WebUSB) Mode — Command Relay ───────────────────────────────────── # Maps operation key → dict of {label: adb_shell_command} _DIRECT_COMMANDS = { 'scan_quick': { 'packages': 'pm list packages', 'devopt': 'settings get global development_settings_enabled', 'adb_enabled': 'settings get global adb_enabled', 'accessibility': 'settings get secure enabled_accessibility_services', 'admins': 'dumpsys device_policy 2>/dev/null | head -60', }, 'scan_stalkerware': { 'packages': 'pm list packages', }, 'scan_hidden': { 'all': 'pm list packages', 'launcher': 'cmd package query-activities -a android.intent.action.MAIN ' '-c android.intent.category.LAUNCHER 2>/dev/null', }, 'scan_admins': { 'admins': 'dumpsys device_policy 2>/dev/null | head -100', }, 'scan_accessibility': { 'services': 'settings get secure enabled_accessibility_services', }, 'scan_listeners': { 'listeners': 'cmd notification list-listeners 2>/dev/null || dumpsys notification 2>/dev/null | grep -i listener | head -30', }, 'scan_spyware': { 'packages': 'pm list packages', 'processes': 'ps -A 2>/dev/null | head -100', }, 'scan_integrity': { 'secure': 'getprop ro.secure', 'debuggable': 'getprop ro.debuggable', 'fingerprint': 'getprop ro.build.fingerprint', 'devopt': 'settings get global development_settings_enabled', 'kernel': 'cat /proc/version', }, 'scan_processes': { 'ps': 'ps -A 2>/dev/null | head -200', }, 'scan_certs': { 'certs': 'ls /data/misc/user/0/cacerts-added/ 2>/dev/null || echo ""', }, 'scan_network': { 'proxy': 'settings get global http_proxy', 'dns1': 'getprop net.dns1', 'dns2': 'getprop net.dns2', 'private_dns': 'settings get global private_dns_mode', 'private_dns_h': 'settings get global private_dns_specifier', }, 'scan_devopt': { 'devopt': 'settings get global development_settings_enabled', 'adb': 'settings get global adb_enabled', 'adb_wifi': 'settings get global adb_wifi_enabled', 'verifier': 'settings get global package_verifier_enable', }, 'perms_dangerous': { 'packages': 'pm list packages', }, } @android_protect_bp.route('/cmd', methods=['POST']) @login_required def direct_cmd(): """Return ADB shell commands for Direct (WebUSB) mode.""" data = request.get_json(silent=True) or {} op = data.get('op', '').replace('/', '_').replace('-', '_') cmds = _DIRECT_COMMANDS.get(op) if cmds: return jsonify({'commands': cmds, 'supported': True}) return jsonify({'commands': {}, 'supported': False}) @android_protect_bp.route('/parse', methods=['POST']) @login_required def direct_parse(): """Analyze raw ADB shell output from Direct (WebUSB) mode.""" import re data = request.get_json(silent=True) or {} op = data.get('op', '').replace('/', '_').replace('-', '_') raw = data.get('raw', {}) mgr = _mgr() def _pkgs(output): """Parse 'pm list packages' output to a set of package names.""" pkgs = set() for line in (output or '').strip().split('\n'): line = line.strip() if line.startswith('package:'): pkgs.add(line[8:].split('=')[0].strip()) return pkgs try: if op in ('scan_stalkerware', 'scan_quick', 'scan_spyware'): packages = _pkgs(raw.get('packages', '')) sigs = mgr._load_signatures() found = [] for family, fdata in sigs.get('stalkerware', {}).items(): for pkg in fdata.get('packages', []): if pkg in packages: found.append({'name': family, 'package': pkg, 'severity': fdata.get('severity', 'high'), 'description': fdata.get('description', '')}) for pkg in packages: if pkg in set(sigs.get('suspicious_system_packages', [])): found.append({'name': 'Suspicious System Package', 'package': pkg, 'severity': 'high', 'description': 'Package mimics a system app name'}) matched = {f['package'] for f in found} result = {'found': found, 'clean_count': len(packages) - len(matched), 'total': len(packages)} if op == 'scan_quick': result['developer_options'] = raw.get('devopt', '').strip() == '1' result['accessibility_active'] = raw.get('accessibility', '').strip() not in ('', 'null') return jsonify(result) elif op == 'scan_hidden': all_pkgs = _pkgs(raw.get('all', '')) launcher_out = raw.get('launcher', '') launcher_pkgs = set() for line in launcher_out.split('\n'): line = line.strip() if '/' in line: launcher_pkgs.add(line.split('/')[0]) hidden = sorted(all_pkgs - launcher_pkgs) return jsonify({'hidden': hidden, 'total': len(all_pkgs), 'hidden_count': len(hidden)}) elif op == 'scan_admins': admins_raw = raw.get('admins', '') active = [l.strip() for l in admins_raw.split('\n') if 'ComponentInfo' in l or 'admin' in l.lower()] return jsonify({'admins': active, 'count': len(active)}) elif op == 'scan_accessibility': raw_val = raw.get('services', '').strip() services = [s.strip() for s in raw_val.split(':') if s.strip() and s.strip() != 'null'] return jsonify({'services': services, 'count': len(services)}) elif op == 'scan_listeners': lines = [l.strip() for l in raw.get('listeners', '').split('\n') if l.strip()] return jsonify({'listeners': lines, 'count': len(lines)}) elif op == 'scan_integrity': issues = [] if raw.get('debuggable', '').strip() == '1': issues.append({'issue': 'Kernel debuggable', 'severity': 'high', 'detail': 'ro.debuggable=1'}) if raw.get('secure', '').strip() == '0': issues.append({'issue': 'Insecure kernel', 'severity': 'high', 'detail': 'ro.secure=0'}) if raw.get('devopt', '').strip() == '1': issues.append({'issue': 'Developer options enabled', 'severity': 'medium', 'detail': ''}) return jsonify({'issues': issues, 'fingerprint': raw.get('fingerprint', '').strip(), 'kernel': raw.get('kernel', '').strip()}) elif op == 'scan_processes': lines = [l for l in raw.get('ps', '').split('\n') if l.strip()] return jsonify({'processes': lines, 'count': len(lines)}) elif op == 'scan_certs': certs = [c.strip() for c in raw.get('certs', '').split('\n') if c.strip() and not c.startswith('ls:')] return jsonify({'user_certs': certs, 'count': len(certs), 'risk': 'high' if certs else 'none'}) elif op == 'scan_network': proxy = raw.get('proxy', '').strip() return jsonify({ 'proxy': proxy if proxy not in ('', 'null') else None, 'dns_primary': raw.get('dns1', '').strip(), 'dns_secondary': raw.get('dns2', '').strip(), 'private_dns': raw.get('private_dns', '').strip(), 'private_dns_h': raw.get('private_dns_h', '').strip(), }) elif op == 'scan_devopt': def flag(k): return raw.get(k, '').strip() == '1' issues = [] if flag('devopt'): issues.append({'setting': 'Developer options', 'risk': 'medium'}) if flag('adb'): issues.append({'setting': 'ADB enabled', 'risk': 'medium'}) if flag('adb_wifi'): issues.append({'setting': 'ADB over WiFi', 'risk': 'high'}) if raw.get('verifier', '').strip() == '0': issues.append({'setting': 'Package verifier disabled', 'risk': 'high'}) return jsonify({'settings': issues}) elif op == 'perms_dangerous': packages = sorted(_pkgs(raw.get('packages', ''))) return jsonify({'packages': packages, 'count': len(packages), 'note': 'Full permission analysis requires Server mode (needs per-package dumpsys)'}) else: return jsonify({'error': f'Direct mode parse not implemented for: {op}. Use Server mode.'}), 200 except Exception as exc: return jsonify({'error': str(exc)})