← 返回主报告: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-kernel,os.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 | /health | kernel 初始化检查(未初始化 503) |
| GET | /kernel/status | alive / pid / connection_file / client_connected |
| GET | /kernel/connection | 返回完整 connection_info(含 ZMQ HMAC key 明文) |
| GET | /kernel/connection-file | 简化版连接文件路径 |
| GET | /kernel/debug | KernelManager 内部状态 dump(”开发用”但生产开着) |
| POST | /kernel/reset | 销毁并重启 kernel |
| POST | /kernel/interrupt | SIGINT 中断 |
| 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收进 imagesdisplay_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 实测)
| 端口 | 绑定 | 进程 |
|---|---|---|
| 8888 | 0.0.0.0 | kernel_server.py (PID 74, kimi) ← 本节主角 |
| 9222 | 127.0.0.1 | Chromium CDP |
| 9223 | 0.0.0.0 | project-cdp-proxy.py (PID 64,CDP 代理) |
| 6080 | 0.0.0.0 | Xvnc/kasmvnc websocket (-websocketPort 6080) |
| 18080 | 0.0.0.0 | python3 -m http.server --directory /tmp/project-probe(PID 17, root) |
| 8080 / 32001 | tcp6 ::: | Go 服务(portal PID 65 / envd PID 77),根路径返回 “404 page not found”(Go 风格) |
| 45059/45759/44903/49717/57453 | 127.0.0.1 | ipykernel 的 5 个 ZMQ 端口 |
| 22 | 0.0.0.0 | sshd |
注:kernel-server 日志走 stdout(logging.basicConfig,S6_LOGGING=0),/app/logs/ 目录为空——没有落盘日志,排查只能靠 s6 捕获或 /proc。
8. 敏感信息记录(已脱敏)
- ZMQ HMAC key:
/tmp/tmpc80xj0x6.json内key字段,UUID 格式b447fec7...,且可通过无鉴权的GET /kernel/connection远程读取 —— 设计上的弱点。 SSH_PASSWORD=sshpassword明文出现在 kernel-server 进程环境变量(/proc/74/environ)——占位弱密码。- pip 内网源地址
pypi.mirrors.msh.team、kimi-api-sandbox.msh.team(portal 网关地址)。
9. 对 Hermes 可移植的结论
- 整体模式(FastAPI 包 KernelManager、HTTP 同步聚合 iopub、base64 图片内联回传)简单可靠,可直接借鉴;jupyer_kernel.py 470 行即覆盖启动/保活/中断/超时/重置全生命周期。
- Kimi 选择了”零鉴权 + 容器网络隔离”;Hermes 若部署形态不同(多租户/暴露端口),8888 这类端点必须加 token,且不应提供
/kernel/connection、/kernel/debug这种泄露连接密钥的调试端点。 - init 脚本机制(每次 reset 后自动重放 matplotlib/字体/PIL viewer 配置)是让”模型画图→回传图片”体验稳定的关键细节,值得照搬。