我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

OpenHands Software Agent SDK 无需身份验证的沙箱 RCE(CVE-2026-112346)

OpenHands Software Agent SDK 在所有网络接口上暴露沙箱 Agent 服务器端口,且默认关闭身份验证,攻击者因此可以以 root 身份执行命令;1.52.0 只修复了 Docker 启动器,未修复 Apptainer。

执行摘要(TL;DR)

  • OpenHands Software Agent SDK 在工作区内运行智能体。其智能体服务器是执行命令和文件操作的 HTTP 与 WebSocket 服务;工作区启动器 DockerWorkspace 或 ApptainerWorkspace 负责启动该服务。OpenHands Software Agent SDK 的 Docker 启动器在主机的所有网络接口上发布沙箱 Agent 服务器的端口,其 Apptainer 启动器则直接在主机上将服务器绑定到 0.0.0.0。会话 API 密钥列表为空时,身份验证默认处于关闭状态。任何能够访问该端口的主机都可以发送无需身份验证的 HTTP 或 WebSocket 请求,在沙箱内以 root 身份执行任意 shell 命令。这包括共享 Docker 网络上的容器、局域网中的机器,以及在主机暴露时来自互联网的客户端。
  • 1.52.0 之前的所有 openhands-sdk 版本均受影响。截至撰写本文时,该软件包在 PyPI 上最近一周的下载量为 604,050 次。
  • 分配并发布该 CVE 的 VulnCheck 将其评为严重级别,CVSS v3.1 评分 9.8,CVSS v4.0 评分 9.3,归类为 CWE-1327(绑定到不受限制的 IP 地址),并致谢 mil4n。目前尚无已知的利用行为,也没有公开的概念验证(PoC)。
  • 对于 Docker,请升级到 2026 年 10 月 5 日发布的 openhands-sdk 1.52.0 或更高版本,重新创建所有沙箱容器,并确认端口现在仅在 127.0.0.1 上发布。对于较旧的部署,请阻断对该端口的网络访问。
  • 1.52.0 对 Apptainer 没有任何改变,它仍然绑定 0.0.0.0:请设置 SESSION_API_KEY(不设置 OH_SESSION_API_KEYS_0,或将两者设为相同的值),并确保不受信任的网络无法访问该端口。在 1.52.0 之前的 Docker 上,设置密钥可以保护服务器,但会导致 SDK 会话失效;请升级以恢复经过身份验证的使用。

我是否受影响?

如果您通过低于 1.52.0 版本的 DockerWorkspace 启动器或通过 ApptainerWorkspace 启动器使用 openhands-sdk,未设置会话 API 密钥,并且沙箱端口可从您不完全信任的网络访问,您就会受到影响。安全公告的受影响范围止于 1.52.0 之前,但 Apptainer 启动器在 1.52.0 中仍然绑定 0.0.0.0。三个条件必须同时成立:

  • 您的代码或平台使用 OpenHands Software Agent SDK(软件包 openhands-sdk),并通过 1.52.0 之前版本的 DockerWorkspace 启动器或 1.52.0 未作修改的 ApptainerWorkspace 启动器驱动它。
  • 未配置会话 API 密钥,这也是默认情况:SDK 启动时所在的环境中既没有设置 SESSION_API_KEY,也没有设置 OH_SESSION_API_KEYS_0,因此服务器的 session_api_keys 列表为空,身份验证处于关闭状态。
  • 任何您不完全信任的人都能访问已发布的端口。修复前的启动器会在主机的所有网络接口上发布该端口,因此这包括共享 Docker 桥接网络上的其他容器、主机所在局域网中的任何设备,以及在主机拥有公网地址且没有防火墙拦截时的整个互联网。
