03-storage — Kimi 沙箱存储与持久化层

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

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

取证时间:2026-08-18。访问方式:SSH(kimi 用户只读)+ root 级 HTTP 文件服务兜底(127.0.0.1:18080)。 沙箱全程未做任何修改。

0. 总览:三层存储架构

沙箱的文件持久化由三个独立层次组成,互不重叠:

挂载点后端读写内容持久性
容器根/(overlay2)宿主机块设备 /run/blk-cube/vdc/0..60 + 可写层 vda/disk/2d9b504a.../upperrw系统、应用代码容器级(镜像层只读,upper 层是否跨重建持久未知)
portal overlay/mnt/portal-overlay(fuse.portal)云端 PortalService(https://kimi-api-sandbox.msh.team/apiv2只读(目录项 -r--r--r--、owner root)用户资产:skills、plugins、auth、vault、agent-gw 凭证、网站模板云端持久(用户/租户级)
drive9/mnt/agents(fuse.drive9)内网 drive9 server http://10.213.5.144,路径 :/projects/<project-id>可读写项目工作区(upload/output/temp 等)云端持久(项目级),close-sync 同步

另有 /workspace(空目录,kimi:kimi)与 /opt/moonbox-project-template/version.json 中声明的兼容路径。

证据(cat /proc/mounts 摘录):

overlay2 / overlay rw,relatime,lowerdir=/run/blk-cube/vdc/60:...:/run/blk-cube/vdc/0,
  upperdir=/run/blk-cube/vda/disk/2d9b504a104a439f9374a6374a236fa7/upper,...
portal /mnt/portal-overlay fuse.portal rw,nosuid,nodev,relatime,allow_other,max_read=1048576
drive9 /mnt/agents fuse.drive9 rw,nosuid,nodev,relatime,allow_other,max_read=1048576

df -h:overlay2 41G(仅用 31M,刚重建);drive9 显示 1.0T 配额。


1. portal 二进制(/usr/local/bin/portal)

  • ELF x86-64,52MB,Go 静态编译,stripped,构建于 2026-08-11。
  • portal --help 揭示它是一个 FUSE overlay 文件系统,把云端各类用户资产挂到本地一个挂载点,关键 flags:
-agentgw        Mount the agent-gw client configuration file.
-auth           Mount the user's persistent CLI/plugin credentials.
-configured_readonly  Mount KimiAPI-configured public read-only directories.
-evo_sessions   Mount a Skill-Evo job's candidate-session overlay (evo sandbox only).
-plugins        Mount the user's installed plugins.
-skills         Mount the user's installed skills.
-upload         Mount the chat upload directory.
-vault          Mount the user's dream vault.
-workspace      Mount the chat workspace as the writable root.
-http-bind      Enable HTTP server for chat bind API   (-http-addr 默认 :8080)
-loopback / -loopback.root / -loopback.path   Overlay local-only loopback directories(本地落盘、不回云)
-gateway-addr   https://kimi-api-sandbox.msh.team/apiv2 (prod 默认)
-gateway-dev-addr  https://kimi.kimi.team/apiv2 (dev 默认)
  • 二进制 strings 显示它通过 connect-RPC 调 dev.msh.team/protos/kimi-go/kimi/portal/v1 的 PortalService,方法包括:ValidateBindTokenGetAPIKeyGetS3CredentialsListChatFilesGetEvoSessionFile(s)SetupAppGetVault/GetVaultPageGetAutoDreamGetWebsiteUrl/GetWebsiteInfraGetBucketWebsite/PutBucketWebsite/DeleteBucketWebsite。即 portal 是 skills/plugins/凭证/vault/网站部署等多类云资产的统一”门面 FS”。

1.1 启动流程:空 manifest 启动 + claim 时绑定(s6 服务脚本 /etc/s6-overlay/s6-rc.d/portal/run

脚本内注释直接写明了架构契约:

Go portal overlay service (bind-token STS contract). Boots with an EMPTY mount manifest and -http-bind (:8080): kimiwarden delivers the identity at claim time — POST /api/v1/bind_token (warden-signed HS256 JWT that portal exchanges for scoped STS via kimiapi PortalService), then POST /api/v1/bind with the overlay manifest (skills/plugins/auth/vault).

运行中的实际命令行(ps aux):

/usr/local/bin/portal -http-bind -allow-other -env prod \
  -gateway-addr https://kimi-api-sandbox.msh.team/apiv2 /mnt/portal-overlay
  • 启用门:KIMI_PROJECT_PORTAL_CAPABILITY_ENABLED=true 或旧信号 KIMI_PROJECT_PORTAL_OVERLAY=1(由 kimiwarden / reception preset 注入)。
  • 注释还提到旧的静态 K_OSS_* 凭证已废弃(”fails closed”),一切授权走 claim 时 bind-token 换 STS。
  • :8080 实测在线:GET /404 page not foundGET /api/v1/bindMethod not allowed(要 POST)。

1.2 /mnt/portal-overlay 内容结构

/mnt/portal-overlay/
├── .agent-gw.json          (444 root, 只读;portal -agentgw 挂载)
├── .agents/plugins/        (portal -plugins:audio_generation, baidu-pan, china_standards,
│                            cloudflare, github, ifind, image_generation, imf,
│                            interactive-research-report-en, lark, musepool, scholar,
│                            sec_edgar, sp_data, ...)
├── .user/
│   ├── auth/               (portal -auth:dws/, lark/ 两类凭证目录)
│   └── skills/             (portal -skills:用户安装的技能,如 engineering-ppt-converter,
│                            openclaw-selfhost-ops, translation-review-coach 等 6 个)
├── .vault/                 (portal -vault "dream vault",当前为空)
└── .websites-templates/    (portal -configured_readonly:13 个网站模板
                             0-origin, 1-cyber-rain-frontend, ..., 12-seaside-stay-fullstack;
                             每个目录含 <name>.zip + info.md)

.agent-gw.json 内容(凭证已脱敏):

{"api_key":"sk-kimi-8OQEd910...",           // 存在:agent-gw API key,sk-kimi- 前缀
 "base_url":"https://agent-gw.kimi.com/coding",
 "kimi_chat_id":"1a014098-****-****-****-********15cb"}

.vault(”dream vault”)当前为空目录;.user/auth/dws 下有 .dws.local 子目录(CLI/插件持久凭证存放点,未读内容)。

1.3 符号链接缝合(reception preset init_script 建立)

容器内大量”本地路径”实际是指向 portal overlay 或 drive9 的 symlink:

/app/.vault               -> /mnt/portal-overlay/.vault
/app/.websites-templates  -> /mnt/portal-overlay/.websites-templates
/app/.agents/plugins      -> /mnt/portal-overlay/.agents/plugins
/mnt/agents/.agents/plugins -> /mnt/portal-overlay/.agents/plugins
/mnt/agents/.user/auth    -> /mnt/portal-overlay/.user/auth
/mnt/agents/.user/skills  -> /mnt/portal-overlay/.user/skills
/mnt/agents/.vault        -> /mnt/portal-overlay/.vault
/mnt/agents/.websites-templates -> /mnt/portal-overlay/.websites-templates

注意:/app/.agents/skills 不是 symlink,是容器镜像内真实目录(预装公共技能集,约上百个),与 portal 挂的用户技能(/app/.user/skills)分离。


2. drive9(/usr/local/bin/drive9)— 可写项目存储

  • ELF x86-64,19.7MB,Go 编译(go1.26.1),带 debug_info 未 strip。是个功能完整的存储/协作 CLI,子命令(drive9 --help 摘录):
create / delete / admin <tenant|pool>          # 租户管理(TiDB Cloud 模式)
ctx show|add|import|fork|ls|use|rm             # 上下文(身份);fork 是 copy-on-write
fs <command>                                   # 文件系统操作
token issue|revoke                             # workspace-zone scoped token
vault set|get|put|with|ls|rm|grant|revoke|audit  # 密钥 vault
journal new|append|cat|find|verify             # append-only agent/workflow 日志
git clone --fast                               # git-aware 快速克隆
pack / unpack                                  # 把本地 overlay 打包到 drive9/S3(快照迁移)
mount [flags] [:/remote] <mountpoint>          # FUSE 挂载(另有 mount drain / mount vault)
profile show                                   # 挂载 profile([local]/[remote] 过滤规则)
region list                                    # 从 drive9 manifest 列出供应区域
doctor fuse / update                           # 诊断与自更新

二进制 strings 还暴露:shadow 写回缓存(writeback-cachesame-path-dirtyflush_debounced)、commit queue 冲突自动重试、SSE 缓存失效(SSE reset — invalidating all caches)、TiDB Cloud 物理共享池租户模型等。

2.1 实际挂载(ps aux)

/usr/local/bin/drive9 mount --foreground --mode=fuse -allow-other \
  -readdir-prefetch --parallel-read-concurrency 8 --read-concurrency 48 \
  --server http://10.213.5.144 --profile kimi-project --durability close-sync \
  :/projects/1a01406e-****-****-****-********d0ce /mnt/agents

mount 日志(/tmp/kimi-project-drive9.log):

drive9: sync mode: strict
drive9: mounted on /mnt/agents (server: http://10.213.5.144,
  actor: 0a8893244854f033a73bd4ade8bc0548, readonly: false,
  write_policy: close-sync,
  cache: /root/.cache/drive9,
  shadow: /root/.cache/drive9/6504f2b4669187a6/shadow)
drive9: SSE reset — invalidating all caches
... flush upload failed for /output/frp-share/frpc.log:
  Put "http://10.213.5.144/v1/fs/projects/1a01406e-****-****-****-********d0ce/output/frp-share/frpc.log": context canceled

说明:写策略 close-sync(close 时同步上传),本地 shadow 缓存在容器内 /root/.cache/drive9//shadow;远端 API 形态是 PUT /v1/fs/projects//;SSE 推送用于缓存失效(多客户端一致性)。

2.2 /root/.drive9/ 配置与凭证

  • 目录只有 profiles/kimi-project 一个 profile,内容是 [local] 过滤清单(哪些路径只留在本地、不同步到云端):
[local]
.store/**  **/.cache/**  **/.git/**  **/.gradle/**  **/node_modules/**
**/target/**  **/dist/**  **/build/**  **/coverage/**  **/__pycache__/**
**/.venv/**  **/tmp/**  .kimi/**  .agents/**  .user/**  .vault/**  ...

即 node_modules、构建产物、git 内部、以及 .agents/.user/.vault(后者本来就由 portal overlay 提供)都不回传云端 —— 这是项目同步的关键策略。

  • 凭证在服务 env 文件 /run/service/drive9-fuse/env(root 600 语义,经 HTTP 兜底读取):
HOME='/root'
DRIVE9_SERVER='http://10.213.5.144'
DRIVE9_API_KEY='drive9_ZXlKaGJHY2lPaUp...'   # 存在:格式 drive9_<base64(JWT)>;
  # JWT payload 含 tenant_id=890c103a-****-****-****-********625a、
  # token_version=1、journal_permissions 列表、iat≈1785482181(2026-08-18 前后签发)

kimi 用户侧 ~/.drive9 无 context(drive9 ctx ls → “no contexts configured”),即只有 root 身份的 FUSE 守护进程持有凭证,沙箱内用户拿到的只是挂载视图。

2.3 s6 服务与 watchdog(/run/service/drive9-fuse/rundrive9-fuse-watchdog/run

  • 启动脚本:每次(重)启动前先 fusermount3 -uz 清残留死挂载;若挂载点被非 drive9 占用则拒绝清理直接退出(防止误清)。启动间隔节流(<2s sleep 1)。
  • watchdog:每 15s 探活 stat /mnt/agents/. + ls -U .drive9-health(专用小目录,避免大目录 readdir 触发预取回源);连续 3 次失败 pkill -9 drive9 交给 s6 重启;探活目录缺失时先 mkdir -p 自愈(真挂死时 mkdir 也会超时,不误放)。

2.4 /mnt/agents 内容

/mnt/agents/
├── .agents/  .user/  .vault  .websites-templates   # 全是 portal overlay 的 symlink(见 1.3)
├── .drive9-health/    # watchdog 探活目录
├── .tmp/              # drive9 内部暂存(root 700)
├── upload/            # 用户上传文件入口(当前空)
├── output/            # 产物出口;现有 output/frp-share/(frpc 客户端与日志 —— 即本分析所用 SSH 隧道的载体)
└── temp/              # 临时(当前空)

kernel-server(jupyter :8888)的 WORKDIR 就是 /mnt/agentsversion.json 声明 workspace_path=/mnt/agentsproject_output_dir=/output,并给出兼容路径集(/mnt/okcomputer/mnt/kimi/workspace/project/app/.user/skills 等)。


3. /workspace

/workspace 是空目录(kimi:kimi, 0755,镜像自带),无任何内容、未被挂载、未被 s6 脚本或 /app 代码引用。version.json 的 compat_paths 里只提到 /workspace/project。判断:遗留兼容路径/保留挂载点,当前模板下不起作用,工作区已全部收敛到 /mnt/agents


4. 项目 ID 与 kimi_chat_id 的关系

两个 ID 是不同对象、各自独立

ID语义出现在
project-id1a01406e-*---*****d0cedrive9 项目工作区(:/projects/<id>,PUT /v1/fs/projects/<id>/…)drive9 挂载命令行、API 路径
kimi_chat_id1a014098-*---*****15cb聊天会话身份,随 agent-gw 凭证绑定/mnt/portal-overlay/.agent-gw.json(portal -agentgw 挂载)

两者同为 1a0140…-…-8… 形态的自定义 UUID(version nibble=8,应为内部有序 ID 方案),前缀相近只是同一租户/批次生成的表象。机制上的关系是:drive9 挂载在沙箱启动时按 project-id 静态拉起(凭证在 env 文件);而 chat 身份在”claim 时”由 kimiwarden 以 bind_token(HS256 JWT)POST 到 portal :8080 换 STS 后才出现.agent-gw.json 即此绑定的产物。一个项目工作区可被不同 chat 复用/重新绑定(portal 启动时 manifest 为空就是为这个动态绑定设计的)。


5. 结论:哪些数据持久、存在哪、怎么同步

持久(云端):

  • 项目工作区全部用户数据 → drive9 server 10.213.5.144projects/<project-id>,经 FUSE + close-sync 写回(close 即上传,shadow 本地缓存兜底,SSE 做多端缓存失效);
  • 用户级资产(已装 skills、plugins、CLI/plugin 凭证、vault、agent-gw 凭证)→ Kimi PortalService 云端,沙箱内以 portal FUSE 只读呈现,写入走云端控制面而非沙箱内直接写;
  • 公开只读资产(websites-templates)→ KimiAPI 配置的公共只读目录。

不持久 / 只留本地:

  • profile kimi-project[local] 清单命中的路径:node_modules、.git、构建产物、缓存、.kimi.agents.user.vault 等 —— 明确不回传;
  • 容器根 overlay 的其余部分(系统改动、/root/.cache/drive9 shadow 等)随容器生命周期;
  • /mnt/agents/.tmp(drive9 内部暂存)。

同步触发点: 文件 close(close-sync 写策略);drive9 mount drain 可手动排空;watchdog 保证 daemon 假死时被 s6 拉起重挂并重放 shadow。

凭证面(均已脱敏):

  • sk-kimi-8OQEd910...:agent-gw API key,存于 portal 挂载的只读 .agent-gw.json
  • drive9_ZXlKaGJHY2l...:drive9 租户 JWT(tenant_id=890c103a-…),仅存于 root 服务 env /run/service/drive9-fuse/env,kimi 用户不可见。

6. 对 Hermes 可移植的点(详见主报告)

  1. “空 manifest 启动 + claim 时绑定”的 overlay 凭证注入:portal 先以无身份状态起 FUSE + HTTP bind API,由控制面(kimiwarden)下发短期 JWT 换 STS 再按需挂载 skills/插件/凭证 —— 免镜像烘凭证、天然支持会话复用与权限最小化。
  2. profile 驱动的同步过滤清单:drive9 kimi-project profile 用一份 [local] glob 清单声明”哪些不回传”,把持久性策略做成显式配置而非隐式行为。
  3. FUSE 假死 watchdog 模式:专用小目录探活 + mkdir 自愈 + 连续失败才 pkill 交 s6 重启,避免大目录 readdir 触发回源,是远端 FS 守护的成熟做法。