NVIDIA 于 2026 年 9 月 28 日发布 OpenShell 0.1.0:默认拒绝智能体全部出站流量,用 openshell policy update 加一条 protocol: rest 的只读规则即可让它读 API 却写不了,前置部署 Forward Deployed Engineer(FDE)可照搬 4 条命令
NVIDIA 于 2026 年 9 月 28 日发布 OpenShell 0.1.0:默认拒绝智能体全部出站流量,加一条 YAML 规则即可只放行只读 API。
这条能力正落在智能体交付的运行时边界这一段。前置部署 Forward Deployed Engineer(FDE)把智能体运行时 agent runtime 装进客户环境之后,最难向客户安全团队交代的就是它到底能碰哪些系统与数据:提示词与系统提示这些护栏都活在智能体内部,被提示注入或自身演化绕过就失效。
前置部署 Forward Deployed Engineer(FDE)在真实项目里需要的不是一份更长的提示词,而是一层智能体自己改不动、要改就得整体重建沙箱的强制边界,并且这层边界要能用 YAML 交付、用命令行回滚。
前置部署 FDE 在隔离环境 air-gapped 交付、多租户 multi-tenant 平台交付与合规审查三个场景都会撞上同一个问题,本文只写 0.1.0 里可直接复制的命令、字段默认值与升级动作。
一、先看清控制点被放到了哪里
NVIDIA 给出的核心设计判断是进程外策略执行 out-of-process policy enforcement:不靠行为提示词约束智能体,而是约束它运行环境的边界,智能体即使被攻破也无法覆盖这些约束。
把这套边界写成可评审的文件,本质上就是策略即代码 policy as code:策略是一份 YAML,改动走命令行与版本管理,而不是散落在提示词和运行手册里。
官方把 OpenShell 定位成更大一层里的运行时部分,同系列还有 NemoClaw 蓝图,它指出一个更实际的攻击面:任何被智能体装进来的第三方技能都是一个没人审过的二进制,而它有文件系统权限,跑在提示词护栏之外。
三个组件分工固定。OpenShell Gateway 管理成批沙箱的生命周期与策略;OpenShell Supervisor 与每个沙箱成对,运行在智能体工作负载之外,逐个检查出站请求;OpenShell Sandbox 用操作系统内核控制限制文件读写与进程能力,除通往 supervisor 的私有通道外没有任何网络路径。
四个强制执行点与「运行时能不能改」是这套设计里最该记住的一张表。网络由 CONNECT 代理加 OPA 策略引擎执行,可以用 openshell policy update 或 openshell policy set 在运行时改;文件系统由内核里的 Landlock LSM 执行,进程由 Seccomp BPF 加降权 setuid/setgid 执行,两者都在沙箱启动时锁定,要改只能销毁并重建沙箱;凭据由网络策略加 provider 端点绑定加代理替换执行,可以运行时调整。
静态控制在沙箱创建时锁定,动态控制可以在运行中的沙箱上更新,这个区分决定了故障处理的顺序:发现少了文件系统权限就重建沙箱,发现少了网络端点就直接改策略,不要为了网络问题去重建。
二、五步跑通:默认拒绝、看日志、放行、复测写请求、删除沙箱
第一步建沙箱,官方教程用的创建命令是 openshell sandbox create --name demo --from registry.example.com/team/agent-tools:1.0 --no-auto-providers。--no-auto-providers 的作用是跳过 provider 配置提问,因为下面只用 curl 做冒烟测试,不接真实模型服务。
第二步在沙箱里试探出站。执行 curl -s https://api.github.com/zen,预期失败,报错文本是 curl: (56) Received HTTP code 403 from proxy after CONNECT。原因是沙箱代理拦下了发往 api.github.com:443 的 HTTPS CONNECT 请求,因为没有任何网络策略授权 /usr/bin/curl 访问该主机。
第三步在宿主机的第二个终端查拒绝日志,命令是 openshell logs demo --since 5m --source sandbox。日志行形如 NET:OPEN [MED] DENIED /usr/bin/curl(64) -> api.github.com:443 [policy:- engine:opa] [reason:network connections not allowed by policy],里面同时给出目的地址、发起请求的可执行文件与拒绝原因。
第四步放行读权限,命令是 openshell policy update demo --rule-name github_api --binary /usr/bin/curl --add-endpoint api.github.com:443:read-only:rest:enforce --wait。端点写法是「主机:端口:访问预设:协议:执行模式」,其中 rest 让代理终止 TLS 并逐请求检查,read-only 只放行 GET、HEAD 与 OPTIONS,enforce 拦截其余方法。加 --wait 会等到沙箱回报新策略版本的结果。
第五步复测两种方法。同一个 GET 请求返回正常内容;改用 curl -X POST 打 https://api.github.com/repos/octocat/hello-world/issues 时,代理返回 403,响应体里带 "error":"policy_denied"、"policy":"github_api" 与 "rule":"POST /repos/octocat/hello-world/issues"。
请求级拒绝与连接级拒绝分两条日志。L7 拒绝行形如 HTTP:POST [MED] DENIED POST http://api.github.com:443/repos/octocat/hello-world/issues [policy:github_api engine:l7]。策略事件的日志级别固定为 INFO,用 --level warn 过滤会把它们全部滤掉。
收尾用 openshell sandbox delete demo。删除会停掉全部进程并清除注入的凭据,沙箱按设计就是可丢弃的,本地重复验证不要复用同一个沙箱。
三、一条网络规则的五个字段决定了它能干什么
上面那条命令等价于策略 YAML 里 network_policies 段落下的一个条目:主机 api.github.com、端口 443、protocol 为 rest、enforcement 为 enforce、access 为 read-only,binaries 列表里只有一个 - path: /usr/bin/curl。
第一个字段是 binaries,它必填且是白名单:只有列在里面的可执行文件才能到达该端点,路径支持 * 匹配单层与 ** 递归匹配。授权绑定的是可执行文件的身份,OpenShell 把每个有授权能力的路径钉在首次运行时观测到的 SHA256 摘要上(trust on first use),后续摘要不符立即拒绝,缺失或冲突的完整性证据也一律 fail closed,命令行里的路径只作诊断信息、不参与授权。
第二个字段是 protocol,不写它就等于放弃请求级检查:代理仍然核对主机、端口与二进制,也仍然终止 TLS 并解析 HTTP,但任何方法、任何路径都放行,非 HTTP 流量直接转发。要逐请求检查必须写明 rest、websocket 或 graphql。
第三个字段是 access,预设值为 full、read-only 与 read-write。read-only 只放行 GET、HEAD 与 OPTIONS;写接口要用 read-write 或显式 rules 逐条列方法与路径。
第四个字段是 enforcement,默认值是 audit,也就是只记录违规、仍然转发流量;要真正拦住必须显式写 enforce,被拦的请求会拿到带 JSON 说明的 403 Forbidden 响应。
第五个字段是 tls,默认行为是自动探测并终止加密,代理用每个沙箱自己的临时 CA 并注入到 NODE_EXTRA_CA_CERTS、DENO_CERT、SSL_CERT_FILE、REQUESTS_CA_BUNDLE、CURL_CA_BUNDLE 与 GIT_SSL_CAINFO 六个信任库,这样才能做凭据注入与 L7 检查。写 tls: skip 会同时关掉占位符凭据替换、动态令牌注入与 L7 检查,只在对方要求客户端证书 mTLS 或协议不是 HTTP 时才用。
带凭据的 REST 端点是唯一的例外入口:需要代理在受支持的文本请求体里替换凭据占位符时,要把 request_body_credential_rewrite 置为 true;确实无法走检查通道的加密流量,只能显式接受 allow_uninspected_credentials 这个例外。
出海方向还有一层 SSRF 保护:策略放行之后代理才做 DNS 解析,并拒绝未声明的内部目的地。回环地址 127.0.0.0/8、链路本地地址 169.254.0.0/16 与未指定地址 0.0.0.0 永久阻断,写 allowed_ips 也覆盖不了;要放行内网服务就给精确主机名,或给尽量窄的 CIDR,例如单机写 10.0.5.20/32。
沙箱的网络侧还有一条不能改的底座:它跑在独立的 Linux 网络命名空间里,配一对 veth,全部流量走宿主侧 veth 地址 10.200.0.1 上的代理,即使进程忽略代理环境变量也只能碰到代理。
四、官方列的七个常见错误
第一个错误是给 REST 或 WebSocket 端点漏写 protocol,结果是代理不施加任何请求规则,核对完主机、端口与二进制之后什么方法和路径都放行;正确做法是补 protocol: rest 或 protocol: websocket 并配具体 rules。前置部署 Forward Deployed Engineer(FDE)在现场最常犯的就是这一条,因为演示用的端点通常只有 GET,漏写 protocol 也看不出问题。
第二个错误是本能用 access: full。full 只带来可观测性,实际仍允许该协议下的全部方法与路径,应该改用 access: read-only 或显式 rules。
第三个错误是把端点永久写进策略 YAML。永久端点在所有沙箱实例上都可达,一次性排查应该走操作员审批:被批准的端点只在当前沙箱实例内跨重启存活,重建沙箱即失效。
第四个错误是二进制 glob 写得太宽。写成 /** 等于任何可执行文件都能授权访问该端点,应该收到具体目录,例如 /sandbox/.vscode-server/**。
第五个错误是对 HTTPS API 写 tls: skip,它会一起丢掉凭据重写、令牌注入与请求检查;除客户端证书 mTLS 场景外保持默认自动探测。
第六个错误是没跑 audit 就直接上 enforce,会在没摸清流量画像时打断智能体已有的工作流;正确顺序是先 audit 看日志、确认规则,再切 enforce。
第七个错误是把原始堆栈贴进缺陷报告。部分框架会把完整的请求配置(含凭据)塞进错误对象,而沙箱不擦除应用层输出,分享前必须自己脱敏。
五、升级到 0.1.0:官方明确不能原地升级
前置部署 Forward Deployed Engineer(FDE)在客户现场升级前要先把这份清单交出去,因为破坏面覆盖部署、沙箱、策略、接口与 SDK 五层,不是一次热更新。
0.0.x 的本地安装无法就地升级,官方给的路径是先清掉旧运行时、卸载旧包,再安装 0.1.0,同时要求升级前停掉全部 gateway 副本并备份数据库。
所有 0.0.x 沙箱必须在升级前删除、升级后重建,因为持久化的沙箱边界与运行时描述符不兼容 0.1.0。被破坏的接口不接受 0.0.x 与 0.1.0 混跑,相关改动见官方 PR #3113 与 PR #3272。
gateway.toml 要迁移到 schema version 2,写法是把版本号写进 [openshell] 段、把复数形式的 compute driver 选择器换成 compute_driver = "kubernetes"、把驱动设置移到 [openshell.drivers.<name>] 段下。迁移后用 openshell-gateway config preflight --path /etc/openshell/gateway.toml 校验通过再重启。
内置 provider 配置被移出网关。升级前先导出,命令是 openshell provider profile export <id> -o yaml --global > <id>.yaml,升级后用 openshell profile import --from ./profiles --global 导回。同时别名被替换:claude 要写成 claude-code,gh 要写成 github。
默认工作负载镜像换成 nvcr.io/nvidia/base/ubuntu:24.04,它不含任何智能体命令行工具与镜像内策略;无镜像策略时的兜底策略会拒绝网络访问、并且不再授予 /app 目录,镜像如果没有 OCI USER 声明的用户,就以 UID 1000 与 GID 1000 运行。
创建命令的行为也变了。openshell sandbox create --from 不再构建 Dockerfile 或目录,必须先 docker build -t registry.example.com/team/agent:0.1.0 . 再 docker push,最后把镜像引用传给 --from;远程网关还要保证它能从镜像仓库拉取。
策略模式变严:拼错、过期与未知字段现在直接报错,不再静默忽略;NetworkBinary.harness 被移除,二进制要写成只含 path 的对象,例如 - path: /usr/bin/curl;tls: terminate 与 tls: passthrough 被移除,官方明确 tls: skip 不是替代品,需要自动检查时直接省略 tls 字段。
追加 L7 规则必须指明目标:命令行要带 --rule-name 与每个 --binary,或带 --any-binary;直接调 API 要发完整的 L7RuleTarget。
接 MCP 的客户端要注意版本协商:每个初始化之后的请求都要带一个 MCP-Protocol-Version 头,并且端点策略必须允许该版本,否则请求会被策略拦下。
带自定义扩展的团队只有一条可行路径:自定义的算力驱动、凭据驱动、网关拦截器与中间件必须交换 PeerMetadata、把协议版本统一到 1.0、并声明自己的族基础能力,两端必须同时升级;沿用 0.0.x 生成的绑定与 0.1.0 对端通信会直接失败。
托管推理路由被整体移除,openshell inference 命令、路由接口与 inference.local 端点都不再用;替代做法是给每个沙箱挂一个 provider,然后调它的原生端点、用原生模型名与请求格式。
回滚要说清:官方没有给 0.1.0 退回 0.0.x 的路径,能回退的只有策略层。升级前必须做的两件事是导出全部 provider profile 与备份网关数据库;如果升上去发现不通,实际动作是重建一个新网关跑 0.1.0,而不是把旧库降级。
六、硬性前置条件:内核版本号本身不构成支持
前置部署 FDE 在私有化与离线场景里第一步要交的就是这张清单,因为缺一项内核能力时沙箱是直接启动失败,而不是给出可用的降级版本。
沙箱边界依赖 Linux 内核能力,缺一项就启动失败,而不是降级运行。文件系统侧要求 Landlock LSM 的 ABI 3 及以上,该 ABI 由 Linux 6.2 引入,并且内核必须启用 Landlock,文件系统策略里的 best_effort 设置也关不掉这条强制基线。
进程侧要求 seccomp 支持嵌套的 user-notification 过滤器,以及带 SECCOMP_ADDFD_FLAG_SEND 的原子 SECCOMP_IOCTL_NOTIF_ADDFD;沙箱在接纳工作负载之前会主动探测这些操作。
Linux 5.19 以下的节点会退化运行。SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV 从 Linux 5.19 起才有,典型受影响的是内核为 5.14 的 RHEL 9.x 与 RHCOS:这类节点上沙箱仍会启动并执行完整的隔离边界,但进入 legacy read-only 取消模式,代理拒绝把结果写回工作负载内存,getpeername、带非空对端地址参数的 accept 或 accept4、以及部分 sendmmsg 路径会返回 EOPNOTSUPP。socket 创建、connect、bind、listen、sendto 与 sendmsg 不受影响,所以出站型工作负载通常照常跑,而带 accept 包装、要读取对端地址的服务型工作负载会失败。
当前模式可以从沙箱的资格检查输出里读出来,字段名是 seccomp_listener_mode,取值为 killable 或 legacy_read_only,交付前先读这个字段,不要只看 uname 的内核版本号。
软件版本门槛:Docker Desktop 或 Docker Engine 需要 28.0,Podman 需要 5.x,Kubernetes 需要 1.29,Helm 需要 3.x。独立的 openshell-gateway 二进制在 Linux 上要求 glibc 2.28 及以上,官方点名兼容的有 Ubuntu 20.04+、RHEL 8+、Rocky Linux 8+、Amazon Linux 2023+ 与 Fedora 32+;Linux 上的 openshell 命令行工具是静态 musl 二进制,不依赖 glibc。
用户命名空间隔离默认是关闭的。要开得设 Helm 值 server.enableUserNamespaces: true,或在网关配置的 [openshell.drivers.kubernetes] 段里写 enable_user_namespaces = true;也可以按沙箱用 API 里 SandboxTemplate 的 user_namespaces 字段覆盖。这会把容器 UID 0 映射成宿主机上的非特权 UID,让 CAP_SYS_ADMIN 之类的能力只作用于容器内部。前置条件是 Kubernetes 1.33+ 且该集群支持用户命名空间(1.35 为 beta、1.36 起 GA)、容器运行时用 containerd 2.0+ 或 CRI-O 1.25+、内核 5.12+ 支持 ID-mapped mounts。官方同时写明:NVIDIA 设备插件与用户命名空间的兼容性尚未验证,同一沙箱同时开 GPU 与用户命名空间时 OpenShell 会打警告,GPU 集群请先单独测试。
沙箱镜像方面,OpenShell 接受标准 Linux OCI 镜像,默认工作负载镜像是 nvcr.io/nvidia/base/ubuntu:24.04,网关镜像是 ghcr.io/nvidia/openshell/gateway:latest;运行时有四种:Docker 用于本地开发与单机网关、Podman 用于无根工作流、Kubernetes 走官方 Helm chart、MicroVM 走基于 libkrun 的 VM 驱动,隔离环境 air-gapped 交付要预取的就是这几个镜像加独立的 openshell-sandbox 与 openshell-prover 归档。
七、把每一次放行与拒绝导成 SIEM 可吃的 JSON
前置部署 Forward Deployed Engineer(FDE)交付受监管客户时,这一节往往比策略本身更重要:客户要的不是一张权限清单,而是可检索的逐请求留痕。
实时看日志用速记格式,要送合规归档或 SIEM 就要打开完整 OCSF JSON 导出。开关是 ocsf_json_enabled,全局开用 openshell settings set --global --key ocsf_json_enabled --value true,按沙箱开就把 --global 换成沙箱名。
这个开关在下一个轮询周期生效,默认轮询间隔是 10 秒,不需要重启沙箱。记录写在沙箱内的 /var/log/openshell-ocsf.YYYY-MM-DD.log,每行一个完整的 OCSF v1.8.0 JSON 对象,网络连接事件的 class_uid 是 4001。两类 sink 都按天轮转、只保留最近 3 个文件,所以长期归档必须在沙箱外自己搬走。
解析记录时有一个容易踩错的点:event 的唯一标识在 metadata.uid,它与沙箱的关联字段是 container.uid,用 metadata.uid 去关联沙箱会错;没有沙箱归属的记录直接省略 container 段。
SSH 会话另有一套默认值。会话令牌 24 小时后过期,同一个令牌最多 10 个并发连接,单个沙箱最多 20 个并发连接;沙箱不在网络上暴露 SSH 端口,sshd 只监听沙箱内 supervisor 能碰到的 Unix socket。要调令牌有效期改 ssh_session_ttl_secs,设成 0 表示永不过期,但这会放大令牌被盗后的复用窗口。
八、谁在采用,以及形式化校验扛住了什么
前置部署 FDE 评估一个运行时能不能进客户环境时,采用方名单的说服力通常高于功能清单,因为同规模团队已经在跑,运维代价已经被别人付过。
NVIDIA 点名的采用方有三个:Cadence 把它用在芯片设计的 ChipStack Autonomous RTL Design Engineer,Slack 在它上面搭按需的智能体平台,Gecko Robotics 用来治理在实体机器人上做决策的智能体。
形式化策略分析在长时程对抗实验里给出过量化观察:被降低护栏的前沿智能体最多花 2 小时试图说服 AI 审查者批准能修改受保护 GitHub 仓库的权限,策略证明器给出的权限范围证据让审查者不被说辞带偏,官方结论是这批测试中没有发生受保护仓库的写入。
策略证明器也可以单独用在流水线里:开源仓库附带独立的 openshell-prover 命令,用于在 CI 里拿一份策略去比对事先定义好的权限边界,把策略评审前移到合并之前。
节奏上,稳定版大约每周发一次,目标是周二,前提是全部阻断性资格检查通过,安全版本会提前发;OpenShell 对最新次版本与上一个次版本提供安全与关键可靠性更新,也就是支持 N-1,且只维护每条次版本线的最新补丁版。
仓库侧的可核实口径:NVIDIA/OpenShell 在 2026 年 9 月 29 日抓取时显示 9017 个 star、1549 次提交,采用 Apache-2.0 许可,许可证文件与 Security Best Practices 文档都在仓库里公开。
九、该谁动手、怎么做、怎么退
该动手的是这样一类角色。前置部署 Forward Deployed Engineer(FDE)或平台团队要把第三方或自研智能体放进客户环境,而客户安全团队会追问「它到底能碰什么」;或者法规与合规要求每一次出站请求都要留痕,这两种情况今天就可以按本文第二、三节的动作做一次最小验证。
具体动作按顺序是四条命令:openshell sandbox create 起一个演示沙箱并确认默认拒绝,openshell policy update 加一条 read-only 的 rest 规则并用 --wait 等结果,openshell logs 分别核对连接级与请求级两条拒绝日志,openshell settings set 打开 ocsf_json_enabled 把轨迹导出来,最后 openshell sandbox delete 清理。
适用条件要写清:宿主机是 Linux 6.2 以上内核,或者接受 Linux 5.19 以下节点的 legacy read-only 退化模式;运行时是 Docker 28.0 以上或 Kubernetes 1.29 以上的集群;要开用户命名空间隔离还得额外满足 Kubernetes 1.33+ 与 containerd 2.0+。内核低于 6.2 且短期不能升级的团队,应该先解决内核,而不是先铺策略。
不该行动的边界也要说清:只在单机跑一次性脚本、工作负载既不碰凭据也不碰客户数据、或者只需要事后审计而不需要事前拦截的场景,加这一层只换来运维负担。
退出方式分三层。网络规则可以整体替换或增量改,用 openshell policy set 做整份替换、openshell policy update 做增量修改,改错了再改一次即可,不影响正在跑的沙箱;文件系统与进程限制在沙箱启动后不可改,只能销毁重建;从 0.0.x 升到 0.1.0 之前必须先导出 provider profile 并备份网关数据库,因为官方不提供版本回退路径。
局限也要写明。原文的对抗实验只给「没有发生受保护仓库写入」这种定性结论,没有公开样本量、通过率与成本,因此不能当作安全强度的量化指标。官方承认多智能体场景的权限合并分析仍在进行中,也就是一个智能体的访问权与另一个叠加之后整个系统获得什么权限,目前还没有完整检查;策略顾问提出的端点修改默认需要人工批准,智能体不能批准自己的请求。此外 0.1.0 是 0.1 系列的首个版本,官方说明 Experimental 标记的接口可能在补丁版本里变更或被移除,生产部署要跟着每周的补丁节奏走,不要停在旧补丁上。
本文所有版本号、字段默认值、命令与错误文本均取自 NVIDIA 官方技术博客与 NVIDIA OpenShell 官方文档,未在本机复现,内核与运行时版本请按自己环境的实际输出再次核对。
原始来源:
- https://developer.nvidia.com/blog/add-runtime-controls-to-ai-agents-with-nvidia-openshell/
- https://docs.nvidia.com/openshell/dev/about/run-your-first-agent
- https://docs.nvidia.com/openshell/tutorials/first-network-policy
- https://docs.nvidia.com/openshell/security/best-practices
- https://docs.nvidia.com/openshell/upgrade/0-1-0
- https://docs.nvidia.com/openshell/about/support-matrix
- https://docs.nvidia.com/openshell/observability/ocsf-json-export
- https://github.com/NVIDIA/OpenShell
- https://developer.nvidia.com/blog/run-autonomous-self-evolving-agents-more-safely-with-nvidia-openshell/
评论(0)
- 暂无评论。