news 2026/9/19 0:25:04

NonoSandboxProvider 设计解读:用 Landlock 与 Seatbelt 为 AI Agent 构建内核级能力沙箱

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NonoSandboxProvider 设计解读:用 Landlock 与 Seatbelt 为 AI Agent 构建内核级能力沙箱

NonoSandboxProvider 设计解读:用 Landlock 与 Seatbelt 为 AI Agent 构建内核级能力沙箱

【免费下载链接】agent-governance-toolkitAI Agent Governance Toolkit — Policy enforcement, zero-trust identity, execution sandboxing, and reliability engineering for autonomous AI agents. Covers 10/10 OWASP Agentic Top 10.项目地址: https://gitcode.com/GitHub_Trending/ag/agent-governance-toolkit

本篇技术指南以 NONO-SANDBOX-PROVIDER 设计提案 为核心骨架,结合 Agent Governance Toolkit 仓库中agent-sandbox的源码、配置与测试,完整剖析NonoSandboxProvider的设计理念与实现细节。读完你将掌握:nono 能力沙箱的一次性执行模型如何满足会话式SandboxProvider契约、NonoConfig.from_sandbox_config如何映射超时/挂载/环境/网络控制、fail-closed 出口网络策略的强制逻辑,以及运行时治理与静态代码扫描如何在sandboxed_exec之前完成拦截。

NonoSandboxProvider是 Agent Governance Toolkit 中agent-sandbox模块(agent-governance-python/agent-sandbox)提供的五种可互换沙箱后端之一。它不依赖 daemon、hypervisor SDK、云账号或外部二进制,而是直接驱动nono-py——一个基于 OS 原生内核原语(Linux 的Landlock、macOS 的Seatbelt)实现的能力沙箱库。本文将从设计提案出发,逐层拆解其配置契约、能力集构建、网络代理、治理拦截与执行生命周期,并用仓库内的单元测试与集成测试验证每个关键行为。


一、设计概览:能力沙箱与一次性执行

1.1 内核原语与 nono-py 绑定

提案开篇即定义了NonoSandboxProvider的技术路线:通过nono-py使用 Landlock(Linux)和 Seatbelt(macOS)实施沙箱,每次执行都运行在一个全新的子进程中,并携带一份显式声明的能力集(capability set)。

从源码看,nono-py暴露的底层原语被明确记录在 config.py 的模块 docstring 中:

  • nono_py.CapabilitySet:文件系统授权(读写模式)+ 网络模式;
  • nono_py.start_proxy:过滤型网络代理;
  • nono_py.sandboxed_exec:一次性原语——fork→ 在子进程内套用沙箱 →exec命令 → 在未沙箱化的父进程中捕获 stdio。

值得注意的是,该提供程序对nono_py采用懒加载策略:provider.py 在模块加载时用try/except ImportError_nono_module置为None,因此导入NonoSandboxProvider本身不需要nono-py已安装;只有构造实例并查询is_available()时才会真正探测依赖与宿主支持情况。这一点在__init__.py的 docstring 与测试test_missing_nono_py_does_not_break_import中都有印证。

1.2 会话模型:会话即“持久化 Bundle”

nono 的sandboxed_exec是**一次性(one-shot)**的:fork、套沙箱、运行命令、子进程退出——没有 Docker 容器或 Hyperlight 微 VM 那种跨调用存活的常驻 guest。

为了满足SandboxProvider抽象基类(sandbox_provider.py)的会话式契约,实现将会话视为一个可持久化的 bundle(见 provider.py):

  • 解析后的NonoConfig
  • 策略评估器(policy evaluator,即可选的AgentControl原生治理会话);
  • 每会话独立的工作目录 workspace;
  • 可选的常驻网络代理 proxy。

每次execute_code()/run()都基于该 bundle fork 一个全新的一次性沙箱。因此跨执行不保留 guest 状态,唯一例外是会话的读写output/目录——它在宿主机上于多次调用之间保留;代理在会话间共享,并在destroy_session时关闭。

