"""NebulaShell Core Engine — 核心引擎 整合功能: - 插件加载(目录结构) - 生命周期管理 - 依赖解析 - 签名校验(RSA-SHA256) - PL 注入(沙箱执行) - 能力注册 - 文件监控与热重载 - HTTP 服务(子模块) - REPL 终端(子模块) - 全面防护:完整性检查、内存保护、行为审计、防篡改监控、降级恢复 - 数据存储接口(为 data-store 插件预留) """ import sys import json import re import os import time import types import hashlib import threading import traceback import importlib.util import functools from pathlib import Path from typing import Any, Optional, Callable from collections import deque, defaultdict from oss.plugin.types import Plugin, register_plugin_type from oss.plugin.capabilities import scan_capabilities from oss.logger.logger import Log from oss.config import get_config # ═══════════════════════════════════════════════════════════════ # 生命周期管理 # ═══════════════════════════════════════════════════════════════ class LifecycleState: PENDING = "pending" RUNNING = "running" STOPPED = "stopped" DEGRADED = "degraded" CRASHED = "crashed" class LifecycleError(Exception): pass class Lifecycle: VALID_TRANSITIONS = { LifecycleState.PENDING: [LifecycleState.RUNNING], LifecycleState.RUNNING: [LifecycleState.STOPPED, LifecycleState.DEGRADED, LifecycleState.CRASHED], LifecycleState.STOPPED: [LifecycleState.RUNNING], LifecycleState.DEGRADED: [LifecycleState.RUNNING, LifecycleState.STOPPED], LifecycleState.CRASHED: [LifecycleState.PENDING, LifecycleState.STOPPED], } def __init__(self, name: str): self.name = name self.state = LifecycleState.PENDING self._hooks: dict[str, list[Callable]] = { "before_start": [], "after_start": [], "before_stop": [], "after_stop": [], "on_crash": [], "on_degrade": [], } self._extensions: dict[str, Any] = {} def add_extension(self, name: str, extension: Any): self._extensions[name] = extension def get_extension(self, name: str) -> Any: return self._extensions.get(name) def start(self): for hook in self._hooks["before_start"]: hook(self) self.transition(LifecycleState.RUNNING) for hook in self._hooks["after_start"]: hook(self) def stop(self): if self.state in (LifecycleState.RUNNING, LifecycleState.DEGRADED): for hook in self._hooks["before_stop"]: hook(self) self.transition(LifecycleState.STOPPED) for hook in self._hooks["after_stop"]: hook(self) def restart(self): self.stop() self.start() def mark_crashed(self): self.transition(LifecycleState.CRASHED) for hook in self._hooks["on_crash"]: hook(self) def mark_degraded(self): self.transition(LifecycleState.DEGRADED) for hook in self._hooks["on_degrade"]: hook(self) def on(self, event: str, hook: Callable): if event in self._hooks: self._hooks[event].append(hook) def transition(self, target_state: LifecycleState): valid = self.VALID_TRANSITIONS.get(self.state, []) if target_state in valid: self.state = target_state else: raise LifecycleError(f"Cannot transition from {self.state} to {target_state}") class LifecycleManager: def __init__(self): self.lifecycles: dict[str, Lifecycle] = {} def create(self, name: str) -> Lifecycle: lifecycle = Lifecycle(name) self.lifecycles[name] = lifecycle return lifecycle def get(self, name: str) -> Optional[Lifecycle]: return self.lifecycles.get(name) def start_all(self): for lc in self.lifecycles.values(): try: lc.start() except LifecycleError: pass def stop_all(self): for lc in self.lifecycles.values(): try: lc.stop() except LifecycleError: pass # ═══════════════════════════════════════════════════════════════ # 插件信息 # ═══════════════════════════════════════════════════════════════ class PluginInfo: """插件信息""" def __init__(self): self.name: str = "" self.version: str = "" self.author: str = "" self.description: str = "" self.readme: str = "" self.config: dict[str, Any] = {} self.extensions: dict[str, Any] = {} self.lifecycle: Any = None self.capabilities: set[str] = set() self.dependencies: list[str] = [] self.pl_injected: bool = False self.file_hash: str = "" # 文件完整性 hash # ═══════════════════════════════════════════════════════════════ # 权限与代理 # ═══════════════════════════════════════════════════════════════ class PermissionError(Exception): """权限错误""" pass class PluginProxy: """插件代理 - 防止越级访问""" def __init__(self, plugin_name: str, plugin_instance: Any, allowed_plugins: list[str], all_plugins: dict): self._plugin_name = plugin_name self._plugin_instance = plugin_instance self._allowed_plugins = set(allowed_plugins) self._all_plugins = all_plugins def get_plugin(self, name: str) -> Any: if name not in self._allowed_plugins and "*" not in self._allowed_plugins: raise PermissionError(f"插件 '{self._plugin_name}' 无权访问插件 '{name}'") if name not in self._all_plugins: return None return self._all_plugins[name]["instance"] def list_plugins(self) -> list[str]: if "*" in self._allowed_plugins: return list(self._all_plugins.keys()) return [n for n in self._allowed_plugins if n in self._all_plugins] def get_capability(self, capability: str) -> Any: return None def __getattr__(self, name: str): return getattr(self._plugin_instance, name) # ═══════════════════════════════════════════════════════════════ # 能力注册表 # ═══════════════════════════════════════════════════════════════ class CapabilityRegistry: """能力注册表""" def __init__(self, permission_check: bool = True): self.providers: dict = {} self.consumers: dict = {} self.permission_check = permission_check def register_provider(self, capability: str, plugin_name: str, instance: Any): self.providers[capability] = {"plugin": plugin_name, "instance": instance} if capability not in self.consumers: self.consumers[capability] = [] def register_consumer(self, capability: str, plugin_name: str): if capability not in self.consumers: self.consumers[capability] = [] if plugin_name not in self.consumers[capability]: self.consumers[capability].append(plugin_name) def get_provider(self, capability: str, requester: str = "", allowed_plugins: list = None) -> Optional[Any]: if capability not in self.providers: return None if self.permission_check and allowed_plugins is not None: pn = self.providers[capability]["plugin"] if pn != requester and pn not in allowed_plugins and "*" not in allowed_plugins: raise PermissionError(f"插件 '{requester}' 无权使用能力 '{capability}'") return self.providers[capability]["instance"] def has_capability(self, capability: str) -> bool: return capability in self.providers def get_consumers(self, capability: str) -> list: return self.consumers.get(capability, []) # ═══════════════════════════════════════════════════════════════ # 依赖解析 # ═══════════════════════════════════════════════════════════════ class DependencyError(Exception): pass class DependencyResolver: def __init__(self): self.graph: dict[str, list[str]] = {} def add_dependency(self, name: str, dependencies: list[str]): self.graph[name] = dependencies def resolve(self) -> list[str]: self._detect_cycles() in_degree: dict[str, int] = {name: 0 for name in self.graph} who_depends_on: dict[str, list[str]] = {name: [] for name in self.graph} for name, deps in self.graph.items(): for dep in deps: if dep in in_degree: in_degree[name] += 1 who_depends_on[dep].append(name) queue = [name for name, degree in in_degree.items() if degree == 0] result = [] while queue: node = queue.pop(0) result.append(node) for dependent in who_depends_on.get(node, []): in_degree[dependent] -= 1 if in_degree[dependent] == 0: queue.append(dependent) if len(result) != len(self.graph): raise DependencyError("无法解析依赖,可能存在循环依赖") return result def _detect_cycles(self): all_deps = set() for deps in self.graph.values(): all_deps.update(deps) all_plugins = set(self.graph.keys()) return list(all_deps - all_plugins) # ═══════════════════════════════════════════════════════════════ # 签名校验 # ═══════════════════════════════════════════════════════════════ class SignatureError(Exception): pass class SignatureVerifier: def __init__(self, key_dir: str = None): config = get_config() self.key_dir = Path(key_dir or str(config.get("SIGNATURE_KEYS_DIR", "./data/signature-verifier/keys"))) self.key_dir.mkdir(parents=True, exist_ok=True) self.public_keys: dict[str, bytes] = {} self._load_builtin_keys() def _load_builtin_keys(self): pub_dir = self.key_dir / "public" if not pub_dir.exists(): return for key_file in pub_dir.glob("*.pem"): author_name = key_file.stem self.public_keys[author_name] = key_file.read_bytes() def _compute_plugin_hash(self, plugin_dir: Path) -> str: hasher = hashlib.sha256() files_to_hash = [] for file_path in sorted(plugin_dir.rglob("*")): if file_path.is_file() and file_path.name != "SIGNATURE": rel_path = file_path.relative_to(plugin_dir) files_to_hash.append((str(rel_path), file_path)) for rel_path, file_path in files_to_hash: hasher.update(rel_path.encode("utf-8")) hasher.update(file_path.read_bytes()) return hasher.hexdigest() def verify_plugin(self, plugin_dir: Path, author: str = "Falck") -> tuple[bool, str]: import base64 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidSignature signature_file = plugin_dir / "SIGNATURE" if not signature_file.exists(): return False, f"Plugin missing signature file: {plugin_dir}" try: sig_data = json.loads(signature_file.read_text()) except json.JSONDecodeError as e: return False, f"Signature file format error: {e}" required_fields = ["signature", "signer", "algorithm", "timestamp"] for field in required_fields: if field not in sig_data: return False, f"Signature missing required field: {field}" signer = sig_data["signer"] signature = base64.b64decode(sig_data["signature"]) if signer not in self.public_keys: return False, f"Unknown signer: {signer}" try: public_key = serialization.load_pem_public_key( self.public_keys[signer], backend=default_backend() ) except Exception as e: return False, f"Public key load failed: {e}" current_hash = self._compute_plugin_hash(plugin_dir) try: signed_data = f"{author}:{current_hash}".encode("utf-8") public_key.verify( signature, signed_data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256() ) return True, f"Signature verified (signer: {signer})" except InvalidSignature: return False, f"Signature mismatch! Plugin may have been tampered with (signer: {signer})" except Exception as e: return False, f"Signature verification error: {e}" def is_official_plugin(self, plugin_dir: Path) -> bool: """检查是否为官方插件(使用内置公钥验证)""" result, _ = self.verify_plugin(plugin_dir, author="NebulaShell") return result class PluginSigner: def __init__(self, private_key_path: str = None): self.private_key = None if private_key_path: self.load_private_key(private_key_path) def load_private_key(self, key_path: str): from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend with open(key_path, "rb") as f: pem_data = f.read() self.private_key = serialization.load_pem_private_key( pem_data, password=None, backend=default_backend() ) def sign_plugin(self, plugin_dir: Path, signer_name: str, author: str = "Falck") -> str: import base64 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.backends import default_backend if not self.private_key: raise ValueError("Private key not loaded") hasher = hashlib.sha256() files_to_hash = [] for file_path in sorted(plugin_dir.rglob("*")): if file_path.is_file() and file_path.name not in ("SIGNATURE",): rel_path = file_path.relative_to(plugin_dir) files_to_hash.append((str(rel_path), file_path)) for rel_path, file_path in files_to_hash: hasher.update(rel_path.encode("utf-8")) hasher.update(file_path.read_bytes()) plugin_hash = hasher.hexdigest() signed_data = f"{author}:{plugin_hash}".encode("utf-8") signature = self.private_key.sign( signed_data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256() ) sig_data = { "signature": base64.b64encode(signature).decode(), "signer": signer_name, "algorithm": "RSA-SHA256", "timestamp": time.time(), "plugin_hash": plugin_hash, "author": author } signature_file = plugin_dir / "SIGNATURE" signature_file.write_text(json.dumps(sig_data, indent=2)) return str(signature_file) # ═══════════════════════════════════════════════════════════════ # 文件监控与热重载 # ═══════════════════════════════════════════════════════════════ class HotReloadError(Exception): pass class FileWatcher: def __init__(self, watch_dirs, extensions, callback): self.watch_dirs = watch_dirs self.extensions = extensions self.callback = callback self._running = False self._thread = None self._file_times = {} self._init_file_times() def _init_file_times(self): for watch_dir in self.watch_dirs: p = Path(watch_dir) if p.exists(): for f in p.rglob("*"): if f.is_file() and f.suffix in self.extensions: self._file_times[str(f)] = f.stat().st_mtime def start(self): self._running = True self._thread = threading.Thread(target=self._watch_loop, daemon=True) self._thread.start() Log.info("Core", "文件监控已启动") def stop(self): self._running = False if self._thread: self._thread.join(timeout=5) def _watch_loop(self): """监控文件变化,触发热重载回调""" while self._running: try: for watch_dir in self.watch_dirs: p = Path(watch_dir) if not p.exists(): continue for f in p.rglob("*"): if not f.is_file() or f.suffix not in self.extensions: continue current_mtime = f.stat().st_mtime last_mtime = self._file_times.get(str(f)) if last_mtime is not None and current_mtime > last_mtime: self._file_times[str(f)] = current_mtime try: self.callback(str(f)) except Exception as e: Log.error("Core", f"热重载回调执行失败: {e}") elif last_mtime is None: self._file_times[str(f)] = current_mtime except Exception as e: Log.error("Core", f"文件监控异常: {e}") time.sleep(2) # ═══════════════════════════════════════════════════════════════ # 全面防护机制 # ═══════════════════════════════════════════════════════════════ class IntegrityChecker: """文件完整性检查""" def __init__(self): self._hashes: dict[str, str] = {} def compute_hash(self, plugin_dir: Path) -> str: """计算插件目录的 SHA-256 hash""" hasher = hashlib.sha256() for file_path in sorted(plugin_dir.rglob("*")): if file_path.is_file() and file_path.name not in ("SIGNATURE", "__pycache__"): rel_path = str(file_path.relative_to(plugin_dir)) hasher.update(rel_path.encode("utf-8")) hasher.update(file_path.read_bytes()) return hasher.hexdigest() def register(self, plugin_name: str, plugin_dir: Path): """注册插件的初始 hash""" self._hashes[plugin_name] = self.compute_hash(plugin_dir) def verify(self, plugin_name: str, plugin_dir: Path) -> tuple[bool, str]: """验证插件文件是否被篡改""" if plugin_name not in self._hashes: return False, f"插件 '{plugin_name}' 未注册完整性检查" current = self.compute_hash(plugin_dir) if current == self._hashes[plugin_name]: return True, "完整性验证通过" return False, f"文件 hash 不匹配,插件可能被篡改" def get_hash(self, plugin_name: str) -> Optional[str]: return self._hashes.get(plugin_name) class MemoryGuard: """运行时内存保护 - 防止插件修改 Core 内部状态""" FROZEN_ATTRS = { "plugins", "capability_registry", "lifecycle_manager", "dependency_resolver", "signature_verifier", "pl_injector", "integrity_checker", "audit_logger", "tamper_monitor", "fallback_manager", "http_server", "repl_shell", } def __init__(self, manager: 'PluginManager'): self._manager = manager self._protected = True def enable(self): self._protected = True def disable(self): self._protected = False def check_setattr(self, obj: Any, name: str, value: Any) -> bool: """检查是否允许设置属性,返回 False 表示拒绝""" if not self._protected: return True if obj is self._manager and name in self.FROZEN_ATTRS: Log.warn("Core", f"内存防护: 阻止了对 Core 内部属性 '{name}' 的修改") return False return True class AuditLogger: """插件行为审计""" def __init__(self, max_logs: int = 1000): self._logs: deque = deque(maxlen=max_logs) self._enabled = True def enable(self): self._enabled = True def disable(self): self._enabled = False def log(self, plugin_name: str, action: str, detail: str = ""): """记录插件行为""" if not self._enabled: return self._logs.append({ "time": time.time(), "plugin": plugin_name, "action": action, "detail": detail, }) def get_logs(self, plugin_name: str = None, limit: int = 50) -> list[dict]: """查询审计日志""" if plugin_name: filtered = [log for log in self._logs if log["plugin"] == plugin_name] else: filtered = list(self._logs) return filtered[-limit:] def get_stats(self) -> dict: """获取审计统计""" stats: dict[str, int] = {} for log in self._logs: stats[log["plugin"]] = stats.get(log["plugin"], 0) + 1 return stats class TamperMonitor: """防篡改监控 - 定期检查已加载插件的文件完整性""" def __init__(self, manager: 'PluginManager', interval: int = 30): self._manager = manager self._interval = interval self._running = False self._thread = None self._alerts: deque = deque(maxlen=100) def start(self): self._running = True self._thread = threading.Thread(target=self._monitor_loop, daemon=True) self._thread.start() Log.info("Core", f"防篡改监控已启动 (间隔: {self._interval}s)") def stop(self): self._running = False if self._thread: self._thread.join(timeout=5) def _monitor_loop(self): while self._running: try: for plugin_name, info in self._manager.plugins.items(): plugin_dir = self._manager._get_plugin_dir(plugin_name) if not plugin_dir: continue valid, msg = self._manager.integrity_checker.verify(plugin_name, plugin_dir) if not valid: alert = { "time": time.time(), "plugin": plugin_name, "message": msg, } self._alerts.append(alert) Log.error("Core", f"防篡改告警: 插件 '{plugin_name}' 可能被篡改!") # 自动停止被篡改的插件 try: info["instance"].stop() lifecycle = self._manager.lifecycle_manager.get(plugin_name) if lifecycle: lifecycle.mark_crashed() except Exception as e: Log.error("Core", f"停止被篡改插件 '{plugin_name}' 失败: {e}") except Exception as e: Log.error("Core", f"防篡改监控异常: {e}") time.sleep(self._interval) def get_alerts(self) -> list[dict]: return list(self._alerts) class FallbackManager: """降级恢复机制 - 插件崩溃时自动重启""" def __init__(self, manager: 'PluginManager', max_retries: int = 3): self._manager = manager self._max_retries = max_retries self._retry_counts: dict[str, int] = {} self._degraded: set[str] = set() def wrap_plugin_method(self, plugin_name: str, method: Callable) -> Callable: """包装插件方法,捕获异常后自动重试""" @functools.wraps(method) def safe_method(*args, **kwargs): try: return method(*args, **kwargs) except Exception as e: Log.error("Core", f"插件 '{plugin_name}' 方法 '{method.__name__}' 异常: {e}") self._handle_crash(plugin_name) return None return safe_method def _handle_crash(self, plugin_name: str): """处理插件崩溃""" retry_count = self._retry_counts.get(plugin_name, 0) lifecycle = self._manager.lifecycle_manager.get(plugin_name) if retry_count < self._max_retries: self._retry_counts[plugin_name] = retry_count + 1 Log.warn("Core", f"插件 '{plugin_name}' 崩溃,正在重启 (第 {retry_count + 1}/{self._max_retries} 次)") try: if lifecycle: lifecycle.mark_crashed() self._manager._restart_plugin(plugin_name) if lifecycle: lifecycle.start() Log.ok("Core", f"插件 '{plugin_name}' 重启成功") except Exception as e: Log.error("Core", f"插件 '{plugin_name}' 重启失败: {e}") else: Log.error("Core", f"插件 '{plugin_name}' 超过最大重试次数 ({self._max_retries}),标记为降级") self._degraded.add(plugin_name) if lifecycle: lifecycle.mark_degraded() def recover(self, plugin_name: str) -> bool: """手动恢复降级的插件""" if plugin_name not in self._degraded: return False self._retry_counts[plugin_name] = 0 self._degraded.discard(plugin_name) try: self._manager._restart_plugin(plugin_name) lifecycle = self._manager.lifecycle_manager.get(plugin_name) if lifecycle: lifecycle.start() Log.ok("Core", f"插件 '{plugin_name}' 已手动恢复") return True except Exception as e: Log.error("Core", f"恢复插件 '{plugin_name}' 失败: {e}") return False def is_degraded(self, plugin_name: str) -> bool: return plugin_name in self._degraded def get_degraded_plugins(self) -> list[str]: return list(self._degraded) # ═══════════════════════════════════════════════════════════════ # 数据存储接口(为 data-store 插件预留) # ═══════════════════════════════════════════════════════════════ class DataStore: """数据存储抽象接口 默认实现使用 JSON 文件存储到 ~/.nebula/data/ 后续可由 data-store 插件替换为更完善的实现 """ def __init__(self): config = get_config() data_dir_env = os.environ.get("NEBULA_DATA_DIR", "") default_dir = Path(data_dir_env) if data_dir_env else Path.home() / ".nebula" / "data" self._base_dir = Path(config.get("DATA_DIR", str(default_dir))) self._base_dir.mkdir(parents=True, exist_ok=True) self._lock = threading.Lock() def _plugin_dir(self, plugin_name: str) -> Path: """获取插件专属数据目录""" pd = self._base_dir / plugin_name pd.mkdir(parents=True, exist_ok=True) return pd def save(self, plugin_name: str, key: str, data: Any) -> bool: """保存数据""" with self._lock: try: file_path = self._plugin_dir(plugin_name) / f"{key}.json" file_path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") return True except Exception as e: Log.error("Core", f"数据存储保存失败 [{plugin_name}/{key}]: {e}") return False def load(self, plugin_name: str, key: str, default: Any = None) -> Any: """加载数据""" with self._lock: try: file_path = self._plugin_dir(plugin_name) / f"{key}.json" if file_path.exists(): return json.loads(file_path.read_text(encoding="utf-8")) return default except Exception as e: Log.error("Core", f"数据存储加载失败 [{plugin_name}/{key}]: {e}") return default def delete(self, plugin_name: str, key: str) -> bool: """删除数据""" with self._lock: try: file_path = self._plugin_dir(plugin_name) / f"{key}.json" if file_path.exists(): file_path.unlink() return True except Exception as e: Log.error("Core", f"数据存储删除失败 [{plugin_name}/{key}]: {e}") return False def list_keys(self, plugin_name: str) -> list[str]: """列出插件所有数据键""" pd = self._plugin_dir(plugin_name) if not pd.exists(): return [] return [f.stem for f in pd.glob("*.json")] def set_custom_path(self, plugin_name: str, custom_path: str) -> bool: """插件自定义存储路径(不能修改到项目目录内)""" path = Path(custom_path).expanduser().resolve() project_dir = Path.cwd().resolve() if str(path).startswith(str(project_dir)): Log.error("Core", f"插件 '{plugin_name}' 试图将数据存储到项目目录: {custom_path}") return False path.mkdir(parents=True, exist_ok=True) # 创建符号链接或记录映射 mapping_file = self._base_dir / "_custom_paths.json" mappings = {} if mapping_file.exists(): try: mappings = json.loads(mapping_file.read_text()) except (json.JSONDecodeError, OSError): pass mappings[plugin_name] = str(path) mapping_file.write_text(json.dumps(mappings, indent=2)) return True # ═══════════════════════════════════════════════════════════════ # PL 注入 # ═══════════════════════════════════════════════════════════════ class PLValidationError(Exception): """PL 校验错误""" pass class PLInjector: """PL 注入管理器 - 带完整安全限制""" MAX_FUNCTIONS_PER_PLUGIN = 50 MAX_REGISTRATIONS_PER_NAME = 10 MAX_NAME_LENGTH = 128 MAX_DESCRIPTION_LENGTH = 256 _FUNCTION_NAME_RE = re.compile(r'^[a-zA-Z0-9_:/\-.]+$') _EVENT_NAME_RE = re.compile(r'^[a-zA-Z][a-zA-Z0-9_.]+$') _ROUTE_PATH_RE = re.compile(r'^/[a-zA-Z0-9_\-/.]+$') _FORBIDDEN_ROUTE_PATTERNS = [r'\.\.', r'//', r'/\.', r'~', r'\%'] def __init__(self, plugin_manager: 'PluginManager'): self._plugin_manager = plugin_manager self._injections: dict = {} self._injection_registry: dict = {} self._plugin_function_count: dict = {} def check_and_load_pl(self, plugin_dir: Path, plugin_name: str) -> bool: """检查并加载 PL 文件夹,返回 True 表示成功""" pl_dir = plugin_dir / "PL" if not pl_dir.exists() or not pl_dir.is_dir(): Log.warn("Core", f"插件 '{plugin_name}' 声明了 pl_injection,但缺少 PL/ 文件夹,拒绝加载") return False pl_main = pl_dir / "main.py" if not pl_main.exists(): Log.warn("Core", f"插件 '{plugin_name}' 的 PL/ 文件夹中缺少 main.py,拒绝加载") return False # 禁止危险文件类型 forbidden_ext = {'.sh', '.bat', '.exe', '.dll', '.so', '.dylib', '.bin'} for f in pl_dir.rglob('*'): if f.suffix.lower() in forbidden_ext: Log.error("Core", f"插件 '{plugin_name}' 的 PL/ 文件夹包含危险文件: {f.name},拒绝加载") return False try: # 受限沙箱 safe_builtins = { 'True': True, 'False': False, 'None': None, 'dict': dict, 'list': list, 'str': str, 'int': int, 'float': float, 'bool': bool, 'tuple': tuple, 'set': set, 'len': len, 'range': range, 'enumerate': enumerate, 'zip': zip, 'map': map, 'filter': filter, 'sorted': sorted, 'reversed': reversed, 'min': min, 'max': max, 'sum': sum, 'abs': abs, 'round': round, 'isinstance': isinstance, 'issubclass': issubclass, 'type': type, 'id': id, 'hash': hash, 'repr': repr, 'print': print, 'object': object, 'property': property, 'staticmethod': staticmethod, 'classmethod': classmethod, 'super': super, 'iter': iter, 'next': next, 'any': any, 'all': all, 'callable': callable, 'hasattr': hasattr, 'getattr': getattr, 'setattr': setattr, 'ValueError': ValueError, 'TypeError': TypeError, 'KeyError': KeyError, 'IndexError': IndexError, 'Exception': Exception, 'BaseException': BaseException, } safe_globals = { '__builtins__': safe_builtins, '__name__': f'plugin.{plugin_name}.PL', '__package__': f'plugin.{plugin_name}.PL', '__file__': str(pl_main), } with open(pl_main, 'r', encoding='utf-8') as f: source = f.read() # 静态源码安全检查 self._static_source_check(source, str(pl_main)) code = compile(source, str(pl_main), 'exec') exec(code, safe_globals) register_func = safe_globals.get('register') if register_func and callable(register_func): register_func(self) Log.ok("Core", f"插件 '{plugin_name}' PL 注入成功") else: Log.warn("Core", f"插件 '{plugin_name}' 的 PL/main.py 缺少 register() 函数,但仍允许加载") self._injections[plugin_name] = {"dir": str(pl_dir)} return True except PLValidationError as e: Log.error("Core", f"插件 '{plugin_name}' PL 安全检查失败: {e}") return False except SyntaxError as e: Log.error("Core", f"插件 '{plugin_name}' PL/main.py 语法错误: {e}") return False except FileNotFoundError as e: Log.error("Core", f"插件 '{plugin_name}' PL 文件不存在:{e}") return False except PermissionError as e: Log.error("Core", f"插件 '{plugin_name}' PL 文件权限错误:{e}") return False except Exception as e: Log.error("Core", f"加载插件 '{plugin_name}' 的 PL 失败:{type(e).__name__}: {e}") traceback.print_exc() return False def _static_source_check(self, source: str, file_path: str): """静态源码安全检查 - 增强版,防止字符串拼接/编码绕过""" import base64 # 首先检查是否有 base64 编码的恶意代码 try: string_pattern = r'([A-Za-z0-9+/=]{20,})' for match in re.finditer(string_pattern, source): try: decoded = base64.b64decode(match.group(1)).decode('utf-8', errors='ignore') for dangerous in ['import ', 'exec(', 'eval(', 'compile(', 'os.', 'sys.', 'subprocess']: if dangerous in decoded: raise PLValidationError(f"{file_path} - 检测到 base64 编码的恶意代码") except Exception: pass except Exception: pass # 检查字符串拼接绕过 concat_patterns = [ r"""['"]ex['"]\s*\+\s*['"]ec['"]""", r"""['"]impor['"]\s*\+\s*['"]t['"]""", r"""['"]eva['"]\s*\+\s*['"]l['"]""", r"""['"]compil['"]\s*\+\s*['"]e['"]""", ] for pattern in concat_patterns: if re.search(pattern, source): raise PLValidationError(f"{file_path} - 检测到字符串拼接绕过尝试") forbidden = [ (r'^\s*import\s+(os|sys|subprocess|shutil|socket|ctypes|cffi|multiprocessing|threading)', '禁止导入系统级模块'), (r'^\s*from\s+(os|sys|subprocess|shutil|socket|ctypes|cffi|multiprocessing|threading)\s+import', '禁止导入系统级模块'), (r'__import__\s*\(', '禁止使用 __import__'), (r'(? bool: if not name or not isinstance(name, str): return False if len(name) > self.MAX_NAME_LENGTH: return False return bool(self._FUNCTION_NAME_RE.match(name)) def _validate_route_path(self, path: str) -> bool: if not path or not isinstance(path, str): return False if len(path) > 256: return False if not self._ROUTE_PATH_RE.match(path): return False for p in self._FORBIDDEN_ROUTE_PATTERNS: if re.search(p, path): return False return True def _validate_event_name(self, event_name: str) -> bool: if not event_name or not isinstance(event_name, str): return False if len(event_name) > self.MAX_NAME_LENGTH: return False return bool(self._EVENT_NAME_RE.match(event_name)) def _check_plugin_limit(self, plugin_name: str) -> bool: count = self._plugin_function_count.get(plugin_name, 0) if count >= self.MAX_FUNCTIONS_PER_PLUGIN: Log.warn("Core", f"插件 '{plugin_name}' 注册功能数已达上限 ({self.MAX_FUNCTIONS_PER_PLUGIN})") return False return True def _check_name_limit(self, name: str) -> bool: registrations = self._injection_registry.get(name, []) if len(registrations) >= self.MAX_REGISTRATIONS_PER_NAME: Log.warn("Core", f"功能名称 '{name}' 注册次数已达上限 ({self.MAX_REGISTRATIONS_PER_NAME})") return False return True def _wrap_function(self, func: Callable, plugin_name: str, name: str) -> Callable: """包装函数,异常安全""" def _safe_wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: Log.error("Core", f"PL 注入功能 '{name}' (来自 {plugin_name}) 执行异常: {e}") return None return _safe_wrapper def _get_caller_plugin_name(self) -> Optional[str]: """通过栈帧回溯获取调用者插件名""" stack = traceback.extract_stack() for frame in stack: filename = frame.filename if '/PL/' in filename and 'main.py' in filename: parts = Path(filename).parts for i, part in enumerate(parts): if part == 'PL': return parts[i - 1] if i > 0 else None return None def register_function(self, name: str, func: Callable, description: str = ""): """注册注入功能 - 带参数校验和权限限制""" if not self._validate_function_name(name): Log.error("Core", f"PL 注入功能名称非法: '{name}'") return if not callable(func): Log.error("Core", f"PL 注入功能 '{name}' 不是可调用对象") return if description and len(description) > self.MAX_DESCRIPTION_LENGTH: description = description[:self.MAX_DESCRIPTION_LENGTH] plugin_name = self._get_caller_plugin_name() or "unknown" if not self._check_plugin_limit(plugin_name): return if not self._check_name_limit(name): return wrapped_func = self._wrap_function(func, plugin_name, name) if name not in self._injection_registry: self._injection_registry[name] = [] self._injection_registry[name].append({ "func": wrapped_func, "plugin": plugin_name, "description": description, }) self._plugin_function_count[plugin_name] = self._plugin_function_count.get(plugin_name, 0) + 1 Log.tip("Core", f"PL 注入功能已注册: '{name}' (来自 {plugin_name})") def register_route(self, method: str, path: str, handler: Callable): """注册 HTTP 路由 - 带路径安全校验""" valid_methods = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'} method_upper = method.upper() if method_upper not in valid_methods: Log.error("Core", f"PL 注入路由方法非法: '{method}'") return if not self._validate_route_path(path): Log.error("Core", f"PL 注入路由路径非法: '{path}'") return self.register_function(f"{method_upper}:{path}", handler, f"路由 {method_upper} {path}") def register_event_handler(self, event_name: str, handler: Callable): """注册事件处理器 - 带名称校验""" if not self._validate_event_name(event_name): Log.error("Core", f"PL 注入事件名称非法: '{event_name}'") return self.register_function(f"event:{event_name}", handler, f"事件 {event_name}") def get_injected_functions(self, name: str = None) -> list[Callable]: if name: return [e["func"] for e in self._injection_registry.get(name, [])] return [f for es in self._injection_registry.values() for f in [e["func"] for e in es]] def get_injection_info(self, plugin_name: str = None) -> dict: if plugin_name: return self._injections.get(plugin_name, {}) return dict(self._injections) def has_injection(self, plugin_name: str) -> bool: return plugin_name in self._injections def get_registry_info(self) -> dict: info = {} for name, entries in self._injection_registry.items(): info[name] = { "count": len(entries), "plugins": [e["plugin"] for e in entries], "descriptions": [e["description"] for e in entries], } return info # ═══════════════════════════════════════════════════════════════ # PluginManager — 核心管理器 # ═══════════════════════════════════════════════════════════════ class PluginManager: """插件管理器 — Core 的核心""" def __init__(self, permission_check: bool = True): self.plugins: dict = {} self.capability_registry = CapabilityRegistry(permission_check=permission_check) self.permission_check = permission_check self.enforce_signature = True self.pl_injector = PLInjector(self) self.lifecycle_manager = LifecycleManager() self.dependency_resolver = DependencyResolver() self.signature_verifier = SignatureVerifier() self.hot_reload_watcher = None # 全面防护 self.integrity_checker = IntegrityChecker() self.memory_guard = MemoryGuard(self) self.audit_logger = AuditLogger() self.tamper_monitor = TamperMonitor(self) self.fallback_manager = FallbackManager(self) # 数据存储 self.data_store = DataStore() # HTTP 服务 & REPL self.http_server = None self.repl_shell = None # NBPF 组件 self.nbpf_loader = None self._nbpf_initialized = False # 插件目录映射 self._plugin_dirs: dict[str, Path] = {} # ── NBPF 支持 ── def _init_nbpf(self): """初始化 NBPF 加载器""" if self._nbpf_initialized: return try: from oss.core.nbpf import NBPFLoader, NBPCrypto, NIRCompiler config = get_config() trusted_keys_dir = Path(config.get("NBPF_TRUSTED_KEYS_DIR", "./data/nbpf-keys/trusted")) rsa_keys_dir = Path(config.get("NBPF_RSA_KEYS_DIR", "./data/nbpf-keys/rsa")) # 加载信任的 Ed25519 公钥 trusted_ed25519 = {} if trusted_keys_dir.exists(): for kf in trusted_keys_dir.glob("*.pem"): name = kf.stem trusted_ed25519[name] = kf.read_bytes() # 加载信任的 RSA 公钥 trusted_rsa = {} if rsa_keys_dir.exists(): for kf in rsa_keys_dir.glob("*.pem"): name = kf.stem trusted_rsa[name] = kf.read_bytes() # 加载 RSA 私钥 rsa_private = None private_dir = Path(config.get("NBPF_KEYS_DIR", "./data/nbpf-keys")) / "private" if private_dir.exists(): pk_files = list(private_dir.glob("*.pem")) if pk_files: rsa_private = pk_files[0].read_bytes() self.nbpf_loader = NBPFLoader( crypto=NBPCrypto(), compiler=NIRCompiler(), trusted_ed25519_keys=trusted_ed25519, trusted_rsa_keys=trusted_rsa, rsa_private_key=rsa_private, ) self._nbpf_initialized = True Log.info("Core", "NBPF 加载器已初始化") except Exception as e: Log.warn("Core", f"NBPF 加载器初始化失败: {e}") def load_nbpf(self, nbpf_path: Path, plugin_name: str = None) -> Optional[Any]: """加载 .nbpf 插件文件 Args: nbpf_path: .nbpf 文件路径 plugin_name: 可选,插件名称 Returns: 插件实例,失败返回 None """ if not self._nbpf_initialized: self._init_nbpf() if self.nbpf_loader is None: Log.error("Core", "NBPF 加载器未初始化,无法加载 .nbpf 文件") return None try: instance, info = self.nbpf_loader.load(nbpf_path, plugin_name) name = info["name"] # 构建 PluginInfo pinfo = PluginInfo() pinfo.name = name pinfo.version = info.get("version", "") pinfo.author = info.get("author", "") pinfo.description = info.get("description", "") pinfo.dependencies = info.get("manifest", {}).get("dependencies", []) # 注册到插件列表 self.plugins[name] = { "instance": instance, "module": None, "info": pinfo, "permissions": [], "nbpf_path": str(nbpf_path), } self._plugin_dirs[name] = nbpf_path.parent # 生命周期 pinfo.lifecycle = self.lifecycle_manager.create(name) # 审计日志 self.audit_logger.log(name, "loaded", f".nbpf 版本 {pinfo.version}") Log.ok("Core", f"NBPF 插件 '{name}' 加载成功") return instance except Exception as e: Log.error("Core", f"NBPF 插件加载失败: {e}") return None def _get_plugin_dir(self, plugin_name: str) -> Optional[Path]: return self._plugin_dirs.get(plugin_name) def _load_manifest(self, plugin_dir: Path) -> dict: mf = plugin_dir / "manifest.json" if not mf.exists(): return {} with open(mf, "r", encoding="utf-8") as f: return json.load(f) def _load_readme(self, plugin_dir: Path) -> str: rf = plugin_dir / "README.md" if not rf.exists(): return "" with open(rf, "r", encoding="utf-8") as f: return f.read() def _parse_config_file(self, file_path: Path, file_type: str) -> dict: """通用配置文件解析 - 使用 ast.literal_eval 安全解析""" import ast if not file_path.exists(): return {} try: with open(file_path, "r", encoding="utf-8") as f: content = f.read() except FileNotFoundError: Log.warn("Core", f"{file_type}文件不存在:{file_path}") return {} except PermissionError as e: Log.error("Core", f"{file_type}文件无权限读取:{file_path} - {e}") return {} except UnicodeDecodeError as e: Log.error("Core", f"{file_type}文件编码错误:{file_path} - {e}") return {} try: result = ast.literal_eval(content) if isinstance(result, dict): return {k: v for k, v in result.items() if not k.startswith("_")} except (ValueError, SyntaxError): pass config = {} for line in content.split('\n'): line = line.strip() if not line or line.startswith('#'): continue match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', line) if match: key, value_str = match.groups() if key.startswith('_'): continue try: value = ast.literal_eval(value_str) config[key] = value except (ValueError, SyntaxError): Log.warn("Core", f"{file_path} 跳过无效的值:{line}") continue return config def _load_config(self, plugin_dir: Path) -> dict: return self._parse_config_file(plugin_dir / "config.py", "配置") def _load_extensions(self, plugin_dir: Path) -> dict: return self._parse_config_file(plugin_dir / "extensions.py", "扩展") def load(self, plugin_dir: Path, use_sandbox: bool = True) -> Optional[Any]: """加载单个插件 支持: - 目录结构插件(main.py) - .nbpf 文件(直接传入 .nbpf 路径) """ # 如果是 .nbpf 文件,使用 NBPF 加载器 if plugin_dir.suffix == ".nbpf": return self.load_nbpf(plugin_dir) main_file = plugin_dir / "main.py" if not main_file.exists(): return None manifest = self._load_manifest(plugin_dir) readme = self._load_readme(plugin_dir) config = self._load_config(plugin_dir) extensions = self._load_extensions(plugin_dir) capabilities = scan_capabilities(plugin_dir) plugin_name = plugin_dir.name.rstrip("}") # 完整性检查:加载前计算 hash self.integrity_checker.register(plugin_name, plugin_dir) # PL 注入检查 pl_injection = manifest.get("config", {}).get("args", {}).get("pl_injection", False) if pl_injection: Log.tip("Core", f"插件 '{plugin_name}' 声明了 pl_injection,正在检查 PL/ 文件夹...") if not self.pl_injector.check_and_load_pl(plugin_dir, plugin_name): Log.error("Core", f"插件 '{plugin_name}' 因 PL 注入检查失败被拒绝加载") return None Log.ok("Core", f"插件 '{plugin_name}' PL 注入检查通过") permissions = manifest.get("permissions", []) spec = importlib.util.spec_from_file_location(f"plugin.{plugin_name}", str(main_file)) module = importlib.util.module_from_spec(spec) module.__package__ = f"plugin.{plugin_name}" module.__path__ = [str(plugin_dir)] sys.modules[spec.name] = module spec.loader.exec_module(module) if not hasattr(module, "New"): return None instance = module.New() if self.permission_check and permissions: instance = PluginProxy(plugin_name, instance, permissions, self.plugins) info = PluginInfo() meta = manifest.get("metadata", {}) info.name = meta.get("name", plugin_name) info.version = meta.get("version", "") info.author = meta.get("author", "") info.description = meta.get("description", "") info.readme = readme info.config = manifest.get("config", {}).get("args", config) info.extensions = extensions info.capabilities = capabilities info.dependencies = manifest.get("dependencies", []) info.pl_injected = pl_injection info.file_hash = self.integrity_checker.get_hash(plugin_name) or "" for cap in capabilities: self.capability_registry.register_provider(cap, plugin_name, instance) info.lifecycle = self.lifecycle_manager.create(plugin_name) self.plugins[plugin_name] = {"instance": instance, "module": module, "info": info, "permissions": permissions} self._plugin_dirs[plugin_name] = plugin_dir # 审计日志 self.audit_logger.log(plugin_name, "loaded", f"版本 {info.version}") return instance def _restart_plugin(self, plugin_name: str): """重启单个插件""" if plugin_name not in self.plugins: return plugin_dir = self._plugin_dirs.get(plugin_name) if not plugin_dir: return # 停止旧实例 try: if hasattr(self.plugins[plugin_name]["instance"], "stop"): self.plugins[plugin_name]["instance"].stop() except Exception: pass # 从 sys.modules 中移除 module_name = f"plugin.{plugin_name}" if module_name in sys.modules: del sys.modules[module_name] module_name = f"nbpf.{plugin_name}" if module_name in sys.modules: del sys.modules[module_name] # 重新加载 del self.plugins[plugin_name] self.load(plugin_dir) def load_all(self, store_dir: str = "store"): if 'plugin' not in sys.modules: pkg = types.ModuleType('plugin') pkg.__path__ = [] pkg.__package__ = 'plugin' sys.modules['plugin'] = pkg Log.tip("Core", "已创建 plugin 命名空间包") if not self._check_any_plugins(store_dir): Log.warn("Core", "未检测到任何插件,自动引导安装...") self._bootstrap_installation() self._load_plugins_from_dir(Path(store_dir)) self._sort_by_dependencies() def _load_plugins_from_dir(self, store_dir: Path): if not store_dir.exists(): return core_plugins = set() skip = {"Core", "archive"} plugin_dirs = [] for ad in store_dir.iterdir(): if ad.is_dir(): for pd in ad.iterdir(): if pd.name in skip: continue # 支持目录插件(main.py)和 .nbpf 文件 if pd.is_dir() and (pd / "main.py").exists(): priority = 100 manifest_file = pd / "manifest.json" if manifest_file.exists(): try: meta = json.loads(manifest_file.read_text()).get("metadata", {}) raw = meta.get("load_priority", 100) priority = 0 if raw == "first" else (int(raw) if isinstance(raw, (int, float)) else 100) except (json.JSONDecodeError, OSError, (ValueError, TypeError)): pass plugin_dirs.append((priority, pd)) elif pd.suffix == ".nbpf": # .nbpf 文件,优先级 50(在普通插件之前加载) plugin_dirs.append((50, pd)) plugin_dirs.sort(key=lambda x: x[0]) for _, pd in plugin_dirs: self.load(pd, use_sandbox=pd.name not in core_plugins) self._link_capabilities() def _check_any_plugins(self, store_dir: str) -> bool: sp = Path(store_dir) if sp.exists(): for ad in sp.iterdir(): if ad.is_dir(): for pd in ad.iterdir(): if pd.name in {"Core", "archive"}: continue if pd.is_dir() and (pd / "main.py").exists(): return True if pd.suffix == ".nbpf": return True return False def _bootstrap_installation(self): Log.info("Core", "跳过引导安装(无可用插件)") def _sort_by_dependencies(self): for n, i in self.plugins.items(): self.dependency_resolver.add_dependency(n, i["info"].dependencies) try: order = self.dependency_resolver.resolve() sp = {} for n in order: if n in self.plugins: sp[n] = self.plugins[n] for n in set(self.plugins.keys()) - set(sp.keys()): sp[n] = self.plugins[n] self.plugins = sp except Exception as e: Log.error("Core", f"依赖解析失败: {e}") def _link_capabilities(self): for pn, info in self.plugins.items(): for cap in info["info"].capabilities: if self.capability_registry.has_capability(cap): for cn in self.capability_registry.get_consumers(cap): if cn in self.plugins: ci = self.plugins[cn]["info"] ca = self.plugins[cn].get("permissions", []) try: p = self.capability_registry.get_provider(cap, requester=cn, allowed_plugins=ca) if p and hasattr(ci, "extensions"): ci.extensions[f"_{cap}_provider"] = p except PermissionError as e: Log.error("Core", f"权限拒绝: {e}") def start_all(self): self._inject_dependencies() for n, i in self.plugins.items(): try: wrapped = self.fallback_manager.wrap_plugin_method(n, i["instance"].start) wrapped() except Exception as e: Log.error("Core", f"启动失败 {n}: {e}") def init_and_start_all(self): Log.info("Core", f"init_and_start_all 被调用,plugins={len(self.plugins)}") self._inject_dependencies() ordered = self._get_ordered_plugins() Log.tip("Core", f"插件启动顺序: {' -> '.join(ordered)}") for name in ordered: if "Core" in name: continue try: Log.info("Core", f"初始化: {name}") wrapped_init = self.fallback_manager.wrap_plugin_method(name, self.plugins[name]["instance"].init) wrapped_init() except Exception as e: Log.error("Core", f"初始化失败 {name}: {e}") for name in ordered: if "Core" in name: continue try: Log.info("Core", f"启动: {name}") wrapped_start = self.fallback_manager.wrap_plugin_method(name, self.plugins[name]["instance"].start) wrapped_start() except Exception as e: Log.error("Core", f"启动失败 {name}: {e}") def _get_ordered_plugins(self) -> list[str]: try: return [n for n in self.dependency_resolver.resolve() if n in self.plugins] except Exception as e: Log.warn("Core", f"依赖解析失败,使用原始顺序: {e}") return list(self.plugins.keys()) def _inject_dependencies(self): Log.info("Core", f"开始注入依赖,共 {len(self.plugins)} 个插件") nm = {} for n in self.plugins: c = n.rstrip("}") nm[c] = n nm[c + "}"] = n for n, i in self.plugins.items(): inst = i["instance"] io = i.get("info") if not io or not io.dependencies: continue for dn in io.dependencies: ad = nm.get(dn) or nm.get(dn + "}") if ad and ad in self.plugins: sn = f"set_{dn.replace('-', '_')}" if hasattr(inst, sn): try: getattr(inst, sn)(self.plugins[ad]["instance"]) Log.ok("Core", f"注入成功: {n} <- {ad}") except Exception as e: Log.error("Core", f"注入依赖失败 {n}.{sn}: {e}") else: Log.warn("Core", f"{n} 没有 {sn} 方法") def stop_all(self): for n, i in reversed(list(self.plugins.items())): try: if hasattr(i["instance"], "stop"): i["instance"].stop() except Exception as e: Log.error("Core", f"插件 {n} 停止失败:{type(e).__name__}: {e}") self.lifecycle_manager.stop_all() def get_info(self, name: str) -> Optional[PluginInfo]: if name in self.plugins: return self.plugins[name]["info"] return None def has_capability(self, capability: str) -> bool: return self.capability_registry.has_capability(capability) def get_capability_provider(self, capability: str) -> Optional[Any]: return self.capability_registry.get_provider(capability) # ── HTTP 服务 ── def start_http_server(self): """启动 HTTP 服务(子模块)""" try: from oss.core.http_api.server import HttpServer from oss.core.http_api.router import HttpRouter from oss.core.http_api.middleware import MiddlewareChain router = HttpRouter() middleware = MiddlewareChain() self.http_server = HttpServer(router=router, middleware=middleware) self.http_server.start() Log.ok("Core", "HTTP 服务已启动") except Exception as e: Log.error("Core", f"HTTP 服务启动失败: {e}") def stop_http_server(self): """停止 HTTP 服务""" if self.http_server: try: self.http_server.stop() Log.info("Core", "HTTP 服务已停止") except Exception as e: Log.error("Core", f"HTTP 服务停止失败: {e}") def get_http_router(self): """获取 HTTP 路由器""" if self.http_server: return self.http_server.router return None # ── REPL ── def start_repl(self): """启动 REPL 终端(子模块)""" try: from oss.core.repl.main import NebulaShell self.repl_shell = NebulaShell(self) Log.ok("Core", "REPL 终端已启动") self.repl_shell.cmdloop() except Exception as e: Log.error("Core", f"REPL 启动失败: {e}") # ── 防护管理 ── def start_tamper_monitor(self): """启动防篡改监控""" self.tamper_monitor.start() def stop_tamper_monitor(self): """停止防篡改监控""" self.tamper_monitor.stop() def get_audit_logs(self, plugin_name: str = None, limit: int = 50) -> list[dict]: """获取审计日志""" return self.audit_logger.get_logs(plugin_name, limit) def get_tamper_alerts(self) -> list[dict]: """获取防篡改告警""" return self.tamper_monitor.get_alerts() def get_degraded_plugins(self) -> list[str]: """获取降级插件列表""" return self.fallback_manager.get_degraded_plugins() def recover_plugin(self, plugin_name: str) -> bool: """手动恢复降级插件""" return self.fallback_manager.recover(plugin_name) def get_status(self) -> dict: """获取 Core 状态摘要""" nbpf_count = sum(1 for i in self.plugins.values() if i.get("nbpf_path")) return { "plugins": { "total": len(self.plugins), "nbpf": nbpf_count, "directory": len(self.plugins) - nbpf_count, "degraded": self.fallback_manager.get_degraded_plugins(), }, "nbpf_loader": self._nbpf_initialized, "http_server": self.http_server is not None, "tamper_monitor": self.tamper_monitor._running, "audit_logs": len(self.audit_logger._logs), "tamper_alerts": len(self.tamper_monitor._alerts), "data_store": str(self.data_store._base_dir), } # ═══════════════════════════════════════════════════════════════ # 类型注册 # ═══════════════════════════════════════════════════════════════ register_plugin_type("PluginManager", PluginManager) register_plugin_type("PluginInfo", PluginInfo) register_plugin_type("CapabilityRegistry", CapabilityRegistry) register_plugin_type("PLInjector", PLInjector) register_plugin_type("Lifecycle", Lifecycle) register_plugin_type("LifecycleManager", LifecycleManager) register_plugin_type("DependencyResolver", DependencyResolver) register_plugin_type("SignatureVerifier", SignatureVerifier) register_plugin_type("IntegrityChecker", IntegrityChecker) register_plugin_type("AuditLogger", AuditLogger) register_plugin_type("TamperMonitor", TamperMonitor) register_plugin_type("FallbackManager", FallbackManager) register_plugin_type("DataStore", DataStore)