软件包 受影响版本 修复版本
openhands-sdk(PyPI) 1.52.0 之前 1.52.0,发布于 2026 年 10 月 5 日
OpenHands Software Agent SDK DockerWorkspace 启动器 1.52.0 之前 1.52.0:在 127.0.0.1 上发布,其客户端会读取已配置的会话密钥
OpenHands Software Agent SDK ApptainerWorkspace 启动器 公告所列为 1.52.0 之前,但 1.52.0 同样受影响 1.52.0 仅更改了 Docker 的端口发布;在 v1.52.0 中 Apptainer 仍绑定 0.0.0.0。请配置会话密钥并隔离该端口(参见修复一节)。

检查您运行的版本以及端口的发布位置:

python -c "from importlib.metadata import version; print(version('openhands-sdk'))" 2>/dev/null || echo 'openhands-sdk not installed in this interpreter'
docker ps --format '{{.Names}}  {{.Ports}}' | grep -E 'agent-server|->8000/tcp'
# Run where the SDK starts; report presence without printing secret values.
python - <<'PYKEYS'
import os
for name in ("SESSION_API_KEY", "OH_SESSION_API_KEYS_0"):
    print(name + ": " + ("set" if os.environ.get(name) else "unset or empty"))
PYKEYS

在 docker ps 的输出中,修复前的沙箱会显示类似 0.0.0.0:31245->8000/tcp 的发布行,表示所有网络接口;而修复后的启动器显示的是 127.0.0.1:31245->8000/tcp。启动器的 Docker run 命令会将每个容器命名为 agent-server-<uuid>。

如何修复?

DockerWorkspace: 升级 SDK,然后重新创建所有沙箱容器,因为正在运行的容器在被重新创建之前会保留旧的端口映射:

python -m pip install --upgrade 'openhands-sdk>=1.52.0'

然后结束每个正在运行的沙箱会话并启动新的会话,让升级后的启动器使用新的端口映射创建新容器(单纯执行 docker restart 会保留旧映射),并使用 docker ps 确认新的发布地址。修复后的 Docker 启动器客户端会从 SDK 进程环境中读取 OH_SESSION_API_KEYS_0,未设置时回退到 SESSION_API_KEY。这与服务器的密钥选择方式一致,因此 SDK 的请求会携带已配置的密钥。

ApptainerWorkspace: 1.52.0 没有改变该启动器对 0.0.0.0 的绑定,因此仅升级并不能消除端口暴露。请按下文所述配置会话密钥并隔离端口。

如果暂时无法升级,或者您使用的是 Apptainer,请限制网络访问并配置身份验证。各启动器的限制如下:

  • 阻断端口: 通过主机防火墙规则,或在 Docker 的 DOCKER-USER 链中添加丢弃发往沙箱端口的非本地流量的规则,可以封堵远程路径,但端口仍绑定在所有网络接口上,任何仍能访问它的进程或容器都可以完整使用无需身份验证的 API。
  • 配置身份验证: 在启动 SDK 进程的环境中、于进程启动之前运行 export SESSION_API_KEY="$(openssl rand -hex 32)";启动器会转发该密钥,服务器会强制校验。已经在运行的沙箱会话会保留其无密钥的服务器,因此请启动新的会话。
  • Apptainer: 其客户端读取 SESSION_API_KEY,在 v1.52.0 中同样如此。请设置该变量并保持 OH_SESSION_API_KEYS_0 未设置,或将两者设为相同的值,使客户端与服务器保持一致。
  • 1.52.0 之前的 Docker: 客户端将其密钥硬编码为 None。设置任一密钥变量都能保护服务器,但会导致 SDK 会话失效;请将此作为临时遏制措施,并通过升级恢复会话。

根本原因是什么?

启动器在主机的所有网络接口上发布沙箱端口且未配置会话密钥,同时显式覆盖了 Agent 服务器自身的安全默认值,即在未设置密钥时绑定回环地址。

存在漏洞的代码是 SDK 1.51.0 版本中的 openhands-workspace/openhands/workspace/docker/workspace.py。启动器会构建用于启动沙箱及其 Agent 服务器的 Docker run 命令:

