02-runtime — Kimi 沙箱代码执行运行时(kernel-server / ipykernel)

← 返回主报告:Kimi Agent(云端沙箱)技术报告GitHub 原文

合规与风险声明:本报告为安全研究与互操作学习目的的逆向分析,全程只读采集,未对目标系统做任何修改;文中所有密钥、token、账号级标识(chat_id / project_id / tenant_id 等)均已脱敏;沙箱内发现的默认弱口令(VNC/SSH)属平台侧配置,仅作安全发现披露,请勿用于访问任何不属于自己的系统。docx/pdf/xlsx/kimi-slides 等引擎二进制为 Moonshot 专有许可(禁再分发/逆向),本文仅作行为级描述。报告基于单次分析窗口(约 50 分钟生命周期),软件版本与技能库存随镜像更新可能变化。

采集时间:2026-08-18。来源:SSH 只读访问(kimi 用户)+ 沙箱内实测 curl。所有文件路径为沙箱内路径。

1. 总体架构:一条 HTTP → ZMQ → ipykernel 的链路

云端 kimiagent(沙箱外)执行代码的完整链路:

云端 agent (kimiagent)
  │  HTTP POST /kernel/execute  {"code": "...", "timeout": 30}
  ▼
kernel-server  :8888  (FastAPI + uvicorn, /app/kernel_server.py, PID 74, 用户 kimi)
  │  jupyter_client.KernelManager / BlockingKernelClient (ZMQ, HMAC-SHA256 签名)
  ▼
ipykernel 子进程  (/usr/local/bin/python3 -m ipykernel_launcher -f /tmp/tmpXXX.json, PID 180)
  │  iopub/shell ZMQ 消息流
  ▼
kernel-server 聚合 stream/error/execute_result/display_data → JSON 响应回传

关键事实:kernel-server 是沙箱内唯一面向云端模型的代码执行入口。/app/.agents/skills/ 下 grep 8888 无任何业务引用(命中的全是 node_modules 里无关文本),说明调用方在沙箱外——云端 agent 直接打容器的 0.0.0.0:8888

2. kernel-server 进程与启动方式

  • s6 服务定义:/etc/s6-overlay/s6-rc.d/kernel-server/run(type=longrun,无 dependencies 文件)
  • 启动脚本内容要点:
  export WORKDIR="${KERNEL_SERVER_WORKDIR:-/mnt/agents}"
  export DISPLAY=:99; export XAUTHORITY=/home/kimi/.Xauthority; export HOME=/home/kimi
  mkdir -p ${WORKDIR}; chown -R kimi:kimi ${WORKDIR}; cd ${WORKDIR}
  exec /command/s6-setuidgid kimi python3 /app/kernel_server.py --host 0.0.0.0 --port 8888
  • 实际环境变量 KERNEL_SERVER_WORKDIR=/tmp/kimi-project-kernel(见 /proc/74/environ),所以代码执行 cwd 不是工作区 /mnt/agents,而是 /tmp/kimi-project-kernel。实测 os.getcwd() 返回 /tmp/kimi-project-kernelos.getuid()=999(kimi)。
  • 进程同时注入 DISPLAY=:99(Xvnc 虚拟显示),因此 kernel 里跑的代码可以直接操作 GUI/截图。
  • 镜像/构建信息:MOONBOX_PROJECT_TEMPLATE_VERSION=project-20260811-2,Python 3.12.12,pip 源指向 pypi.mirrors.msh.team / mirrors.cloud.aliyuncs.com

3. kernel-server 的 HTTP API(/app/kernel_server.py,约 330 行)

FastAPI 应用,uvicorn 跑在 0.0.0.0:8888。端点清单:

