Skip to main content
Glama

Rattan

An MCP server that runs commands inside an Arch Linux sandbox: seccomp (pledge-style) + user namespaces + bubblewrap + Landlock + overlayfs. Agent changes are discarded unless you call env_commit.

Implemented: host capability probe, a C stage3 inner binary (no_new_privs → Landlock → seccomp), a bootstrapped Arch rootfs with pacman, content-addressed overlay commits, and 22 MCP tools.

The server exposes sandboxed file tools — rattan_read_file, rattan_write_file, rattan_edit, rattan_grep — that mirror Vibe's host-side file tools but operate inside the sandbox. They accept container paths only (under /workspace or /tmp); host paths are rejected loudly. With --bind-cwd, /workspace maps to the host launch directory, so sandboxed writes land in the real project. These let subagents do file I/O without Vibe's host-touching tools.

┌───────────────┐   stdio    ┌────────────────────────────────────────────┐
│  MCP client   │ ◀────────▶ │  rattan server (Python, trusted, unpledged) │
│  (agent/LLM)  │            └────────────────────────────────────────────┘
                                      │
                              bwrap + overlayfs
                                      │
                 ┌────────────────────┼─────────────────────┐
                 ▼                    ▼                     ▼
        ┌────────────────┐   ┌────────────────┐   ┌──────────────────┐
        │ base rootfs    │   │ committed      │   │ session upperdir │
        │ (read-only)    │   │ layers (COW)   │   │ (writable,       │
        │                │   │                │   │  discard default)│
        └────────────────┘   └────────────────┘   └──────────────────┘
                            ┌───────────────────────────────┐
                            │  /init = stage3 (C binary)     │
                            │  no_new_privs → Landlock →     │
                            │  seccomp → execvp(user cmd)    │
                            └───────────────────────────────┘

Security

Layers, outermost to innermost:

  • user namespaces — commands run as unprivileged uid/gid 1000

  • bubblewrap --unshare-all — separate mount/PID/IPC/UTS/network namespaces; network denied in agent mode

  • overlayfs COW — read-only base rootfs + committed layers; writes land in a session-only upperdir

  • stage3 (/init) — applies no_new_privs, Landlock, rlimits, seccomp before execvp

stage3 denies keyctl, add_key, request_key, ptrace (unless gdb), unshare, setns, mount, pivot_root, umount2, reboot.

It's a containment tool, not a malware sandbox: the host kernel and host tools are trusted. Network is only available to pacman_install (provisioning). Redirects (>, >>, <, 2>, 2>&1) are applied, with targets confined to /workspace and /tmp.

Enforceable invariants, each with a test: docs/security-invariants.md.

Requirements

  • Linux ≥ 6.2, unprivileged userns enabled, Landlock active, bubblewrap, overlayfs

  • Python ≥ 3.10

make verify checks all of it.

Build

git clone https://github.com/jmars/rattan.git && cd rattan
python3 -m venv .venv && .venv/bin/pip install -e .
make stage3            # needs cosmocc + assimilate on PATH
make bootstrap-rootfs  # base Arch rootfs → ~/.local/share/rattan/rootfs/base
make verify

Run

rattan --probe   # host capability check
rattan           # MCP server over stdio

MCP client config:

{ "mcpServers": { "rattan": {
  "command": "/path/to/rattan/.venv/bin/rattan",
  "args": []
}}}

Tools (18)

  • command execution: shell_run, shell_list

  • environment: env_status, env_reset/env_discard, env_commit, env_snapshot_list, env_rollback, env_gc

  • packages: pacman_install, pacman_run

  • background jobs: shell_job_start, shell_job_status, shell_job_wait, shell_job_output, shell_job_kill, shell_job_list

  • host access: bind_host_dir

Examples

shell_run(command="uname -a", structured=False)
# "Linux rattan ... GNU/Linux"

pacman_install(packages=["tree"])   # network
shell_run(command="tree /workspace")
env_discard()                        # tree is gone again

env_commit(message="added tree")     # → {commit_id, ...}
env_snapshot_list()
env_rollback(to_commit_id="...")
env_gc()

pacman_run(args=["-Q", "tree"])      # read-only, no network

shell_job_start(command="make -j4", cwd="/workspace")
shell_job_wait(job_id=1)

bind_host_dir(host_path="/home/me/data", mount_point="/mnt/data", mode="ro")

Only user data directories can be bound: a non-hidden subdir under $HOME (e.g. ~/projects/foo). bind_host_dir rejects /, all system dirs (/etc /proc /sys /usr /var /boot /dev /run /bin /lib /root /tmp /opt /srv ...), another user's home, $HOME itself and every hidden $HOME/.* subtree (config/credentials like .ssh, .config), and the rattan data dir.

Server CLI

The MCP server accepts a few startup flags:

rattan [--bind HOST=MOUNT[:ro|rw] ...] [--bind-cwd]
  • --bind HOST=MOUNT[:ro|rw] — bind a host directory into the container for every session (read-write by default; --bind-ro is the read-only alias). Repeatable. Same path rules as bind_host_dir above.

  • --bind-cwd — bind the directory the server was launched from onto /workspace (read-write), with /workspace as the default working directory. The agent then operates directly on that host directory via /workspace — no cd into a container-specific path is needed, because /workspace is the host dir the server launched from. Both reads and writes flow straight through to the host directory.

Verify

make verify   # host gate: capability probe, bwrap, stage3, overlay, shell_run
make test     # 187 tests

Docs

docs/architecture.md · docs/implementation-plan.md · docs/security-invariants.md · docs/bootstrap.md · docs/decisions/

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jmars/rattan'

If you have feedback or need assistance with the MCP directory API, please join our Discord server