"""安全代码沙箱 · 静态代码守卫（AST 扫描，纯逻辑，执行前的第一道防线）。

真正的执行隔离在 interpreter.CodeInterpreter（Docker：禁网/非 root/丢能力/限资源/一次性）。
本守卫是纵深防御：在进容器前先拦掉明显的逃逸/越权/原生调用向量。
生产可把执行运行时换成 gVisor(runsc) / Kata 进一步硬化。
"""

from __future__ import annotations

import ast

# 危险模块：导入即拒（执行 / 原生 / 网络 / 序列化反序列化 / 进程等逃逸或越权向量）。
_DENY_MODULES = {
    "os", "sys", "subprocess", "socket", "ctypes", "importlib", "pickle",
    "marshal", "shutil", "multiprocessing", "threading", "pty", "signal",
    "mmap", "fcntl", "resource", "requests", "urllib", "http", "ftplib",
    "telnetlib", "smtplib", "asyncio", "platform", "pdb", "gc",
}

# 危险内置调用：拒（动态执行 / 文件 / 解释器内省入口）。
_DENY_CALLS = {
    "eval", "exec", "compile", "__import__", "open", "input",
    "breakpoint", "exit", "quit", "globals", "vars", "memoryview",
}

# 危险属性：拒（典型沙箱逃逸链：obj.__class__.__subclasses__()... / __globals__ ...）。
_DENY_ATTRS = {
    "__subclasses__", "__globals__", "__builtins__", "__bases__",
    "__mro__", "__reduce__", "__reduce_ex__", "__code__", "__dict__",
    "__getattribute__", "__base__",
}


class SecureSandbox:
    """Harness · 静态代码守卫。"""

    def static_guard(self, code: str) -> tuple[bool, str]:
        """AST 扫描危险导入 / 调用 / 属性。返回 (是否放行, 拒绝原因)。"""
        try:
            tree = ast.parse(code)
        except SyntaxError as e:
            return False, f"语法错误：{e.msg}"
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    top = alias.name.split(".")[0]
                    if top in _DENY_MODULES:
                        return False, f"禁止导入模块：{alias.name}"
            elif isinstance(node, ast.ImportFrom):
                top = (node.module or "").split(".")[0]
                if top in _DENY_MODULES:
                    return False, f"禁止导入模块：{node.module}"
            elif isinstance(node, ast.Call):
                name = _call_name(node.func)
                if name in _DENY_CALLS:
                    return False, f"禁止调用：{name}"
            elif isinstance(node, ast.Attribute):
                if node.attr in _DENY_ATTRS:
                    return False, f"禁止访问属性：{node.attr}"
        return True, ""


def _call_name(func: ast.AST) -> str:
    """取被调用者的名字：普通名(eval) 或属性末段(o.system)。"""
    if isinstance(func, ast.Name):
        return func.id
    if isinstance(func, ast.Attribute):
        return func.attr
    return ""