方法路径作用
GET/服务信息
GET/healthkernel 初始化检查(未初始化 503)
GET/kernel/statusalive / pid / connection_file / client_connected
GET/kernel/connection返回完整 connection_info(含 ZMQ HMAC key 明文
GET/kernel/connection-file简化版连接文件路径
GET/kernel/debugKernelManager 内部状态 dump(”开发用”但生产开着)
POST/kernel/reset销毁并重启 kernel
POST/kernel/interruptSIGINT 中断
POST/kernel/execute执行代码,{code, timeout(默认30s), restart(bool)}

鉴权:完全没有。 无 token、无签名、无 IP 白名单,CORS 配置为 allow_origins=["*"] + allow_credentials=True。我在沙箱内不带任何凭据直接执行成功:

$ curl -s -X POST http://127.0.0.1:8888/kernel/execute \
    -H 'Content-Type: application/json' -d '{"code":"print(1+1)","timeout":10}'
{"success":true,"output":"2","error":null,"images":[]}

安全性完全依赖网络层隔离(容器不出网到非授权方)。任何人能到 8888 就能以 kimi 身份执行任意代码,且 /kernel/connection 还会泄露 ZMQ HMAC key(实测值为 b447fec7-...,UUID 格式),拿到后可绕过 kernel-server 直接连 ZMQ 端口执行。

4. ipykernel 的启动与管理(/app/jupyter_kernel.py,约 470 行)

JupyterKernel 类封装 jupyter_client.manager.KernelManager

  • 启动KernelManager(ip=get_host_ip())start_kernel(),jupyter_client 自动生成 connection file(本次为 /tmp/tmpc80xj0x6.json),再起 client、start_channels(),循环 wait_for_ready(timeout=30)。注意构造时传了容器 hostname IP,但实际 connection file 里 ip 字段是 127.0.0.1(5 个 ZMQ 端口为 127.0.0.1 上的随机高位端口:shell 45059 / iopub 45759 / stdin 44903 / control 49717 / hb 57453,与 netstat -tln 输出一一对应)。
  • 初始化脚本:每次新 kernel 启动后置 _init_script_pending=True,在下一次 /kernel/execute 前先执行 /app/ipython.py.init;失败则整个请求 500。init 脚本内容:matplotlib inline + 全套 rcParams(中文字体探测:扫描 fontManager 里名字含 CJK/Han/CN/TW/JP 的字体追加到 font.family)、注册 ImageShow.IPythonViewer 让 PIL 图片直接以 PNG 回传。
  • 保活:每次 execute 前 _ensure_kernel_alive()——检查 is_alive()kernel_info() 探活,失败自动 _start_kernel() 重启。
  • 并发:kernel_server 用一把全局 asyncio.Lock,所有 execute/reset 串行(单 kernel 模型,同一时刻只有一个执行)。
  • 中断:双保险——先按 PID 发 SIGINT(_get_kernel_pid() 有 4 级 fallback:provisioner.process.pid → km.kernel.pid → 按 kernel_id 扫 psutil 进程 → 按 connection file 名扫进程),再尝试 client.interrupt()。
  • 超时:execute 超过 timeout 后自动 interrupt,等待 kernel 回 idle,返回 Execution timed out after N seconds。异常路径还会自动重启 kernel。
  • 获取 PID 的四级 fallback 是个细节亮点:兼容 jupyter-client 新旧版本,对 Hermes 管理子进程有参考价值。

5. 执行结果的回传协议

kernel-server 只消费 iopub 通道,按 msg_type 分流:

  • stream → 原样拼接 stdout/stderr 文本
  • error → traceback 拼接进 error 字段
  • execute_result → 取 text/plain 追加到 output;有 image/png 收进 images
  • display_data → 优先收 image/png,否则 text/plain
  • 终止条件:收到本 msg_id 的 status: idle

响应 JSON:{success, output(str), error, images[list[str]]}——图片是 base64 PNG 数组直接内联在 HTTP 响应里,前端/agent 拿到即可渲染。没有单独的流式接口,是一次性同步返回(SSE/WebSocket 流式由云端 agent 侧自己处理或没有)。

6. 安全限制与拦截逻辑(重点:几乎没有)

kernel-server / jupyter_kernel 两层没有任何代码审查、命令黑名单、网络/文件系统拦截。已验证的限制只有:

  • 执行超时(默认 30s,调用方可调大)+ 超时自动 SIGINT
  • 全局串行锁(单 kernel)
  • 以非 root 用户 kimi(uid 999)运行
  • ZMQ 通道本身的 HMAC-SHA256 签名(防的是乱连,不是权限控制)

即:安全模型 = “容器即沙箱”,隔离边界在容器/网络层(只有 portal 网关对外),不在运行时内。拦截类逻辑(browser-guard 管的是浏览器导航,不在本节范围)与代码执行无关。

/app/utils.py(约 50 行)只是三个小工具:get_screensize()(调 xrandr 拿 Xvnc 分辨率)、run_command()run_command_background()——供 kernel 内代码 import 用,进一步印证”GUI + shell 能力直接暴露给模型代码”的设计取向。

7. 端口归属(netstat/ss 实测)

端口绑定进程
88880.0.0.0kernel_server.py (PID 74, kimi) ← 本节主角
9222127.0.0.1Chromium CDP
92230.0.0.0project-cdp-proxy.py (PID 64,CDP 代理)
60800.0.0.0Xvnc/kasmvnc websocket (-websocketPort 6080)
180800.0.0.0python3 -m http.server --directory /tmp/project-probe(PID 17, root)
8080 / 32001tcp6 :::Go 服务(portal PID 65 / envd PID 77),根路径返回 “404 page not found”(Go 风格)
45059/45759/44903/49717/57453127.0.0.1ipykernel 的 5 个 ZMQ 端口
220.0.0.0sshd

注:kernel-server 日志走 stdout(logging.basicConfigS6_LOGGING=0),/app/logs/ 目录为空——没有落盘日志,排查只能靠 s6 捕获或 /proc。

8. 敏感信息记录(已脱敏)

  • ZMQ HMAC key:/tmp/tmpc80xj0x6.jsonkey 字段,UUID 格式 b447fec7...,且可通过无鉴权的 GET /kernel/connection 远程读取 —— 设计上的弱点。
  • SSH_PASSWORD=sshpassword 明文出现在 kernel-server 进程环境变量(/proc/74/environ)——占位弱密码。
  • pip 内网源地址 pypi.mirrors.msh.teamkimi-api-sandbox.msh.team(portal 网关地址)。

9. 对 Hermes 可移植的结论

  1. 整体模式(FastAPI 包 KernelManager、HTTP 同步聚合 iopub、base64 图片内联回传)简单可靠,可直接借鉴;jupyer_kernel.py 470 行即覆盖启动/保活/中断/超时/重置全生命周期。
  2. Kimi 选择了”零鉴权 + 容器网络隔离”;Hermes 若部署形态不同(多租户/暴露端口),8888 这类端点必须加 token,且不应提供 /kernel/connection/kernel/debug 这种泄露连接密钥的调试端点。
  3. init 脚本机制(每次 reset 后自动重放 matplotlib/字体/PIL viewer 配置)是让”模型画图→回传图片”体验稳定的关键细节,值得照搬。