ports = ["-p", f"{self.host_port}:8000"]        # line 218
if self.extra_ports:
    ports += [
        "-p",
        f"{self.host_port + 1}:8001",  # VSCode   # line 222
    ]

Docker 的 -p 参数用于在主机上发布容器端口。如果像这里一样未在规格中指定主机 IP,发布地址就是 0.0.0.0,即主机拥有的所有网络接口。启用 extra_ports 时(默认关闭),启动器还会在 host_port + 1 上发布 VS Code 端口 8001,同样未指定主机 IP,因此这第二个端口也位于所有网络接口上。在同一条 run 命令的后面,启动器显式地将监听地址传给 Agent 服务器:

run_cmd = [
    "docker", "run", "-d", "--platform", self.platform, "--rm",
    ...
    image,
    "--host",
    "0.0.0.0",                                    # line 248-250
    "--port",
    "8000",
]

在容器内部,端口转发需要绑定 0.0.0.0。暴露范围取决于主机侧的发布位置,而这里发布到了所有网络接口。问题的另一半是身份验证。容器启动后,启动器会为该工作区配置 SDK 自身的客户端:

if not self.host:
    object.__setattr__(self, "host", f"http://127.0.0.1:{self.host_port}")
object.__setattr__(self, "api_key", None)          # line 272

客户端密钥: Docker 客户端的密钥被硬编码为 None。另外,启动器会将已设置的会话密钥环境变量转发到容器中,但只有服务器读取它们;SDK 自己的客户端从不获取该密钥。

转发的配置: Docker 启动器自身的 forward_env 字段说明指出,这些变量使服务器在监听 0.0.0.0 时能够对请求进行身份验证。

服务器默认值: 在未配置密钥变量时,服务器的 session_api_keys 列表为空。CVE 记录指出,正是这一默认值关闭了身份验证。

Agent 服务器的安全绑定默认值于 2026 年 7 月引入:拉取请求 #4180“fix(agent-server): default bind host to loopback without a session API key”使未配置密钥的服务器绑定回环地址。启动器显式传入的 --host 0.0.0.0 覆盖了这一默认值,这也是修复必须修改启动器而不是服务器的原因。

漏洞如何被利用?

任何能访问该端口的客户端,只需发送普通的未认证 API 请求,即可在沙箱内以 root 身份执行 shell 命令。 目前没有公开的漏洞利用代码。如需在不使用 payload 的情况下检测暴露情况,请从要检查的网络位置运行 curl -s -o /dev/null -w '%{http_code}\n' http://<sandbox-host>:<port>/api/conversations/count:200 表示 API 无需密钥即可响应,401 表示已强制使用会话密钥,000 表示从该位置无法访问该端口。根据 CVE 记录和源代码,攻击路径需要四个步骤:

  1. 攻击者能够通过已发布的端口访问主机:例如与沙箱主机位于同一 Docker 桥接网络上的容器、局域网中的主机,或在机器暴露且未经过滤时互联网上的任意主机。
  2. 攻击者向 Agent 服务器的 API 发送 HTTP 或 WebSocket 请求。由于服务器启动时 session_api_keys 列表为空,因此无需任何密钥。
  3. 服务器在沙箱容器内以 root 身份执行所请求的 shell 命令,并返回命令的全部输出。
  4. 除了命令执行之外,同一 API 还能访问挂载的工作区目录和密钥存储,CVE 记录指出这些密钥会以明文形式被读取。

整条攻击链并不需要请求本身存在漏洞:该 API 是 SDK 自身的接口,专为 Agent 使用而设计,只是它对任何人都处于监听状态。

攻击流程是怎样的?

发往被错误发布的端口的一个无需身份验证的请求,会变成在 Agent 自身沙箱内以 root 身份执行命令,而该沙箱还挂载着工作区和密钥存储。