_Session类(provider.py)正是这一 bundle 的数据结构,用__slots__声明了configevaluatorworkspaceinterpreterproxy五个字段。


二、配置契约:NonoConfig 与 SandboxConfig 的映射

2.1 NonoConfig 字段一览

NonoConfig是一个类型化、零依赖的数据类(config.py),建模了 provider 关注的 nono 能力面。字段及其语义如下表:

字段类型默认值语义
readonly_pathslist[str][]授予沙箱只读的宿主目录,映射caps.allow_path(p, READ)
readwrite_pathslist[str][]授予沙箱读写的宿主目录,映射caps.allow_path(p, READ_WRITE)
allow_outboundboolFalse是否允许出站网络;True时启动 nono 过滤代理并绑定沙箱,False时调用block_network()
allowed_hostslist[str][]由代理强制执行的出站主机白名单,仅在allow_outbound=True时有意义
allow_unrestricted_egressboolFalse显式选择无限制出站(allow_outbound=Trueallowed_hosts为空),映射代理allow_all_hosts=True
timeout_secondsfloat60.0传给sandboxed_exectimeout_secs墙钟执行预算
env_varsdict[str, str]{}暴露给沙箱进程的环境变量,使用前经sanitize_env_vars清洗
include_system_pathsboolTrue是否授予标准系统目录读权限(见default_system_paths),使解释器可加载

__post_init__强制两条校验(config.py):timeout_seconds必须为正数;_check_egress()强制执行下文所述的 fail-closed 出口契约。对应测试test_nonpositive_timeout_rejectedtest_outbound_without_hosts_rejected验证了这两个拒绝分支。

2.2 from_sandbox_config 的映射规则

提案指出:NonoConfig.from_sandbox_config映射 timeout、mounts、environment 和 network controls”。对照实现(config.py),映射逻辑为:

  1. 挂载(mounts)SandboxConfig.input_dirreadonly_paths(只读),SandboxConfig.output_dirreadwrite_paths(读写);两者在挂载前都会经validate_mount_path校验,拒绝指向受保护系统目录(如/usr)。
  2. 网络(network)network_enabled=Truenetwork_allowlist非空 →allow_outbound=True+allowed_hostsnetwork_enabled=True但 allowlist 为空且network_default='allow'allow_unrestricted_egress=True;其余情况保持allow_outbound=False
  3. 超时(timeout)timeout_seconds直接透传,并取max(0.001, ...)兜底。
  4. 环境(environment)env_vars原样带入,后续由sanitized_env()清洗。

测试test_from_sandbox_configtest_mounts_and_egresstest_network_default_allow_enables_unrestrictedtest_network_allowlist_takes_precedence_over_default等(test_nono_sandbox.py)逐一覆盖了这些映射与优先级规则。

2.3 两个“明确拒绝”:tool_allowlist 与资源限制

提案强调了两条关键的 fail-closed 边界:

  • Nono 没有工具注册通道,非空tool_allowlist直接拒绝。在 provider.py 的create_session中,若base_cfg.tool_allowlist非空,立即抛出ValueError("nono does not support tool allowlisting ..."),而不是静默忽略——这与 MXC provider 的行为一致,但与支持 in-sandbox 工具注册的 Hyperlight 形成对比(见 README.md 的“Host configuration and native governance”表格)。空列表则放行(test_empty_tool_allowlist_is_allowed)。
  • CPU 与内存控制由操作系统负责SandboxConfig中的memory_mbcpu_limit在 nono 中不可表达,from_sandbox_config直接丢弃,交由 OS 治理(docstring 明确注明)。SandboxConfig的默认值本身为memory_mb=512cpu_limit=1.0(sandbox_provider.py),但在 nono 路径下不产生任何实际约束。

三、出口网络控制:默认阻断、白名单代理、显式全开

