| |
| from __future__ import annotations |
|
|
| import ast |
| import io |
| import textwrap |
| from contextlib import redirect_stdout |
| from typing import Any |
|
|
| from config import PY_UNSAFE_DEFAULT |
| from utils.audit import audit_event |
|
|
|
|
| SAFE_BUILTINS: dict[str, Any] = { |
| "print": print, |
| "len": len, |
| "range": range, |
| "min": min, |
| "max": max, |
| "sum": sum, |
| "enumerate": enumerate, |
| "sorted": sorted, |
| "int": int, |
| "float": float, |
| "str": str, |
| "bool": bool, |
| "list": list, |
| "dict": dict, |
| "set": set, |
| "tuple": tuple, |
| "abs": abs, |
| "round": round, |
| "zip": zip, |
| "any": any, |
| "all": all, |
| } |
|
|
|
|
| def _has_forbidden_nodes(tree: ast.AST) -> tuple[bool, str]: |
| """Detecta importações e acessos perigosos no modo SAFE.""" |
| for node in ast.walk(tree): |
| if isinstance(node, (ast.Import, ast.ImportFrom)): |
| return True, "Uso de import não é permitido no modo SAFE." |
| if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): |
| if node.func.id in {"eval", "exec", "__import__", "open"}: |
| return True, f"Uso de {node.func.id} não é permitido no modo SAFE." |
| if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name): |
| |
| if node.value.id in {"os", "sys", "subprocess", "pathlib"}: |
| return True, f"Acesso a {node.value.id} não é permitido no modo SAFE." |
| return False, "" |
|
|
|
|
| def _parse_flags(args: str) -> dict[str, Any]: |
| """ |
| Interpreta flags: |
| --unsafe (exige token CONFIRMO) |
| --preview (não executa; apenas mostra o código detectado) |
| """ |
| tokens = args.split() |
| flags = {"unsafe": False, "preview": False, "confirmed": False, "rest": ""} |
|
|
| |
| rest = [] |
| for t in tokens: |
| tl = t.lower() |
| if tl == "--unsafe": |
| flags["unsafe"] = True |
| elif tl == "--preview": |
| flags["preview"] = True |
| elif tl == "confirmo": |
| flags["confirmed"] = True |
| else: |
| rest.append(t) |
| flags["rest"] = " ".join(rest).strip() |
| return flags |
|
|
|
|
| def _normalize_block(block: str) -> str: |
| if not block: |
| return "" |
| |
| return textwrap.dedent(block).strip("\n\r ") |
|
|
|
|
| async def handle_py(args: str, block: str) -> str: |
| """ |
| /py <<<codigo>>> -> Modo SAFE |
| /py --unsafe CONFIRMO <<<...>>> -> Modo UNSAFE (permite import) |
| |
| Regras: |
| - SAFE: sem import, sem eval/exec/__import__/open/os/sys/subprocess. |
| - UNSAFE: requer --unsafe e CONFIRMO. |
| - --preview: apenas retorna o código detectado (sem executar). |
| """ |
| flags = _parse_flags(args) |
| code = _normalize_block(block) |
|
|
| if flags["preview"]: |
| preview = code if code else "(vazio)" |
| return "**Preview de código (/py)**\n```\n" + preview[:6000] + "\n```" |
|
|
| if not code: |
| return "Uso: /py [--unsafe CONFIRMO] [--preview] <<<código Python>>>" |
|
|
| try: |
| if flags["unsafe"] or PY_UNSAFE_DEFAULT: |
| |
| if not flags["confirmed"]: |
| return ( |
| "⚠️ Modo UNSAFE requer confirmação explícita.\n" |
| "Use: `/py --unsafe CONFIRMO <<<seu código>>>`" |
| ) |
| audit_event("pyexec_unsafe", {"preview": False}) |
|
|
| |
| buf = io.StringIO() |
| glb: dict[str, Any] = {} |
| loc: dict[str, Any] = {} |
| with redirect_stdout(buf): |
| exec(code, glb, loc) |
| output = buf.getvalue() |
| if not output.strip(): |
| output = "_(sem saída)_" |
| if len(output) > 6000: |
| output = output[:6000] + "\n...[truncado]..." |
| return "**/py (UNSAFE)**\n```\n" + output + "\n```" |
|
|
| |
| audit_event("pyexec_safe", {"preview": False}) |
| |
| tree = ast.parse(code, mode="exec") |
| bad, reason = _has_forbidden_nodes(tree) |
| if bad: |
| return f"🚫 Bloqueado no modo SAFE: {reason}" |
|
|
| buf = io.StringIO() |
| safe_globals = {"__builtins__": SAFE_BUILTINS} |
| safe_locals: dict[str, Any] = {} |
| with redirect_stdout(buf): |
| exec(compile(tree, "<safe>", "exec"), safe_globals, safe_locals) |
| output = buf.getvalue() |
| if not output.strip(): |
| output = "_(sem saída)_" |
| if len(output) > 6000: |
| output = output[:6000] + "\n...[truncado]..." |
| return "**/py (SAFE)**\n```\n" + output + "\n```" |
|
|
| except SyntaxError as e: |
| return f"❌ Erro de sintaxe em /py: {e}" |
| except Exception as e: |
| return f"❌ Erro em /py: {e}" |
|
|
|
|
| |
| handle_pyexec = handle_py |
|
|