Create docs/adrs/0002-linux-users-over-docker.md
3d73e76de8b9 jacobcole 2026-04-23 1 file
new file mode 100644
index 0000000..772d592
@@ -0,0 +1,83 @@
+---
+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
\ No newline at end of file