网络是 Agent 沙箱最敏感的边界之一。提案的核心承诺是:网络访问默认被阻断过滤出口通过带allowed_hosts的代理实现;无限制出口必须显式 default-allow。

3.1 三态网络模型

NonoConfig._check_egress()(config.py)将出口策略固化为三种状态:

状态条件代理配置能力绑定
完全阻断allow_outbound=False不启动代理caps.block_network()
过滤出口allow_outbound=Trueallowed_hosts非空ProxyConfig(allowed_hosts=[...])caps.proxy_only(proxy)
无限制出口allow_outbound=True+allow_unrestricted_egress=True(显式)ProxyConfig(allow_all_hosts=True)caps.proxy_only(proxy)

其中“过滤出口”是 fail-closed 的核心:allow_outbound=True时,若allowed_hosts为空且未显式设置allow_unrestricted_egress,配置构造直接抛ValueError——绝不允许隐式地“可达任意主机”。错误消息甚至给出了通过策略defaults.network_default: allow显式开启的途径。

3.2 代理的生命周期

代理的启动与关闭逻辑在 provider 中成对出现:

  • 启动_start_proxy()(provider.py)根据 egress 策略构造ProxyConfigcreate_session中若allow_outbound为真则启动代理,失败时清理 workspace 并抛RuntimeError
  • 绑定_build_caps()中,有代理时调用caps.proxy_only(session.proxy),否则caps.block_network()(provider.py)。
  • 环境注入_build_env()将代理的sandbox_env(含HTTP_PROXY等)合并进子进程环境,使流量经代理路由(provider.py)。
  • 关闭destroy_session()在移除 workspace之前先关闭代理,避免后台线程存活超出会话(provider.py)。

测试test_network_blocked_by_defaulttest_proxy_started_for_egresstest_unrestricted_egress_uses_allow_all_hoststest_proxy_shut_down_on_destroy完整验证了这一生命周期,包括代理环境下HTTP_PROXY被写入沙箱环境。


四、原生治理:deny 必须先于 sandboxed_exec

提案“Native governance”一节指出:可选的AgentControl被包装在HostSession中,运行时(runtime)与静态扫描(static-scan)的拒绝都发生在sandboxed_exec之前。这意味着策略拒绝根本不会触达 nono 沙箱。

4.1 HostSession 包装

_build_runtime_session()(provider.py)从agent_control_specification导入HostSession,将调用方传入的runtime(ACS 原生治理运行时)与agent_idsession_id一并包装。create_session中仅在runtime is not None时才构建 evaluator。

4.2 执行前的两道闸门

execute_code()(provider.py)在 fork 任何沙箱之前依次执行:

  1. 治理门(governance gate):构造{"agent_id", "action": "execute", "code", ...}上下文,调用evaluator.pre_tool_call(tool_name="sandbox_execute", ...)。若决策为 transform 判决(applies_transform),直接抛PermissionError——因为沙箱无法改写即将执行的代码,视为拒绝而非放行;若permits=False,同样抛PermissionError(携带decision.messagereason)。测试test_policy_deny_blocks_execution用一个返回 deny 判决的假 runtime 验证了该分支。
  2. 静态扫描门(static-scan gate):调用enforce_no_subprocess_execution(code)(来自 code_scanner.py)。这是一个 Python AST 扫描器,其_DANGEROUS_CALLS表覆盖subprocessPopen/run/call等)、ossystem/exec*/spawn*/popen等)、pty.spawnshutil.which;命中即抛SandboxCodeViolationPermissionError子类)。测试test_code_scanner_blocks_subprocesstest_run_once_destroys_session_on_guard_violation验证了拦截与清理。

4.3 非 Python 解释器的 fail-closed

