There's no clean way to run a session's commands as a non-root user today.
- The container's control plane runs as root (legitimately — it appends the CF runtime CA cert to the system bundle, manages FUSE mounts, etc.).
Bun.spawn for the persistent bash shell inherits root.
- Every
session.exec() framing script runs as root.
User code that wants to run unprivileged (e.g. Claude Code's --permission-mode bypassPermissions refuses under root) has no good options:
exec runuser -u claude bash as the first scripted command hangs: exec replaces bash before the framing script writes its exit-code file, so session.exec() polls forever.
runuser -u claude bash (no exec) spawns a child shell, blocking the parent root bash until the child exits — same hang.
- Per-command
runuser -u <user> -- bash -lc <cmd> wrappers work but pollute every call site and lose the persistent shell state across commands (cwd, env, functions defined in earlier commands).
This is related to #677.
Proposal
Add user?: string to SessionOptions. When set, the container resolves it to a uid/gid at session boot and spawns the persistent bash shell under that user via Bun.spawn's uid/gid options. Control plane stays root;
only the session shell drops privileges.
const session = await sandbox.createSession({ user: 'claude' });
await session.exec('whoami'); // → 'claude'
Persistent shell state still works because privilege drop happens once at spawn, not per command.
There's no clean way to run a session's commands as a non-root user today.
Bun.spawnfor the persistent bash shell inherits root.session.exec()framing script runs as root.User code that wants to run unprivileged (e.g. Claude Code's
--permission-mode bypassPermissionsrefuses under root) has no good options:exec runuser -u claude bashas the first scripted command hangs:execreplaces bash before the framing script writes its exit-code file, sosession.exec()polls forever.runuser -u claude bash(noexec) spawns a child shell, blocking the parent root bash until the child exits — same hang.runuser -u <user> -- bash -lc <cmd>wrappers work but pollute every call site and lose the persistent shell state across commands (cwd, env, functions defined in earlier commands).This is related to #677.
Proposal
Add
user?: stringtoSessionOptions. When set, the container resolves it to a uid/gid at session boot and spawns the persistent bash shell under that user viaBun.spawn'suid/gidoptions. Control plane stays root;only the session shell drops privileges.
Persistent shell state still works because privilege drop happens once at spawn, not per command.