#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ЗАКРЫТЬ НЕОБРАТИМЫЕ КОМАНДЫ TG-БОТА СПИСКОМ ДОПУСКА. Проблема: /kill /pause /promote /resume выполняются кем угодно, кто написал боту. Публичные кнопки на витрине ведут именно сюда. Это excessive agency — тот самый класс отказа, который мы сами продаём в аудите. Патч НЕ переписывает логику бота. Он вставляет одну проверку сразу после того места, где бот достал id отправителя, и пропускает дальше только своих — для опасных команд. Читающие команды (/pnl /top /open ...) остаются открытыми. python3 acl_patch.py <файл> # сухой прогон, ничего не меняет python3 acl_patch.py <файл> --apply # применить (с бэкапом) Список допуска — файл /opt/arena/tg_acl.json: {"allow": [123, 456]} Если файла нет, берётся ACL_FALLBACK ниже. Пустой список = никого не пускать к опасным командам (безопасное поведение по умолчанию). """ import ast, json, os, re, shutil, sys, time GUARD_MARK = "# --- ACL GUARD (arena) ---" GUARD_SRC = ''' {mark} import json as _acl_json, os as _acl_os _ACL_FILE = "/opt/arena/tg_acl.json" ACL_FALLBACK = [] # пусто = опасные команды не выполняет НИКТО _ACL_DANGER = ("/kill", "/pause", "/resume", "/promote", "/stop", "/restart", "/disable") def _acl_allowed(): """Список читается на каждый вызов — правка файла действует без рестарта.""" try: with open(_ACL_FILE, encoding="utf-8") as f: return {{int(x) for x in _acl_json.load(f).get("allow", [])}} except Exception: env = _acl_os.environ.get("ARENA_TG_ALLOW", "") if env: return {{int(x) for x in env.replace(",", " ").split() if x.strip()}} return set(ACL_FALLBACK) def acl_block(sender, text): """True — команду выполнять НЕЛЬЗЯ. Пускаем только своих и только опасное.""" t = (text or "").strip().lower().split()[:1] if not t or not any(t[0].startswith(d) for d in _ACL_DANGER): return False # читающие команды открыты всем try: return int(sender) not in _acl_allowed() except (TypeError, ValueError): return True # не смогли опознать — не пускаем {mark} ''' def find_anchor(lines): """Ищем строку, где бот достаёт id отправителя, и переменную с текстом.""" chat_re = re.compile(r"^(\s*)(\w+)\s*=\s*\(?\s*\w+\.get\(\s*[\"']chat[\"']") text_re = re.compile(r"^\s*(\w+)\s*=\s*\(?\s*\w+\.get\(\s*[\"']text[\"']") chat_var = text_var = None chat_i = indent = None for i, ln in enumerate(lines): m = chat_re.match(ln) if m and chat_i is None: indent, chat_var, chat_i = m.group(1), m.group(2), i m2 = text_re.match(ln) if m2 and text_var is None: text_var = m2.group(1) return chat_i, indent, chat_var, text_var def main(): if len(sys.argv) < 2: sys.exit(__doc__) path = sys.argv[1] apply = "--apply" in sys.argv src = open(path, encoding="utf-8").read() lines = src.splitlines() if GUARD_MARK in src: print("✅ защита уже стоит — повторно не вставляю") return chat_i, indent, chat_var, text_var = find_anchor(lines) print(f"файл: {path} строк: {len(lines)}") print(f"якорь (id отправителя): строка {chat_i + 1 if chat_i is not None else '—'}" f" переменная: {chat_var or '—'}") print(f"переменная с текстом: {text_var or '—'}") if chat_i is None or not chat_var or not text_var: print("\n❌ структура не распознана — патч НЕ применяю, нужен ручной разбор") return guard_call = (f"{indent}if acl_block({chat_var}, {text_var}):\n" f"{indent} continue{'':s}\n") print("\nвставлю после якоря:") print(" " + guard_call.strip().replace("\n", "\n ")) print("и блок acl_block(...) в начало файла после импортов") # куда положить сам блок: после последней строки импортов в шапке last_imp = 0 for i, ln in enumerate(lines[:80]): if re.match(r"^(import |from )\w", ln): last_imp = i new = lines[:last_imp + 1] + GUARD_SRC.format(mark=GUARD_MARK).splitlines() \ + lines[last_imp + 1:chat_i + 1] + guard_call.rstrip("\n").splitlines() \ + lines[chat_i + 1:] out = "\n".join(new) + "\n" try: ast.parse(out) print("\n✅ результат синтаксически корректен") except SyntaxError as e: print(f"\n❌ патч ломает синтаксис: {e} — НЕ применяю") return if not apply: print("\nсухой прогон. Для применения добавьте --apply") return bak = f"{path}.bak.{int(time.time())}" shutil.copy2(path, bak) open(path, "w", encoding="utf-8").write(out) print(f"\n✅ применено. Бэкап: {bak}") print(" Дальше: создать /opt/arena/tg_acl.json со своим id и перезапустить бота.") if __name__ == "__main__": main()