from __future__ import annotations import json import threading from pathlib import Path from typing import Dict, List from config import HOST_SANDBOX_MACOS_WRITABLE_PATHS, deploy_config_path # macOS 默认拒绝读取的敏感路径(支持 ~/ 前缀)。 DEFAULT_MACOS_DENY_READ_PATHS = [ "~/.ssh", "~/.aws", "~/.azure", "~/.gcp", "~/.google", "~/.kube", "~/.docker", "~/.gnupg", "~/.npmrc", "~/.netrc", "~/.pypirc", "~/.git-credentials", "~/.bash_history", "~/.zsh_history", "~/.psql_history", "~/.mysql_history", "~/.pgpass", "/Library/Keychains", "~/Library/Keychains", ] # 默认按正则拒绝读取的敏感文件(可匹配文件系统中任意位置)。 DEFAULT_MACOS_DENY_READ_REGEXES = [ r"^/.*\.env(\.[^/]*)?$", ] _LOCK = threading.Lock() # 部署级配置(机器特定可读写路径,会被运行时写回)→ ~/.agents//config _POLICY_PATH = Path(deploy_config_path("host_sandbox_policy.json")) def _default_policy() -> Dict: return { "macos_writable_paths": [], "macos_readable_extra_paths": [], "macos_deny_read_paths": list(DEFAULT_MACOS_DENY_READ_PATHS), "macos_deny_read_regexes": list(DEFAULT_MACOS_DENY_READ_REGEXES), } def _ensure_file() -> None: if _POLICY_PATH.exists(): return _POLICY_PATH.parent.mkdir(parents=True, exist_ok=True) _POLICY_PATH.write_text( json.dumps(_default_policy(), ensure_ascii=False, indent=2), encoding="utf-8", ) def load_policy() -> Dict: with _LOCK: _ensure_file() try: data = json.loads(_POLICY_PATH.read_text(encoding="utf-8")) except Exception: data = _default_policy() if not isinstance(data, dict): data = _default_policy() defaults = _default_policy() needs_save = False for key in defaults: if key not in data or not isinstance(data.get(key), list): data[key] = list(defaults[key]) needs_save = True data["macos_writable_paths"] = [str(x).strip() for x in data["macos_writable_paths"] if str(x).strip()] data["macos_readable_extra_paths"] = [str(x).strip() for x in data["macos_readable_extra_paths"] if str(x).strip()] data["macos_deny_read_paths"] = [str(x).strip() for x in data["macos_deny_read_paths"] if str(x).strip()] data["macos_deny_read_regexes"] = [str(x).strip() for x in data["macos_deny_read_regexes"] if str(x).strip()] if needs_save: try: _POLICY_PATH.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") except Exception: pass return data def save_policy(policy: Dict) -> Dict: payload = dict(policy or {}) defaults = _default_policy() for key in defaults: items = payload.get(key) if not isinstance(items, list): items = list(defaults[key]) payload[key] = [str(x).strip() for x in items if str(x).strip()] with _LOCK: _ensure_file() _POLICY_PATH.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") return payload def get_macos_writable_paths() -> List[str]: file_items = load_policy().get("macos_writable_paths", []) merged: List[str] = [] for raw in list(HOST_SANDBOX_MACOS_WRITABLE_PATHS or []) + list(file_items or []): val = str(raw).strip() if val and val not in merged: merged.append(val) return merged def get_macos_readable_paths() -> List[str]: data = load_policy() readable_extra = data.get("macos_readable_extra_paths", []) merged: List[str] = [] for raw in list(get_macos_writable_paths()) + list(readable_extra or []): val = str(raw).strip() if val and val not in merged: merged.append(val) return merged def get_macos_deny_read_paths() -> List[str]: data = load_policy() paths = data.get("macos_deny_read_paths", []) merged: List[str] = [] for raw in list(DEFAULT_MACOS_DENY_READ_PATHS) + list(paths or []): val = str(raw).strip() if val and val not in merged: merged.append(val) return merged def get_macos_deny_read_regexes() -> List[str]: data = load_policy() patterns = data.get("macos_deny_read_regexes", []) merged: List[str] = [] for raw in list(DEFAULT_MACOS_DENY_READ_REGEXES) + list(patterns or []): val = str(raw).strip() if val and val not in merged: merged.append(val) return merged