flowchart LR A["网络上的攻击者
局域网、容器、互联网"] --> B["网络可访问的智能体端口
Docker:-p 未指定主机 IP
Apptainer:--host 0.0.0.0"] B --> C["session_api_keys 列表
为空的 Agent 服务器"] C --> D["无需身份验证的 HTTP 或
WebSocket 请求被接受"] D --> E["shell 命令在沙箱内
以 root 身份执行"] E --> F["读取工作区文件和
明文密钥存储"] style B fill:#f8d7da style C fill:#f8d7da

攻击者能做什么?

影响范围是沙箱容器及挂载到其中的数据。 根据 CVE 记录,攻击者可以在沙箱容器内以 root 身份执行任意 shell 命令,完全访问 Agent 所操作的已挂载工作区,并以明文形式读取 SDK 为该会话配置的密钥存储。这正是 Agent 的全部工作内容:它编辑的仓库或目录、其密钥存储中的凭据,以及该会话挂载的其他任何内容。需要满足以下两个前提条件:

  • 已发布端口可通过网络访问,而修复前的启动器会将其发布到所有网络接口;无需身份验证、无需用户交互,除网络位置外也无需任何权限。
  • 部署从未配置会话密钥,而这正是 SDK 的默认设置。

修复为何有效?

修复为提交 7b66584,于 2026 年 10 月 3 日作为拉取请求 #5209(“fix(workspace): publish Docker ports on loopback”)合并。它于 10 月 5 日随 v1.52.0 发布,同时解决了 Docker 发布地址和客户端密钥查找两个问题:

-        ports = ["-p", f"{self.host_port}:8000"]
+        ports = ["-p", f"127.0.0.1:{self.host_port}:8000"]
         if self.extra_ports:
             ports += [
                 "-p",
-                f"{self.host_port + 1}:8001",  # VSCode
+                f"127.0.0.1:{self.host_port + 1}:8001",  # VSCode
-        object.__setattr__(self, "api_key", None)
+        session_api_key = os.environ.get(
+            "OH_SESSION_API_KEYS_0", os.environ.get("SESSION_API_KEY")
+        )
+        object.__setattr__(self, "api_key", session_api_key)

现在端口仅发布在回环地址上,网络上的其他主机无法访问该端口,VS Code 端口也遵循同样的规则。这就是其中的取舍:其他机器上的客户端无法再直接连接已发布的端口。该拉取请求将此变更描述为让默认网络行为保持在本机范围内,因为 SDK 自身的客户端本来就通过 127.0.0.1 连接。容器内的服务器仍然绑定 0.0.0.0,修复自带的回归测试也对此进行了断言:容器内部的绑定是 Docker 端口转发所必需的,而主机侧的发布地址才是边界。

客户端现在会解析服务器所接受的同一个密钥,优先使用当前的 OH_SESSION_API_KEYS_0 变量,并以旧版的 SESSION_API_KEY 作为回退,因此已配置的密钥会在双向进行身份验证,而不再只用于服务器的入站检查。

该修复修改的是 Docker 启动器,其测试覆盖了回环地址发布和客户端密钥查找。安全公告也提到了 Apptainer,但其启动器在 v1.52.0 中仍然传入 --host 0.0.0.0。

对于 Apptainer,该修复没有任何改变:启动器在 1.52.0 中与之前一样,直接在主机上以 --host 0.0.0.0 运行 Agent 服务器,因此端口在所有网络接口上监听。缓解措施就是修复部分给出的方法:会话密钥加网络隔离。服务器监听工作区的 host_port,除非您另行指定,否则它是 30000 到 39999 之间的一个空闲端口。在主机上运行 ss -ltn | grep ':<port> ',查看它是否监听在 0.0.0.0 上,然后从不应访问它的机器上运行 nc -vz <host> <port>:如果连接成功,说明端口已暴露,只有防火墙和密钥在保护它。

如何检查我是否已遭入侵?

目前尚未发布与该漏洞相关的入侵指标(IOC),也没有已知的利用行为,因此没有可供 grep 的 IOC 列表。对于曾在可访问端口上运行 1.52.0 之前版本沙箱的部署,您可以检查以下内容:

  • 端口映射(docker ps --format '{{.Names}} {{.Ports}}'):修复前的沙箱会在发布行中显示 0.0.0.0:,这证实端口曾在所有网络接口上发布。这只是暴露条件的一半:是否有不受信任的一方能够访问它,取决于您的防火墙规则和网络路径。
  • 沙箱容器的日志:查找与您的 Agent 会话活动不对应的 HTTP 或 WebSocket 请求,因为 Agent 服务器会记录其处理的请求。
  • 密钥存储中沙箱可以访问的所有内容:将配置给已暴露沙箱的每个密钥都视为已泄露,进行轮换,并从干净的镜像重新构建容器。

如果暴露条件从未成立,即端口已被防火墙阻断,或主机不存在来自不受信任网络的路径,那么实际风险几乎为零,按计划打补丁即可。

时间线

  • 2026-07-23:Agent 服务器通过拉取请求 #4180 获得安全默认值,在未设置会话 API 密钥时绑定回环地址。
  • 2026-10-03:拉取请求 #5209“fix(workspace): publish Docker ports on loopback”作为提交 7b66584 合并。
  • 2026-10-05:v1.52.0 发布;其更新日志仅按标题列出该修复,未提及安全问题,也未发布安全公告。
  • 2026-10-26:VulnCheck 发布 CVE-2026-112346,CVSS v3.1 评分 9.8,v4.0 评分 9.3,CWE-1327,致谢 mil4n,受影响范围为 1.52.0 之前的版本。
  • 2026-10-26:GitHub 安全公告数据库将该记录同步为 GHSA-rp7h-crm7-h5qj。
  • 2026-10-27:发布本文。

常见问题(FAQ)

OpenHands 主平台是否存在同样的缺陷?

该安全公告涵盖 SDK 的 DockerWorkspace 和 ApptainerWorkspace 启动器,SDK 的本地会话会使用它们。安全公告并未确认托管版 OpenHands 平台或其他产品是否嵌入了这些启动器,因此任何基于 OpenHands 的部署的运维人员都应执行同样的检查:已安装的 SDK 版本,以及所有 agent-server 容器的发布地址。需要查找的特征是名为 agent-server-*、在 0.0.0.0 上发布 8000 端口的容器。

为什么 v1.52.0 发布时没有附带安全公告?

v1.52.0 的更新日志仅以拉取请求的标题“fix(workspace): publish Docker ports on loopback”列出该修复,未提及安全问题,发布时也没有附带安全公告。这就是为什么在 VulnCheck 的 CVE 记录于 10 月 26 日出现之前,依赖扫描器没有任何可告警的依据,也是为什么在此期间,可靠的信号是版本检查,而不仅仅是 CVE 数据源。

仅靠会话密钥是否足以提供保护?

它可以封堵无需身份验证的命令执行路径:服务器会在会话、命令执行和文件访问等受保护的路由上检查会话密钥。公开的服务器信息路由不能可靠地用于测试身份验证是否生效。但它不会改变端口的位置:1.52.0 之前的 Docker 启动器仍会在所有网络接口上发布端口,而它自己的客户端无法发送密钥,因此会话会失效。1.52.0 之前的 Apptainer 启动器在设置 SESSION_API_KEY 后仍可正常工作,因为其客户端会读取该变量。对于 Docker,升级会增加回环地址发布。对于 Apptainer,除了身份验证之外,还应在网络层面隔离该端口;1.52.0 不会改变其绑定地址。

我是否需要重新创建正在运行的容器?

需要。端口映射是在创建容器时设定的,因此即使您在主机上升级了 SDK,已经在运行的沙箱仍会保留其在 0.0.0.0 上的发布。请停止并重新创建每个沙箱,然后使用 docker ps 确认新的映射。

为什么修复后三周内都没有安全公告?

CVE 记录、GHSA 条目和数据库映射的日期都是 10 月 26 日,即修复版本发布三周之后。及时升级的部署在不知情的情况下就已安全,而固定版本的部署在此期间没有任何可以触发告警的安全公告。这正是在 Agent 基础设施上坚持版本管理的典型理由:绑定网络端口的沙箱组件值得在版本层面进行跟踪,而不仅仅在 CVE 层面跟踪。

openhands-sdk 低于 1.52.0 的版本还在哪里?

在您的仓库和容器镜像上运行 Ostorlab SCA,列出仍包含低于 1.52.0 版本 openhands-sdk 的所有项目和发布版本。

参考资料

  • CVE-2026-112346 记录,由作为 CNA 的 VulnCheck 于 2026 年 10 月 26 日发布,包含 CVSS v3.1 9.8 和 v4.0 9.3 向量、CWE-1327、1.52.0 之前的受影响范围以及对两个启动器的描述:https://www.cve.org/CVERecord?id=CVE-2026-112346(2026 年 10 月 28 日查阅)
  • GHSA-rp7h-crm7-h5qj,从 CVE 记录同步而来的 GitHub 安全公告数据库条目:https://github.com/advisories/GHSA-rp7h-crm7-h5qj(2026 年 10 月 27 日查阅)
  • VulnCheck 安全公告“OpenHands Software Agent SDK < 1.52.0 Unauthenticated Sandbox Agent Server RCE”,严重程度为严重,致谢 mil4n:https://www.vulncheck.com/advisories/openhands-software-agent-sdk-unauthenticated-sandbox-agent-server-rce(2026 年 10 月 27 日查阅)
  • 修复拉取请求 #5209“fix(workspace): publish Docker ports on loopback”,于 2026 年 10 月 3 日合并,其描述中说明了客户端密钥传递的理由:https://github.com/OpenHands/software-agent-sdk/pull/5209(2026 年 10 月 27 日查阅)
  • 修复提交 7b66584ff8899144eb4ce679c2811861342af9de,包含回环地址发布和会话密钥解析的 diff:https://github.com/OpenHands/software-agent-sdk/commit/7b66584ff8899144eb4ce679c2811861342af9de(2026 年 10 月 27 日查阅)
  • v1.52.0 版本,于 2026 年 10 月 5 日发布,列出该修复但未提及安全问题:https://github.com/OpenHands/software-agent-sdk/releases/tag/v1.52.0(2026 年 10 月 27 日查阅)
  • v1.51.0 中存在漏洞的源代码 openhands-workspace/openhands/workspace/docker/workspace.py,第 89 至 95 行(forward_env)以及第 218、222、248 至 250 和 272 行:https://github.com/OpenHands/software-agent-sdk/blob/v1.51.0/openhands-workspace/openhands/workspace/docker/workspace.py(2026 年 10 月 27 日查阅)
  • Agent 服务器的安全默认值,拉取请求 #4180“fix(agent-server): default bind host to loopback without a session API key”,于 2026 年 7 月 23 日合并:https://github.com/OpenHands/software-agent-sdk/pull/4180(2026 年 10 月 27 日查阅)
  • openhands-sdk 的 PyPI 下载统计,所查阅的一周内下载量为 604,050 次:https://pypistats.org/packages/openhands-sdk(2026 年 10 月 27 日查阅)
  • v1.52.0 中的 Apptainer 启动器,仍保留 0.0.0.0 绑定和通过 SESSION_API_KEY 进行的客户端密钥查找:https://github.com/OpenHands/software-agent-sdk/blob/v1.52.0/openhands-workspace/openhands/workspace/apptainer/workspace.py(2026 年 10 月 28 日查阅)