agentKimi
An MCP server that runs Kimi K2.7-code (Moonshot AI) as an autonomous codingagent inside a bubblewrap-sandboxed git worktree. Kimi gets the full Claude Codetoolset (Write, Edit, Bash, Read, Glob, Grep, …) via the Claude Agent SDK, works inan isolated worktree, and the server returns a git diff of everything it changedas ground truth for review.
Built with TypeScript + Bun, the@modelcontextprotocol/sdk (stdiotransport), and @anthropic-ai/claude-agent-sdkpointed at Moonshot's Anthropic-compatible endpoint.
Platform: Linux only. Requires
bubblewrap(bwrap) and unprivileged usernamespaces — the sandbox fails closed if they're unavailable (no unsandboxedfallback).
Tools
| Tool | Returns |
|---|---|
agentkimi_start(prompt, workdir?) |
{ session_id, summary, diff, files_changed } |
agentkimi_send(session_id, message) |
{ summary, diff, files_changed, test_output } |
agentkimi_end(session_id) |
Closes the session (removes the worktree, keeps the branch). |
Multi-turn conversations resume via the SDK's resume (survives server restarts).One worktree per session.
Worktree modes
workdiris inside a git repo → a linked worktree on branchagentkimi/<id>offHEAD. The branch is preserved afteragentkimi_end, so youcan inspect, merge, or delete it. Its git-dir lives in the source repo and is neverexposed to the sandbox.- No
workdir→ a throwawaygit initrepo under~/.agentkimi/worktrees/<id>, with a separated git-dir under~/.agentkimi/gitdirs/<id>that is never mounted into the sandbox. Removed on end.
Security model
The threat model assumes Kimi may be jailbroken or prompt-injected and will try toread host secrets, escape the sandbox, or run code outside it. There are two realboundaries, plus several hardening layers.
bwrap namespace — the PRIMARY boundary (bwrap.ts)
Each turn runs as a bubblewrapped subprocess in a fresh user/PID/IPC/UTS/cgroupnamespace:
- Host secrets are not mounted —
~/.ssh,~/.aws,~/.git-credentials, and anyother home/secret directory simply do not exist inside the namespace (ENOENT),even via interpreters (python,node,bun). This is containment byconstruction, not by command filtering. --clearenv— the namespace starts from an empty environment; onlyHOME,PATH, andCLAUDE_CODE_TMPDIRare set. The API key never appears in thenamespace environment, so it can't be read via/proc/<pid>/environ.- Private SDK tmpfs — the SDK extracts its bundled
claudebinary into aper-session tmpfs (/sbx-tmp, viaCLAUDE_CODE_TMPDIR). The host-shared/tmp/claude-<uid>is never mounted, so a sandboxed process can't poison a binaryan unsandboxed host process later runs, or read another session's output. - Empty tmpfs
HOME; the project dir and Bun runtime are mounted read-only;the worktree is the only writable workspace. AGENTKIMI_NO_NET=1adds--unshare-netto drop all network egress.
In-process gate — SECONDARY, defense-in-depth (sandbox.ts)
A PreToolUse hook plus a canUseTool callback confine file operations to the activeworktree (symlink-safe, deepest-existing-ancestor realpath, no lexical fallback) anddeny destructive commands, egress tools, and env/process introspection. This raisesthe bar against casual misuse — but the bwrap namespace is what actually contains adetermined attacker (a regex gate cannot confine an interpreter).
Hardened git operations (worktree.ts)
The worktree contents are fully attacker-controlled, and the server runs git againstthem outside the sandbox — a classic host-RCE surface. So every git call:
- uses an argv array (
execFileSync, no shell) — no path/branch can inject acommand; - passes
--git-dir/--work-treeexplicitly against a git-dir the sandbox can'twrite, so an in-tree.git/.gitattributescan't define a command; - runs with
--no-ext-diff --no-textconv,-c core.hooksPath=/dev/null -c core.fsmonitor=,GIT_CONFIG_SYSTEM/GLOBAL=/dev/null, and a child env with allGIT_*variables stripped (these otherwise bypass the-coverrides); - cleanup uses
fs.rmSync(norm -rfshell string) with a path-prefix check.
Linked-mode filter repos are refused by default. A git repo can definefilter.<name>.smudge/clean in its config and commit a .gitattributes that runsthe filter command on checkout/diff — i.e. opening an untrusted repo can execute codeon the host. agentKimi enumerates the source repo's filter drivers and refuses tocreate a linked worktree if any are defined, unless you opt in withAGENTKIMI_ALLOW_FILTERS=1 (only for repos you trust — e.g. ones using git-lfs).
Minimal secret footprint (launch.sh, config.ts)
launch.sh greps only the Kimi key line out of the env file — it never sourcesthe file, so unrelated secrets never enter the server process. The server builds anexplicit child env for Kimi containing only what it needs; process.env is neverspread.
Honest residual risk
- Network is ON by default (Kimi needs Moonshot + WebFetch). With net on, ajailbroken Kimi could exfiltrate the worktree code it is working on. No hostsecret is reachable (they aren't mounted, and the namespace env is cleared), but set
AGENTKIMI_NO_NET=1for sensitive repos. - The worktree is writable — Kimi can write anything there. Review the returneddiff before merging.
- Requires kernel support for unprivileged user namespaces; bwrap fails with a clearerror at spawn if unavailable.
Setup
1. Install
git clone https://github.com/dominiclynchwoodlands-ui/agentKimi
cd agentKimi
bun install
You also need bubblewrap installed (bwrap --version). On Debian/Ubuntu:sudo apt install bubblewrap; on Arch: sudo pacman -S bubblewrap.
2. Provide the Kimi key
Create an env file containing a single line, and point AGENTKIMI_ENV_FILE at it(only KIMI_API_KEY or MOONSHOT_API_KEY is read):
mkdir -p ~/.agentkimi
printf 'KIMI_API_KEY=%s\n' "<your-moonshot-key>" > ~/.agentkimi/.env
chmod 600 ~/.agentkimi/.env
3. Register with Claude Code
MCP servers live in ~/.claude.json (user scope) — not settings.json. Add:
"agentkimi": {
"type": "stdio",
"command": "/abs/path/to/agentKimi/launch.sh",
"env": { "AGENTKIMI_ENV_FILE": "/home/you/.agentkimi/.env" }
}
or:
claude mcp add agentkimi --scope user \
-e AGENTKIMI_ENV_FILE=/home/you/.agentkimi/.env \
-- /abs/path/to/agentKimi/launch.sh
Restart Claude Code so it picks up the new server.
Configuration
| Env var | Effect |
|---|---|
AGENTKIMI_ENV_FILE |
Path to the file holding the Kimi key (launch.sh greps just that line). |
AGENTKIMI_NO_NET=1 |
Drop all network egress from the sandbox (--unshare-net). |
AGENTKIMI_DENY_PATHS |
Colon-separated paths that may not be used as a workdir (e.g. "$HOME/work/secrets:$HOME/.config"). Empty by default. |
AGENTKIMI_ALLOW_FILTERS=1 |
Permit a linked worktree on a repo that defines git filter drivers (refused by default). Only for repos you trust. |
Runtime state
~/.agentkimi/
.env # your Kimi key (you create this)
cfg/ # isolated config dir: deny-only settings.json + skills symlink
sessions.json # durable session registry (atomic writes + cross-process lock)
worktrees/<id>/ # per-session working trees
gitdirs/<id>/ # separated git-dirs for throwaway repos (never mounted)
Development
bun install
bunx tsc --noEmit # type-check
bun test # security regression suite (no API key needed)
bun smoke.ts # bwrap + SDK integration check (needs KIMI_API_KEY in env)
The *.security.test.ts files are fast, hermetic regressions for the git-RCE andenv-isolation hardening. smoke.ts exercises the real bwrap sandbox and a live SDKturn, and requires KIMI_API_KEY to be exported.
License
MIT