Files
AgentLab/backend/app/core/config.py
T
ThearchyHelios dd57c12016 沙箱改用系统自带的隔离原语,移除 Docker 依赖
跑一段不可信代码不需要一整套容器运行时。macOS 用 Seatbelt
(sandbox-exec,系统自带),Linux 用 bubblewrap;冷启动从 ~250ms
降到 ~25ms,且不再需要装任何东西。

守住三件事:默认断网、家目录不可读、只有工作区可写。策略由内核
强制且对子进程继承——沙箱内 subprocess 出来的 cat 读家目录同样是
Operation not permitted。解释器改用 sys.base_prefix 下那个干净的
Python,沙箱代码因此 import 不到后端的 FastAPI、SQLAlchemy 和凭据
处理代码。

如实标注能力边界,不给虚假的安全感:
- macOS 不强制 RLIMIT_AS,内存限不住(实测设 256MB 仍可分配 900MB),
  health 接口申报 enforced 字段,界面把限不住的项划掉
- Seatbelt 是访问控制而非虚拟化:进程表共享,以当前用户身份运行
- bubblewrap 侧多隔离 PID/IPC/挂载视图,内存限额真实生效

顺带修两个 bug:
- LocalSandbox 设 RLIMIT_NPROC=64 会误伤——macOS 上它按 uid 全局
  计数,用户已有上千进程时沙箱内任何 fork 都失败,连 bash 都起不来
- 模型所属 provider 被停用时,原先静默换一家继续用原模型名,必然
  404 且报错指不到原因;改为直接说清是哪个 provider 被停用了

写 SBPL 的坑记在注释里:(deny default) 会让 Python 直接 SIGABRT,
改用「默认允许 + 精确拒绝」。

7 个模板全部回归通过。
2026-09-13 18:04:36 +08:00

88 lines
2.4 KiB
Python

from __future__ import annotations
from functools import lru_cache
from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict
BACKEND_DIR = Path(__file__).resolve().parents[2]
PROJECT_DIR = BACKEND_DIR.parent
class Settings(BaseSettings):
"""全局配置。所有项都可以用 AGENTLAB_ 前缀的环境变量覆盖。"""
model_config = SettingsConfigDict(
env_prefix="AGENTLAB_",
env_file=(PROJECT_DIR / ".env", BACKEND_DIR / ".env"),
extra="ignore",
)
# --- 服务 ---
host: str = "127.0.0.1"
port: int = 8000
cors_origins: list[str] = ["http://localhost:5273", "http://127.0.0.1:5273"]
# --- 存储 ---
data_dir: Path = PROJECT_DIR / "data"
# --- 密钥加密 ---
# 用于加密落库的 provider api key。留空则自动生成并存到 data_dir/.secret_key。
secret_key: str = ""
# --- 沙箱 ---
sandbox_backend: str = "auto" # auto | seatbelt | bubblewrap | local | off
sandbox_timeout: int = 30
sandbox_memory_mb: int = 512
sandbox_cpus: float = 1.0
sandbox_network: bool = False
sandbox_max_output: int = 20_000
# --- 执行引擎护栏 ---
max_run_seconds: int = 600
max_graph_steps: int = 200
max_agent_steps: int = 25
max_concurrent_runs: int = 20
# --- 工具 ---
http_tool_timeout: int = 20
http_tool_max_bytes: int = 2_000_000
# 逗号分隔的域名白名单;为空表示不限制(仅拦截内网地址)
http_tool_allowlist: list[str] = []
@property
def db_path(self) -> Path:
return self.data_dir / "agentlab.db"
@property
def db_url(self) -> str:
return f"sqlite+aiosqlite:///{self.db_path}"
@property
def checkpoint_path(self) -> Path:
"""LangGraph checkpointer 用独立的库文件,避免和业务表抢写锁。"""
return self.data_dir / "checkpoints.db"
@property
def workspace_dir(self) -> Path:
"""沙箱与文件工具的可写根目录,一切文件访问都被限制在这里面。"""
return self.data_dir / "workspace"
@property
def uploads_dir(self) -> Path:
return self.data_dir / "uploads"
def ensure_dirs(self) -> None:
for d in (self.data_dir, self.workspace_dir, self.uploads_dir):
d.mkdir(parents=True, exist_ok=True)
@lru_cache
def get_settings() -> Settings:
s = Settings()
s.ensure_dirs()
return s
settings = get_settings()