由于扫描器只理解 Python,execute_code会先校验解释器是否为 Python(_is_python_interpreter匹配py|python|pypy系列,provider.py):若配置了node等非 Python 解释器,execute_code直接抛ValueError并引导调用方改用run()显式命令——宁可拒绝,也不执行未扫描的代码test_non_python_interpreter_fails_closed;绝对路径如/usr/bin/python3则放行,test_absolute_python_interpreter_path_allowed)。


五、能力集构建与执行生命周期

5.1 CapabilitySet 的组装

_build_caps()(provider.py)按以下顺序组装CapabilitySet

  1. include_system_paths=True时,对default_system_paths()的每个路径授予READ,缺失路径以FileNotFoundError静默跳过;
  2. 额外只读目录(如解析出的解释器所在目录)授予READ
  3. cfg.readonly_pathsREADcfg.readwrite_pathsREAD_WRITE
  4. 网络按第三节的三态模型绑定代理或阻断。

其中default_system_paths()(config.py)返回 Unix 根目录集(/usr/bin/sbin/lib/lib64/opt)+ macOS 专属(/private/Library/Frameworks/dev/System)+ 当前解释器的 prefix(sys.prefixsys.base_prefix),且只返回宿主上真实存在的路径,同一列表在 Linux 与 macOS 上均安全。

5.2 命令解析与环境隔离

_resolve_command()(provider.py)在未沙箱化的父进程中将裸程序名解析为绝对路径(shutil.which在宿主PATH上查找;macOS 上常见的裸python回退到sys.executable),因为沙箱内inherit_env=False、没有PATH可解析;解析出的程序目录随后被授予只读访问。

_build_env()(provider.py)组装子进程环境:config.sanitized_env()(经sanitize_env_vars剥离LD_PRELOADPYTHONSTARTUPNODE_OPTIONS等启动钩子,见 _hardening.py)加上可选的NONO_CONTEXTJSON 上下文(不可序列化时告警丢弃),有代理时再并入代理环境。

5.3 _spawn 与结果处理

_spawn()(provider.py)是执行中枢:

  • 以会话output/目录为cwd(相对写落盘到持久 workspace);
  • timeout_secs=timeoutinherit_env=False调用nono.sandboxed_exec(caps, command, ...)
  • 超时判定基于 nono 约定:退出码 124表示因超时被杀死(_TIMEOUT_EXIT_CODE,provider.py),此时killed=Truekill_reason注明超时时长;
  • stdout/stderr 按流截断至 1 MiB(_OUTPUT_MAX_BYTES),超出部分追加[...output truncated at byte limit]标记;
  • 结果封装为SandboxResultsuccessexit_codestdoutstderrduration_secondskilledkill_reason)。

对应测试test_timeout_marks_killedtest_timeout_passed_throughtest_output_truncatedtest_sandboxed_exec_failure_surfacestest_cwd_is_output_dir覆盖了这些行为。

5.4 agent_id 校验

_validate_agent_id()(provider.py)要求agent_id匹配^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$,因为它会被插值进日志与 workspace 目录名——先拒绝敌意字符,杜绝路径穿越与控制字符注入(test_invalid_agent_id_rejected)。


六、安装、可用性与实战用法

6.1 安装与平台前提

agt-sandbox将 nono 作为可选依赖发布(pyproject.toml),nono-py>=0.10.1,<0.11,且注明仅 Linux/macOS,无 Windows wheel

pip install "agt-sandbox[nono]" # Linux/macOS only

可用性探测由_compute_unavailable_reason()完成(provider.py):nono-py未安装 → 提示安装命令;已安装但is_supported()为假 → 提示需要 Linux kernel 5.13+(Landlock)或 macOS。is_available()返回探测结果;在不可用宿主上调用create_session会抛RuntimeErrortest_unavailable_when_extension_missingtest_unavailable_when_unsupported_host)。

生产就绪性提示(来自 README.md):nono-py上游 PyPI 分级为Alpha。内核级强制(Landlock/Seatbelt)结构上强于进程内守卫,但项目仍在成熟中——建议先用于纵深防御、开发与 CI,投入生产硬边界前自行完成安全评审;Windows 或无 Landlock 的内核请改用DockerSandboxProvider等其他后端。

