Ctrl K
ComunidadGuías…

butler/tools/terminal_danger.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Dangerous terminal command patterns (Hermes approval.py subset)."""

from __future__ import annotations

import contextvars
import os
import re
from dataclasses import dataclass
import logging

logger = logging.getLogger(__name__)

_CURRENT_SESSION: contextvars.ContextVar[str] = contextvars.ContextVar(
    "butler_terminal_session",
    default="",
)


@dataclass(frozen=True)
class DangerCheckResult:
    allowed: bool
    reason: str = ""
    pattern: str = ""


_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
    ("rm_rf", re.compile(r"\brm\s+(?:-\S+\s+)*\S*/", re.I)),
    ("curl_pipe_sh", re.compile(r"\b(curl|wget)\b[^\n|]*\|\s*(ba)?sh\b", re.I)),
    ("chmod_777", re.compile(r"\bchmod\s+[-+]?777\b", re.I)),
    ("disk_destroy", re.compile(r"\bmkfs\.|dd\s+if=.*of=/dev/", re.I)),
    ("reverse_shell", re.compile(r"\b(nc|netcat)\b.*(-e|--exec)\s+/bin/", re.I)),
]


def danger_patterns_enabled() -> bool:
    from butler.defaults.env_defaults import TERMINAL_DANGER_CHECK_DEFAULT

    raw = os.getenv("BUTLER_TERMINAL_DANGER_CHECK", str(int(TERMINAL_DANGER_CHECK_DEFAULT))).strip().lower()
    return raw not in ("0", "false", "no", "off")


def set_terminal_session_context(session_key: str) -> contextvars.Token[str]:
    return _CURRENT_SESSION.set(str(session_key or "").strip())


def get_terminal_session_context() -> str:
    return str(_CURRENT_SESSION.get() or "").strip()


def check_dangerous_command(command: str) -> DangerCheckResult:
    if not danger_patterns_enabled():
        return DangerCheckResult(True)
    text = (command or "").strip()
    if not text:
        return DangerCheckResult(True)

    from butler.execpolicy.engine import PolicyDecision
    from butler.tools.terminal_danger_ops import (
        evaluate_execpolicy_safe,
        is_terminal_pattern_approved_safe,
    )

    pol = evaluate_execpolicy_safe(text)
    if pol is not None:
        if pol.decision == PolicyDecision.FORBIDDEN:
            msg = pol.justification or "execpolicy forbidden"
            return DangerCheckResult(False, reason=f"execpolicy: {msg}", pattern=pol.matched_rule)
        if pol.decision == PolicyDecision.ALLOW:
            return DangerCheckResult(True)
    for name, pattern in _PATTERNS:
        if pattern.search(text):
            sk = get_terminal_session_context()
            if sk and is_terminal_pattern_approved_safe(sk, name):
                return DangerCheckResult(True)
            return DangerCheckResult(
                False,
                reason=(
                    "该 terminal 命令命中危险模式,已阻断。"
                    "请拆分命令或经 Owner `/批准执行` 后重试;"
                    f"同类模式可用 `/批准模式 {name}` 本会话放行。"
                ),
                pattern=name,
            )
    return DangerCheckResult(True)