Paliadin: per-user HOME profile for tmux/claude isolation #21
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The Paliadin PoC currently runs the per-user
claudepanes (paliad-paliadin-<userid8>) under m's normalHOME=/home/m. That means every claude session — regardless of which Paliad user fired it — inherits:~/.claude/skills/directory (everymai-*,lex-*,youpc-*skill, plus all themcp__*tool surfaces these wire up).mcp.json(today: paliad/Supabase only — but no enforcement that it stays minimal)~/.netrc,~/.config/age,~/.dotfiles, etc. via filesystem reachability under themuserFor the single-tenant PoC this is fine —
services.PaliadinOwnerEmailgates/paliadinto m only. The moment a second account is allowed in, every privileged surface above leaks across users.Direct prompt-pane access (
tmux attach -t paliad-paliadin-<userid8>) for debugging is non-negotiable. Container-on-mRiver is overkill and breaks that workflow.Proposal
Stage A — Custom HOME per user (this issue)
LocalPaliadinService.startSession(userID)setsHOME=<profileRoot>/<userID>on the spawned tmux session. A newensureProfile(userID)helper bootstraps the profile lazily:What this buys:
mai-memory, nomai-mail, nolex-*..netrc/ dotfiles / other repos via skill-defaults that read from$HOME.tmux attach -t paliad-paliadin-<userid>still works exactly as today — same machine, samemuser, just a differentHOMEenv.What it does not buy:
cat /home/m/.netrcif it actively tries — protection here is "agent doesn't see it, won't ask for it", not "agent cannot reach it".Default
profileRoot:/var/lib/paliadin/profiles(override viaPALIADIN_PROFILE_ROOT). Falls back to<repo>/.paliadin-profiles/for local dev where m doesn't want to touch/var/lib.Stage B —
bwrapwrap (separate follow-up issue, do not block A)When a non-trusted second user arrives, wrap the tmux launch in
bwrapwith:--ro-bind /usr /usr --ro-bind /etc /etc--bind <profileRoot>/<userID> <profileRoot>/<userID>--bind /home/m/dev/paliad /home/m/dev/paliad(read-only? TBD)--bind /tmp/paliadin /tmp/paliadin--unshare-pid --unshare-net=disable(or net filter for Supabase only)Kernel-enforced isolation.
tmux attachstill possible viansenterfrom the host — small friction but acceptable.Not in scope for this issue. File when needed.
Stage C — Container-per-user (skip)
Docker / nspawn buys multi-tenancy but breaks the
tmux attachdebugging flow we explicitly want to keep. Skip unless / until paliad goes hosted with multiple paying tenants.Implementation sketch (Stage A)
internal/services/paliadin_profile.go(new):ensureProfile(userID uuid.UUID) (homeDir string, err error). Creates the dir tree on first call, idempotent thereafter. Symlinks~/.claude/skills/paliadininto the profile so skill updates from the canonical source still flow through.internal/services/paliadin.go: when runningtmux new-session, prependenv HOME=<profile> ...to the spawn command (or usetmux set-environment HOME <profile>beforenew-session).cmd/server/main.go: readPALIADIN_PROFILE_ROOT, default sensibly, log it on boot next topaliadin: local tmux mode.processLateFileand friends keep working (the response dir is unchanged, onlyHOMEchanges).Risks
.mcp.jsonmust not omit Supabase by accident — would silently break every SQL recipe in the skill. Add a smoke-test: spawn a fresh profile, run a turn, assertused_toolsincludesmcp__supabase__execute_sqlfor a known data question.~/.claude/skills/paliadincouples profiles to m's home — fine for laptop PoC, but if/when this runs on a server without/home/m, the install path needs to change. Use an env var (PALIADIN_SKILL_SOURCE) that defaults to~/.claude/skills/paliadinbut can point elsewhere.Out of scope
bwrap).docs/design-paliadin-2026-05-07.md §2).HOME-scoped MCP data files.