6.2 一次性用法:run_once

每次 nono 沙箱都是全新 fork、运行后即退出的子进程,因此最贴合的一次性用法是run_once(provider.py):内部创建会话 → 执行 → 销毁,无需跟踪 session_id,且失败路径同样保证清理(test_run_once_cleans_up_on_failure):

from agent_sandbox import NonoSandboxProvider, SandboxConfig provider = NonoSandboxProvider() if not provider.is_available(): raise SystemExit("nono not supported here (needs Linux+Landlock or macOS)") execution = provider.run_once( "agent-1", "print('hello from nono')", config=SandboxConfig(timeout_seconds=20, network_enabled=False), ) print(execution.result.stdout)

6.3 会话式用法:create_session + execute_code

需要跨调用共享持久output/目录(以及常驻网络代理)时,使用完整生命周期。create_session内部会创建scripts/(只读授予沙箱,存放待执行脚本)与output/(读写授予沙箱,即持久输出区)两个子目录(provider.py):

from agent_control_specification import AgentControl from agent_sandbox import NonoSandboxProvider, SandboxConfig provider = NonoSandboxProvider() runtime = AgentControl.from_path(str("manifest.yaml")) config = SandboxConfig( timeout_seconds=90, input_dir="/data/agent-input", output_dir="/data/agent-output", network_enabled=True, network_allowlist=["api.openai.com", "*.github.com"], ) handle = provider.create_session("agent-1", runtime=runtime, config=config) execution = provider.execute_code( "agent-1", handle.session_id, "print('hello from nono session')", ) print(execution.result.stdout) provider.destroy_session("agent-1", handle.session_id)

6.4 低层命令执行:run

run()(provider.py)直接以命令列表方式执行任意程序,不经代码扫描(适合非 Python 语言);有会话时复用该会话的配置/workspace/代理,否则构建临时一次性会话并在调用后清理。空命令返回失败结果(test_run_empty_command)。

6.5 集成测试入口

真实的端到端验证(真实 OS 沙箱)位于 test_nono_integration.py,默认跳过,需满足两个条件:nono-py已安装且is_supported()为真、环境变量AGT_NONO_INTEGRATION=1(macOS 上若检测到已处于沙箱内也会跳过,因为 Seatbelt 禁止嵌套沙箱):

pip install "agt-sandbox[nono]" export AGT_NONO_INTEGRATION=1 pytest agent-governance-python/agent-sandbox/tests/test_nono_integration.py -v

而 test_nono_sandbox.py 通过 monkeypatch 假nono_py模块保持测试环境的封闭性(无内核沙箱、无网络),可在任意宿主(含不支持 nono 的 Windows)运行,覆盖配置校验、fail-closed 出口契约、生命周期、能力绑定、超时/截断、各类守卫等全部行为。


七、设计要点的验证与小结

回到提案本身,其每一项设计声明都能在实现与测试中找到对应证据:

提案声明实现位置验证测试
Landlock/Seatbelt,经nono-pyprovider.py 模块 docstring、config.py docstringtest_available_when_supported
每次执行全新子进程 + 显式能力集_spawnnono.sandboxed_exec(caps, ...)test_session_paths_granted
from_sandbox_config映射 timeout/mounts/env/networkconfig.pytest_from_sandbox_configtest_mounts_and_egress
网络默认阻断NonoConfig.allow_outbound=False默认值 +_build_capsblock_network()test_network_blocked_by_default
过滤出口走allowed_hosts代理_start_proxy+caps.proxy_onlytest_proxy_started_for_egress
无限制出口需显式 default allow_check_egressfail-closedtest_outbound_unrestricted_requires_explicit_opt_in
非空tool_allowlist拒绝create_sessionValueErrortest_tool_allowlist_fails_closed
CPU/内存归 OS 管理from_sandbox_config丢弃memory_mb/cpu_limittest_from_sandbox_config(字段未透传)
AgentControl包装进HostSession,deny 先于sandboxed_exec_build_runtime_session+execute_code治理门test_policy_deny_blocks_execution
静态扫描先于sandboxed_execenforce_no_subprocess_execution(code)test_code_scanner_blocks_subprocess

