[[
wikihub
]]
Search
⌘K
Explore
People
For Agents
Sign in
Explore
People
For Agents
Sign in
@jacobcole / picortex — planning / docs/adrs/0002-linux-users-over-docker.md
Suggest edit
Cancel
Submit suggestion
Title
Name
Note
--- visibility: public --- # ADR-0002: Linux users over Docker for per-chat isolation **Status:** Accepted (provisional — revisit after [D2 isolation report](../plans/2026-04-23-initial-roadmap.md#d2-security-isolation-report)) **Date:** 2026-04-23 **Deciders:** Jacob ## Context Each chat (1:1 or group) needs an isolated workspace so compromise of chat A cannot read chat B's files, exfiltrate secrets, or influence chat B's Claude Code sessions. Cortex uses **Docker containers per workspace**. picortex explicitly rejects that model. Alternatives: | Option | Spin-up cost | Memory per idle | Security | Complexity | |---|---|---|---|---| | Docker container per chat | seconds + image pull | 100-500 MB | strong | high (daemon, network, volumes) | | Linux user + home dir + POSIX perms | milliseconds | ~ a few MB (tmux+bash) | moderate | low | | firejail / bubblewrap namespace | milliseconds | ~ a few MB | strong | medium (seccomp policy tuning) | | nsjail | milliseconds | ~ a few MB | strong | medium | | Landlock (6.1+ kernel) | ~ nothing | ~ a few MB | strong | medium (new-ish API) | | VM per chat (microVM, Firecracker) | 100s ms | 128+ MB | very strong | high | Jacob's context: - **Single user (Jacob).** Threat model is "sloppy group-chat prompt injection" not "adversarial tenant." - **Small machine.** Target is a 4 GB VPS or Mac Mini. - **Iteration speed matters more than defense-in-depth** at v0.1. - **Prior research** in `~/memory/research/openclaw-group-chat-security.md` and `openclaw-security-audit-2026-02-20.md` documents the prompt-injection threat model in depth. ## Decision **v1: per-chat Unix user + home dir with POSIX permissions (0700) + `runuser`/`sudo -u`.** This will be hardened in D2 by potentially wrapping the tmux session entry point in **bubblewrap** or **Landlock** — those are cheap composable additions to the user-based base, not replacements for it. ## Consequences ### Positive - Sub-second chat provisioning (`useradd`, `mkdir`, `chown`, `tmux new-session`). - Idle cost: one tmux + one bash ≈ a few MB RAM. - No Docker daemon, no image registry, no volume orchestration. - POSIX is boring and well-understood; less novel surface area. - Trivially composes with bubblewrap / Landlock later without re-architecting. ### Negative - Shared kernel. A kernel-level exploit affects everyone. - Shared `/tmp`, shared networking by default. Needs mitigation. - No easy "rm -rf the container" if a chat goes bad — teardown means `userdel -r` + cleanup. - Cap'ed by `useradd` uid range on some systems; unlikely to matter at Jacob's scale. ### Mitigations - **`/tmp` per user:** set `pam_namespace` so `/tmp` is per-user-isolated. - **Network:** Claude Code processes run without `NET_BIND_SERVICE` and behind a firewall that blocks outbound except `api.anthropic.com` and a known allowlist. Egress filter via `iptables` owner-match. - **FS allowlist:** chat user can't write outside its home. Enforced by owner-only permissions on the home dir and read-only bind mounts for shared tools (`/usr/local/bin/picortex-shims`). - **Resource limits:** `cgroups` v2 per chat user — CPU share, memory cap (256 MB default), pids.max. - **Seccomp / Landlock:** added in D2 if threat model changes. - **Teardown path:** `userdel -rf chat-<hex>` + remove sudoers drop-in. Scripted in `scripts/destroy-chat.sh`. ## Trigger for revisit - D2 report finds Linux-user perms insufficient for the threat model. - First isolation incident. - Jacob invites a second user to picortex. - picortex opens to multi-tenant. ## Alternatives considered in detail - **Docker containers per chat:** rejected (see top of file). Would add ~50 MB image-cache baseline per chat and >1 s spin-up; operationally heavy for a personal tool. - **OpenClaw sandbox:** rejected per explicit user directive ("no OpenClaw"). Also: sandbox model is whole-agent, not per-chat. - **Firejail:** considered; keep as a possible D2 addition. Its default profiles are friendlier than bubblewrap for interactive shells. - **bubblewrap (`bwrap`):** the strongest "cheap" hardening option; composes well with Linux users. Primary D2 candidate. - **nsjail:** more flexible than bwrap but more config; hold for D2. - **Microsandbox / gVisor / Firecracker:** overkill for v1. ## References - `~/memory/research/openclaw-group-chat-security.md` - `~/memory/research/openclaw-security-audit-2026-02-20.md` - [Cortex R2.1](../wiki/cortex-inheritance.md#r2-workspace-identity) — "each chat = own filesystem" invariant