NonoSandboxProvider的价值在于:以纯 Python 安装即可获得内核级强制的隔离能力,无 daemon、无 hypervisor、无云账号,同时把“显式优于隐式”的 fail-closed 原则贯彻到配置、网络、工具通道与执行前拦截的每一层。对 Agent Governance Toolkit 的落地场景而言,它是本地开发与 CI 环境中兼具部署轻量与结构性强度的默认选择之一;而对需要更强硬边界的生产环境,则可通过同一SandboxProvider抽象无缝切换到 Docker、Hyperlight、MXC 或 Azure Container Apps 后端。相关源码与测试位于 agent-governance-python/agent-sandbox,可继续深入阅读。

【免费下载链接】agent-governance-toolkitAI Agent Governance Toolkit — Policy enforcement, zero-trust identity, execution sandboxing, and reliability engineering for autonomous AI agents. Covers 10/10 OWASP Agentic Top 10.项目地址: https://gitcode.com/GitHub_Trending/ag/agent-governance-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 0:22:18

IntelliJ IDEA连接MySQL数据库:从Navicat协同到JDBC配置与报错排查

IntelliJ IDEA连接Navicat数据库&#xff0c;这句话我在不少开发群里见过&#xff0c;每次看到都会心一笑&#xff1a;说的人其实不是想让IDEA去连Navicat这个软件&#xff0c;而是想在开发过程中把IDEA和Navicat这对组合真正用起来。这里必须先点破一个底层事实——Navicat是数…

作者头像 李华
网站建设 2026/9/19 0:21:08

JavaWeb项目实战:基于Servlet+JSP+MySQL的校园论坛系统

简介&#xff1a;这份PDF文档是一份完整的基于Java Web的校园论坛系统设计与实现毕业设计资料&#xff0c;适合计算机相关专业学生、Java Web初学者以及需要参考SSH框架项目开发的工程师使用。资源仅有1个PDF文件&#xff0c;压缩包大小约3.73MB&#xff0c;文档排版规范、目录…

作者头像 李华
网站建设 2026/9/19 0:18:13

AR-NAR混合Transformer架构:YuE模型原理与Python实战

1. 项目概述&#xff1a;从“YuE”到AR–NAR MoT——一个被热搜掩盖的前沿生成模型架构最近在Hugging Face社区和Python技术圈里&#xff0c;“YuE”这个词频繁出现在各类讨论帖、模型下载页和代码仓库的README里&#xff0c;甚至衍生出“YuE2”这样的迭代代号。但如果你直接搜…

作者头像 李华
网站建设 2026/9/19 0:16:06

JMeter安装配置实战:JDK匹配、环境变量与命令行压测

先放结论&#xff1a;JMeter 这个开源性能测试工具&#xff0c;做接口压测、功能测试、分布式压测基本是测试开发岗位的标配技能了。这玩意儿是 Apache 基金会出的&#xff0c;用纯 Java 写的&#xff0c;所以跨平台做得很好&#xff0c;只要你有 JDK 环境&#xff0c;Windows、…

作者头像 李华
网站建设 2026/9/19 0:14:30

从Word试卷到题库:Python提取数学试题与结构化分析实践

简介&#xff1a;这套由吉林多所名校联合命制的2017年七年级下学期第一次月考数学卷&#xff0c;适合刚进入下学期的学生用于阶段自测&#xff0c;也适合教师用来评估教学进度、家长辅助家庭辅导。试卷依据课标和命题要求设计&#xff0c;包含选择、填空、解答等多种题型&#…

作者头像 李华